Merge "Change CtsJdwpTestCases to run with new agent JDWP implementation." am: 8e28f3e392 am: a1fc0e5db1
am: 766dd2d046

Change-Id: I3ce6a19a355927649f53ae1e90b5f019815839d8
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cc2aad9..5f3e99f 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,8 @@
                       tests/tests/animation/
                       tests/tests/print/
                       tests/tests/text/
+                      tests/tests/graphics/
                       tests/tests/transition/
+                      tests/tests/uirendering/
                       tests/tests/view/
                       tests/tests/widget/
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index 03b45d1..a21108e 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -31,12 +31,19 @@
 python -V 2>&1 | grep -q "Python 2.7" || \
     echo ">> Require python 2.7" >&2
 
-for M in numpy PIL Image matplotlib pylab scipy.stats scipy.spatial
+for M in numpy PIL matplotlib scipy.stats scipy.spatial
 do
     python -c "import $M" >/dev/null 2>&1 || \
         echo ">> Require Python $M module" >&2
 done
 
+for N in 'PIL Image' 'matplotlib pylab'
+do
+    IFS=' ' read module submodule <<< "$N"
+    python -c "from $module import $submodule" >/dev/null 2>&1 || \
+        echo ">> Require Python $module module $submodule submodule" >&2
+done
+
 CV2_VER=$(python -c "\
 try:
     import cv2
@@ -45,8 +52,8 @@
     print \"N/A\"
 ")
 
-echo $CV2_VER | grep -q "^2.4" || \
-    echo ">> Require python opencv 2.4. Got $CV2_VER" >&2
+echo $CV2_VER | grep -q -e "^2.4" -e "^3.2" || \
+    echo ">> Require python opencv 2.4. or 3.2. Got $CV2_VER" >&2
 
 export PYTHONPATH="$PWD/pymodules:$PYTHONPATH"
 
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index e6f096f..3d409a1 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -446,6 +446,33 @@
     return props.has_key("android.lens.info.minimumFocusDistance") and \
         props["android.lens.info.minimumFocusDistance"] == 0
 
+def logical_multi_camera(props):
+    """Returns whether a device is a logical multi-camera.
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.request.availableCapabilities") and \
+           11 in props["android.request.availableCapabilities"]
+
+def logical_multi_camera_physical_ids(props):
+    """Returns a logical multi-camera's underlying physical cameras.
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        list of physical cameras backing the logical multi-camera.
+    """
+    physicalIdsList = []
+    if logical_multi_camera(props):
+        physicalIds = props['android.logicalMultiCamera.physicalIds'];
+        physicalIdsString = ''.join(chr(e) for e in physicalIds);
+        physicalIdsList = physicalIdsString.split('\x00');
+    return physicalIdsList
 
 def debug_mode():
     """Returns True/False for whether test is run in debug mode.
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index 83e654e..3020548 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -12,27 +12,37 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import matplotlib
-matplotlib.use('Agg')
-
-import its.error
-from matplotlib import pylab
-import sys
-from PIL import Image
-import numpy
-import math
-import unittest
-import cStringIO
-import scipy.stats
-import copy
-import cv2
 import os
+import unittest
+
+import cv2
+import its.caps
+import its.device
+import its.error
+import numpy
+
+VGA_HEIGHT = 480
+VGA_WIDTH = 640
+
 
 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))
     return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
 
+
+def gray_scale_img(img):
+    """Return gray scale version of image."""
+    if len(img.shape) == 2:
+        img_gray = img.copy()
+    elif len(img.shape) == 3:
+        if img.shape[2] == 1:
+            img_gray = img[:, :, 0].copy()
+        else:
+            img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
+    return img_gray
+
+
 class Chart(object):
     """Definition for chart object.
 
@@ -57,6 +67,23 @@
         self._scale_start = scale_start
         self._scale_stop = scale_stop
         self._scale_step = 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() as cam:
+                props = cam.get_camera_properties()
+                if its.caps.read_3a(props):
+                    self.locate(cam, props)
+                else:
+                    print 'Chart locator skipped.'
+                    self._set_scale_factors_to_one()
+
+    def _set_scale_factors_to_one(self):
+        """Set scale factors to 1.0 for skipped tests."""
+        self.wnorm = 1.0
+        self.hnorm = 1.0
+        self.xnorm = 0.0
+        self.ynorm = 0.0
+        self.scale = 1.0
 
     def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
         """Take an image with s, e, & fd to find the chart location.
@@ -79,7 +106,7 @@
         req['android.lens.focusDistance'] = fd
         cap_chart = its.image.stationary_lens_cap(cam, req, fmt)
         img_3a = its.image.convert_capture_to_rgb_image(cap_chart, props)
-        img_3a = its.image.flip_mirror_img_per_argv(img_3a)
+        img_3a = its.image.rotate_img_per_argv(img_3a)
         its.image.write_image(img_3a, 'af_scene.jpg')
         template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
         focal_l = cap_chart['metadata']['android.lens.focalLength']
@@ -95,41 +122,38 @@
         print 'Chart/image scale factor = %.2f' % scale_factor
         return template, img_3a, scale_factor
 
-    def locate(self, cam, props, fmt, s, e, fd):
-        """Find the chart in the image.
+    def locate(self, cam, props):
+        """Find the chart in the image, and append location to chart object.
 
-        Args:
-            cam:            An open device session
-            props:          Properties of cam
-            fmt:            Image format for the capture
-            s:              Sensitivity for the AF request as defined in
-                            android.sensor.sensitivity
-            e:              Exposure time for the AF request as defined in
-                            android.sensor.exposureTime
-            fd:             float; autofocus lens position
-
-        Returns:
+        The values appended are:
             xnorm:          float; [0, 1] left loc of chart in scene
             ynorm:          float; [0, 1] top loc of chart in scene
             wnorm:          float; [0, 1] width of chart in scene
             hnorm:          float; [0, 1] height of chart in scene
+            scale:          float; scale factor to extract chart
+
+        Args:
+            cam:            An open device session
+            props:          Camera properties
         """
-        chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
-                                                          s, e, fd)
+        if its.caps.read_3a(props):
+            s, e, _, _, fd = cam.do_3a(get_results=True)
+            fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+            chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
+                                                              s, e, fd)
+        else:
+            print 'Chart locator skipped.'
+            self._set_scale_factors_to_one()
+            return
         scale_start = self._scale_start * s_factor
         scale_stop = self._scale_stop * s_factor
         scale_step = self._scale_step * s_factor
+        self.scale = s_factor
         max_match = []
         # check for normalized image
         if numpy.amax(scene) <= 1.0:
             scene = (scene * 255.0).astype(numpy.uint8)
-        if len(scene.shape) == 2:
-            scene_gray = scene.copy()
-        elif len(scene.shape) == 3:
-            if scene.shape[2] == 1:
-                scene_gray = scene[:, :, 0]
-            else:
-                scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
+        scene_gray = gray_scale_img(scene)
         print 'Finding chart in scene...'
         for scale in numpy.arange(scale_start, scale_stop, scale_step):
             scene_scaled = scale_img(scene_gray, scale)
@@ -142,26 +166,33 @@
         # determine if optimization results are valid
         opt_values = [x[0] for x in max_match]
         if 2.0*min(opt_values) > max(opt_values):
-            estring = ('Unable to find chart in scene!\n'
+            estring = ('Warning: unable to find chart in scene!\n'
                        'Check camera distance and self-reported '
                        'pixel pitch, focal length and hyperfocal distance.')
-            raise its.error.Error(estring)
-        # find max and draw bbox
-        match_index = max_match.index(max(max_match, key=lambda x: x[0]))
-        scale = scale_start + scale_step * match_index
-        print 'Optimum scale factor: %.3f' %  scale
-        top_left_scaled = max_match[match_index][1]
-        h, w = chart.shape
-        bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
-        top_left = (int(top_left_scaled[0]/scale),
-                    int(top_left_scaled[1]/scale))
-        bottom_right = (int(bottom_right_scaled[0]/scale),
-                        int(bottom_right_scaled[1]/scale))
-        wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
-        hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
-        xnorm = float(top_left[0]) / scene.shape[1]
-        ynorm = float(top_left[1]) / scene.shape[0]
-        return xnorm, ynorm, wnorm, hnorm
+            print estring
+            self._set_scale_factors_to_one()
+        else:
+            if (max(opt_values) == opt_values[0] or
+                        max(opt_values) == opt_values[len(opt_values)-1]):
+                estring = ('Warning: chart is at extreme range of locator '
+                           'check.\n')
+                print estring
+            # find max and draw bbox
+            match_index = max_match.index(max(max_match, key=lambda x: x[0]))
+            self.scale = scale_start + scale_step * match_index
+            print 'Optimum scale factor: %.3f' %  self.scale
+            top_left_scaled = max_match[match_index][1]
+            h, w = chart.shape
+            bottom_right_scaled = (top_left_scaled[0] + w,
+                                   top_left_scaled[1] + h)
+            top_left = (int(top_left_scaled[0]/self.scale),
+                        int(top_left_scaled[1]/self.scale))
+            bottom_right = (int(bottom_right_scaled[0]/self.scale),
+                            int(bottom_right_scaled[1]/self.scale))
+            self.wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
+            self.hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
+            self.xnorm = float(top_left[0]) / scene.shape[1]
+            self.ynorm = float(top_left[1]) / scene.shape[0]
 
 
 class __UnitTest(unittest.TestCase):
@@ -186,7 +217,8 @@
             blur = cv2.blur(chart, (j, j))
             blur = blur[:, :, numpy.newaxis]
             sharpness[j] = (yuv_full_scale *
-                    its.image.compute_image_sharpness(blur / white_level))
+                            its.image.compute_image_sharpness(blur /
+                                                              white_level))
         self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
                                       numpy.sqrt(2), atol=0.1))
         self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 26af162..4fc5fe7 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -25,6 +25,7 @@
 import hashlib
 import numpy
 import string
+import unicodedata
 
 CMD_DELAY = 1  # seconds
 
@@ -118,10 +119,10 @@
             try:
                 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
                 break
-            except socket.error:
+            except socket.error or socket.timeout:
                 if i == NUM_RETRIES - 1:
                     raise its.error.Error(self.device_id,
-                                          "acquiring socket lock timed out")
+                                          "socket lock returns error")
                 else:
                     time.sleep(RETRY_WAIT_TIME_SEC)
 
@@ -207,7 +208,7 @@
               'com.android.cts.verifier/.camera.its.ItsTestActivity '
               '--activity-brought-to-front') % self.adb)
         time.sleep(CMD_DELAY)
-        _run(('%s shell am startservice --user 0 -t text/plain '
+        _run(('%s shell am start-foreground-service --user 0 -t text/plain '
               '-a %s') % (self.adb, self.INTENT_START))
 
         # Wait until the socket is ready to accept a connection.
@@ -385,6 +386,26 @@
         self.props = data['objValue']['cameraProperties']
         return data['objValue']['cameraProperties']
 
+    def get_camera_properties_by_id(self, camera_id):
+        """Get the camera properties object for device with camera_id
+
+        Args:
+            camera_id: The ID string of the camera
+
+        Returns:
+            The Python dictionary object for the CameraProperties object. Empty
+            if no such device exists.
+
+        """
+        cmd = {}
+        cmd["cmdName"] = "getCameraPropertiesById"
+        cmd["cameraId"] = camera_id
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'cameraProperties':
+            raise its.error.Error('Invalid command response')
+        return data['objValue']['cameraProperties']
+
     def do_3a(self, regions_ae=[[0,0,1,1,1]],
                     regions_awb=[[0,0,1,1,1]],
                     regions_af=[[0,0,1,1,1]],
@@ -497,6 +518,17 @@
         "dng", "raw", "raw10", "raw12", or "rawStats". 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
+        current camera device is a logical multi-camera. The physical camera id
+        must refer to a physical camera backing this logical camera device. And
+        only "yuv", "raw", "raw10", "raw12" support the physical camera id field.
+
+        Currently only 2 physical streams with the same format are supported, one
+        from each physical camera:
+        - yuv physical streams of the same size.
+        - raw physical streams with the same or different sizes, depending on
+          device capability. (Different physical cameras may have different raw sizes).
+
         Note that one or more surfaces can be specified, allowing a capture to
         request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
         yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
@@ -658,14 +690,36 @@
             cmd["outputSurfaces"] = [{"format": "yuv",
                                       "width" : max_yuv_size[0],
                                       "height": max_yuv_size[1]}]
+
+        # Figure out requested physical camera ids, physical and logical
+        # streams.
+        physical_cam_ids = {}
+        physical_buffers = {}
+        physical_cam_format = None
+        logical_cam_formats = []
+        for i,s in enumerate(cmd["outputSurfaces"]):
+            if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]:
+                if "physicalCamera" in s:
+                    if physical_cam_format is not None and s["format"] != physical_cam_format:
+                        raise its.error.Error('ITS does not support capturing multiple ' +
+                                              'physical formats yet')
+                    physical_cam_ids[i] = s["physicalCamera"]
+                    physical_buffers[s["physicalCamera"]] = []
+                    physical_cam_format = s["format"]
+                else:
+                    logical_cam_formats.append(s["format"])
+            else:
+                logical_cam_formats.append(s["format"])
+
         ncap = len(cmd["captureRequests"])
         nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
         # Only allow yuv output to multiple targets
-        yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
-        n_yuv = len(yuv_surfaces)
+        logical_yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"\
+                        and "physicalCamera" not in s]
+        n_yuv = len(logical_yuv_surfaces)
         # Compute the buffer size of YUV targets
         yuv_maxsize_1d = 0
-        for s in yuv_surfaces:
+        for s in logical_yuv_surfaces:
             if not ("width" in s and "height" in s):
                 if self.props is None:
                     raise its.error.Error('Camera props are unavailable')
@@ -676,14 +730,14 @@
         yuv_sizes = [c["width"]*c["height"]*3/2
                      if "width" in c and "height" in c
                      else yuv_maxsize_1d
-                     for c in yuv_surfaces]
+                     for c in logical_yuv_surfaces]
         # Currently we don't pass enough metadta from ItsService to distinguish
         # different yuv stream of same buffer size
         if len(yuv_sizes) != len(set(yuv_sizes)):
             raise its.error.Error(
                     'ITS does not support yuv outputs of same buffer size')
-        if len(formats) > len(set(formats)):
-            if n_yuv != len(formats) - len(set(formats)) + 1:
+        if len(logical_cam_formats) > len(set(logical_cam_formats)):
+          if n_yuv != len(logical_cam_formats) - len(set(logical_cam_formats)) + 1:
                 raise its.error.Error('Duplicate format requested')
 
         raw_formats = 0;
@@ -722,6 +776,7 @@
                 "rawStats":[], "dng":[], "jpeg":[]}
         yuv_bufs = {size:[] for size in yuv_sizes}
         mds = []
+        physical_mds = []
         widths = None
         heights = None
         while nbufs < ncap*nsurf or len(mds) < ncap:
@@ -738,12 +793,18 @@
                 nbufs += 1
             elif jsonObj['tag'] == 'captureResults':
                 mds.append(jsonObj['objValue']['captureResult'])
+                physical_mds.append(jsonObj['objValue']['physicalResults'])
                 outputs = jsonObj['objValue']['outputs']
                 widths = [out['width'] for out in outputs]
                 heights = [out['height'] for out in outputs]
             else:
-                # Just ignore other tags
-                None
+                tagString = unicodedata.normalize('NFKD', jsonObj['tag']).encode('ascii', 'ignore');
+                for x in ['rawImage', 'raw10Image', 'raw12Image', 'yuvImage']:
+                    if (tagString.startswith(x)):
+                        physicalId = jsonObj['tag'][len(x):];
+                        if physicalId in physical_cam_ids.values():
+                            physical_buffers[physicalId].append(buf)
+                            nbufs += 1
         rets = []
         for j,fmt in enumerate(formats):
             objs = []
@@ -752,8 +813,17 @@
                 obj["width"] = widths[j]
                 obj["height"] = heights[j]
                 obj["format"] = fmt
-                obj["metadata"] = mds[i]
-                if fmt == 'yuv':
+                if j in physical_cam_ids:
+                    for physical_md in physical_mds[i]:
+                        if physical_cam_ids[j] in physical_md:
+                            obj["metadata"] = physical_md[physical_cam_ids[j]]
+                            break
+                else:
+                    obj["metadata"] = mds[i]
+
+                if j in physical_cam_ids:
+                    obj["data"] = physical_buffers[physical_cam_ids[j]][i]
+                elif fmt == 'yuv':
                     buf_size = widths[j] * heights[j] * 3 / 2
                     obj["data"] = yuv_bufs[buf_size][i]
                 else:
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index 24b48bb..1654faa8 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -414,7 +414,7 @@
     return img
 
 
-def get_black_level(chan, props, cap_res):
+def get_black_level(chan, props, cap_res=None):
     """Return the black level to use for a given capture.
 
     Uses a dynamic value from the capture result if available, else falls back
@@ -428,7 +428,7 @@
     Returns:
         The black level value for the specified channel.
     """
-    if (cap_res.has_key('android.sensor.dynamicBlackLevel') and
+    if (cap_res is not None and cap_res.has_key('android.sensor.dynamicBlackLevel') and
             cap_res['android.sensor.dynamicBlackLevel'] is not None):
         black_levels = cap_res['android.sensor.dynamicBlackLevel']
     else:
@@ -642,11 +642,14 @@
     """
     hfull = img.shape[0]
     wfull = img.shape[1]
-    xtile = math.ceil(xnorm * wfull)
-    ytile = math.ceil(ynorm * hfull)
-    wtile = math.floor(wnorm * wfull)
-    htile = math.floor(hnorm * hfull)
-    return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
+    xtile = int(math.ceil(xnorm * wfull))
+    ytile = int(math.ceil(ynorm * hfull))
+    wtile = int(math.floor(wnorm * wfull))
+    htile = int(math.floor(hnorm * hfull))
+    if len(img.shape)==2:
+        return img[ytile:ytile+htile,xtile:xtile+wtile].copy()
+    else:
+        return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
 
 
 def compute_image_means(img):
@@ -697,6 +700,22 @@
     return snr
 
 
+def compute_image_max_gradients(img):
+    """Calculate the maximum gradient of each color channel in the image.
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+
+    Returns:
+        A list of gradient max values, one per color channel in the image.
+    """
+    grads = []
+    chans = img.shape[2]
+    for i in xrange(chans):
+        grads.append(numpy.amax(numpy.gradient(img[:, :, i])))
+    return grads
+
+
 def write_image(img, fname, apply_gamma=False):
     """Save a float-3 numpy array image to a file.
 
@@ -780,6 +799,7 @@
     [gy, gx] = numpy.gradient(luma)
     return numpy.average(numpy.sqrt(gy*gy + gx*gx))
 
+
 def normalize_img(img):
     """Normalize the image values to between 0 and 1.
 
@@ -790,21 +810,39 @@
     """
     return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img))
 
-def flip_mirror_img_per_argv(img):
-    """Flip/mirror an image if "flip" or "mirror" is in argv
+
+def chart_located_per_argv():
+    """Determine if chart already located outside of test.
+
+    If chart info provided, return location and size. If not, return None.
+
+    Args:
+        None
+    Returns:
+        chart_loc:  float converted xnorm,ynorm,wnorm,hnorm,scale from argv text.
+                    argv is of form 'chart_loc=0.45,0.45,0.1,0.1,1.0'
+    """
+    for s in sys.argv[1:]:
+        if s[:10] == "chart_loc=" and len(s) > 10:
+            chart_loc = s[10:].split(",")
+            return map(float, chart_loc)
+    return None, None, None, None, None
+
+
+def rotate_img_per_argv(img):
+    """Rotate an image 180 degrees if "rotate" is in argv
 
     Args:
         img: 2-D numpy array of image values
     Returns:
-        Flip/mirrored image
+        Rotated image
     """
     img_out = img
-    if "flip" in sys.argv:
-        img_out = numpy.flipud(img_out)
-    if "mirror" in sys.argv:
-        img_out = numpy.fliplr(img_out)
+    if "rotate180" in sys.argv:
+        img_out = numpy.fliplr(numpy.flipud(img_out))
     return img_out
 
+
 def stationary_lens_cap(cam, req, fmt):
     """Take up to NUM_TRYS caps and save the 1st one with lens stationary.
 
@@ -829,6 +867,7 @@
             raise its.error.Error('Cannot settle lens after %d trys!' % trys)
     return cap[NUM_FRAMES-1]
 
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/tests/inprog/test_test_patterns.py b/apps/CameraITS/tests/inprog/test_test_patterns.py
deleted file mode 100644
index f75b141..0000000
--- a/apps/CameraITS/tests/inprog/test_test_patterns.py
+++ /dev/null
@@ -1,41 +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.device
-import its.objects
-import os.path
-
-def main():
-    """Test sensor test patterns.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    with its.device.ItsSession() as cam:
-        caps = []
-        for i in range(1,6):
-            req = its.objects.manual_capture_request(100, 10*1000*1000)
-            req['android.sensor.testPatternData'] = [40, 100, 160, 220]
-            req['android.sensor.testPatternMode'] = i
-
-            # Capture the shot twice, and use the second one, so the pattern
-            # will have stabilized.
-            caps = cam.do_capture([req]*2)
-
-            img = its.image.convert_capture_to_rgb_image(caps[1])
-            its.image.write_image(img, "%s_pattern=%d.jpg" % (NAME, i))
-
-if __name__ == '__main__':
-    main()
-
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 752e02b..e78488e 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -90,8 +90,8 @@
         pixel_pitch_w = (sensor_size["width"] / fmts[0]["width"] * 1E3)
         print "Assert pixel_pitch WxH: %.2f um, %.2f um" % (pixel_pitch_w,
                                                             pixel_pitch_h)
-        assert 1.0 <= pixel_pitch_w <= 10
-        assert 1.0 <= pixel_pitch_h <= 10
+        assert 0.9 <= pixel_pitch_w <= 10
+        assert 0.9 <= pixel_pitch_h <= 10
         assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
 
         diag = math.sqrt(sensor_size["height"] ** 2 +
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index c3c2147..dfd9ed8 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -12,19 +12,21 @@
 # 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.image
 import its.objects
 import its.target
 
-def main():
-    """Test that the android.sensor.sensitivity parameter is applied properly
-    within a burst. Inspects the output metadata only (not the image data).
-    """
+NUM_STEPS = 3
+ERROR_TOLERANCE = 0.97  # Allow ISO to be rounded down by 3%
 
-    NUM_STEPS = 3
-    ERROR_TOLERANCE = 0.97 # Allow ISO to be rounded down by 3%
+
+def main():
+    """Test android.sensor.sensitivity parameter applied properly in burst.
+
+    Inspects the output metadata only (not the image data).
+    """
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -35,15 +37,17 @@
         sens_step = (sens_range[1] - sens_range[0]) / NUM_STEPS
         sens_list = range(sens_range[0], sens_range[1], sens_step)
         e = min(props['android.sensor.info.exposureTimeRange'])
-        reqs = [its.objects.manual_capture_request(s,e) for s in sens_list]
-        _,fmt = its.objects.get_fastest_manual_capture_settings(props)
+        reqs = [its.objects.manual_capture_request(s, e) for s in sens_list]
+        _, fmt = its.objects.get_fastest_manual_capture_settings(props)
 
         caps = cam.do_capture(reqs, fmt)
-        for i,cap in enumerate(caps):
+        for i, cap in enumerate(caps):
             s_req = sens_list[i]
-            s_res = cap["metadata"]["android.sensor.sensitivity"]
-            assert(s_req >= s_res)
-            assert(s_res/float(s_req) > ERROR_TOLERANCE)
+            s_res = cap['metadata']['android.sensor.sensitivity']
+            msg = 's_write: %d, s_read: %d, TOL: %.2f' % (s_req, s_res,
+                                                          ERROR_TOLERANCE)
+            assert s_req >= s_res, msg
+            assert s_res/float(s_req) > ERROR_TOLERANCE, msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
new file mode 100644
index 0000000..a1d9cb8
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -0,0 +1,174 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+NAME = os.path.basename(__file__).split('.')[0]
+PATTERNS = [1, 2]
+COLOR_BAR_ORDER = ['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]}
+CH_TOL = 2E-3  # 1/2 DN in [0:1]
+LSFR_COEFFS = 0b100010000  # PN9
+
+
+def check_solid_color(cap, props):
+    """Simple test for solid color.
+
+    Args:
+        cap: capture element
+        props: capture properties
+    Returns:
+        True/False
+    """
+    print 'Checking solid TestPattern...'
+    r, gr, gb, b = its.image.convert_capture_to_planes(cap, props)
+    r_tile = its.image.get_image_patch(r, 0.0, 0.0, 1.0, 1.0)
+    gr_tile = its.image.get_image_patch(gr, 0.0, 0.0, 1.0, 1.0)
+    gb_tile = its.image.get_image_patch(gb, 0.0, 0.0, 1.0, 1.0)
+    b_tile = its.image.get_image_patch(b, 0.0, 0.0, 1.0, 1.0)
+    var_max = max(np.amax(r_tile), np.amax(gr_tile), np.amax(gb_tile),
+                  np.amax(b_tile))
+    var_min = min(np.amin(r_tile), np.amin(gr_tile), np.amin(gb_tile),
+                  np.amin(b_tile))
+    white_level = int(props['android.sensor.info.whiteLevel'])
+    print ' pixel min: %.f, pixel max: %.f' % (white_level*var_min,
+                                               white_level*var_max)
+    return np.isclose(var_max, var_min, atol=CH_TOL)
+
+
+def check_color_bars(cap, props, mirror=False):
+    """Test image for color bars.
+
+    Compute avg of bars and compare to ideal
+
+    Args:
+        cap:            capture element
+        props:          capture properties
+        mirror (bool):  whether to mirror image or not
+    Returns:
+        True/False
+    """
+    print 'Checking color bar TestPattern...'
+    delta = 0.0005
+    num_bars = len(COLOR_BAR_ORDER)
+    color_match = []
+    img = its.image.convert_capture_to_rgb_image(cap, props=props)
+    if mirror:
+        print ' Image mirrored'
+        img = np.fliplr(img)
+    for i, color in enumerate(COLOR_BAR_ORDER):
+        tile = its.image.get_image_patch(img, float(i)/num_bars+delta,
+                                         0.0, 1.0/num_bars-2*delta, 1.0)
+        color_match.append(np.allclose(its.image.compute_image_means(tile),
+                                       COLOR_CHECKER[color], atol=CH_TOL))
+    print COLOR_BAR_ORDER
+    print color_match
+    return all(color_match)
+
+
+def check_pattern(cap, props, pattern):
+    """Simple tests for pattern correctness.
+
+    Args:
+        cap: capture element
+        props: capture properties
+        pattern (int): valid number for pattern
+    Returns:
+        boolean
+    """
+
+    # white_level = int(props['android.sensor.info.whiteLevel'])
+    if pattern == 1:  # solid color
+        return check_solid_color(cap, props)
+
+    elif pattern == 2:  # color bars
+        striped = check_color_bars(cap, props, mirror=False)
+        # check mirrored version in case image rotated from sensor orientation
+        if not striped:
+            striped = check_color_bars(cap, props, mirror=True)
+        return striped
+
+    else:
+        print 'No specific test for TestPattern %d' % pattern
+        return True
+
+
+def test_test_patterns(cam, props, af_fd):
+    """test image sensor test patterns.
+
+    Args:
+        cam: An open device session.
+        props: Properties of cam
+        af_fd: Focus distance
+    """
+
+    avail_patterns = props['android.sensor.availableTestPatternModes']
+    print 'avail_patterns: ', avail_patterns
+    sens_min, _ = props['android.sensor.info.sensitivityRange']
+    exposure = min(props['android.sensor.info.exposureTimeRange'])
+
+    for pattern in PATTERNS:
+        if pattern in avail_patterns:
+            req = its.objects.manual_capture_request(int(sens_min),
+                                                     exposure)
+            req['android.lens.focusDistance'] = af_fd
+            req['android.sensor.testPatternMode'] = pattern
+            fmt = {'format': 'raw'}
+            cap = cam.do_capture(req, fmt)
+            img = its.image.convert_capture_to_rgb_image(cap, props=props)
+
+            # Save pattern
+            its.image.write_image(img, '%s_%d.jpg' % (NAME, pattern), True)
+
+            # Check pattern for correctness
+            assert check_pattern(cap, props, pattern)
+        else:
+            print 'Pattern not in android.sensor.availableTestPatternModes.'
+
+
+def main():
+    """Test pattern generation test.
+
+    Test: capture frames for each valid test pattern and check if
+    generated correctly.
+    android.sensor.testPatternMode
+    0: OFF
+    1: SOLID_COLOR
+    2: COLOR_BARS
+    3: COLOR_BARS_FADE_TO_GREY
+    4: PN9
+    """
+
+    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))
+
+        # For test pattern, use min_fd
+        fd = props['android.lens.info.minimumFocusDistance']
+        test_test_patterns(cam, props, fd)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
new file mode 100644
index 0000000..3103cd8
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_3a.py b/apps/CameraITS/tests/scene1/test_3a.py
index 08cd747..2c5dcfe 100644
--- a/apps/CameraITS/tests/scene1/test_3a.py
+++ b/apps/CameraITS/tests/scene1/test_3a.py
@@ -12,8 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.device
 import its.caps
+import its.device
+
+import numpy as np
+
 
 def main():
     """Basic test for bring-up of 3A.
@@ -26,14 +29,18 @@
         its.caps.skip_unless(its.caps.read_3a(props))
 
         sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
-        print "AE: sensitivity %d, exposure %dms" % (sens, exp/1000000)
-        print "AWB: gains", gains, "transform", xform
-        print "AF: distance", focus
-        assert(sens > 0)
-        assert(exp > 0)
-        assert(len(gains) == 4)
-        assert(len(xform) == 9)
-        assert(focus >= 0)
+        print 'AE: sensitivity %d, exposure %dms' % (sens, exp/1000000)
+        print 'AWB: gains', gains, 'transform', xform
+        print 'AF: distance', focus
+        assert sens > 0
+        assert exp > 0
+        assert len(gains) == 4
+        for g in gains:
+            assert not np.isnan(g)
+        assert len(xform) == 9
+        for x in xform:
+            assert not np.isnan(x)
+        assert focus >= 0
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_ae_af.py b/apps/CameraITS/tests/scene1/test_ae_af.py
index 626a475..ed94017 100644
--- a/apps/CameraITS/tests/scene1/test_ae_af.py
+++ b/apps/CameraITS/tests/scene1/test_ae_af.py
@@ -16,7 +16,7 @@
 import its.device
 import its.target
 
-import numpy
+import numpy as np
 
 GAIN_LENGTH = 4
 TRANSFORM_LENGTH = 9
@@ -39,12 +39,12 @@
         for k, v in sorted(SINGLE_A.items()):
             print k
             try:
-                s, e, g, xform, fd = cam.do_3a(get_results=True,
-                                               do_ae=v[0],
-                                               do_af=v[1],
-                                               do_awb=v[2])
+                s, e, gains, xform, fd = cam.do_3a(get_results=True,
+                                                   do_ae=v[0],
+                                                   do_af=v[1],
+                                                   do_awb=v[2])
                 print ' sensitivity', s, 'exposure', e
-                print ' gains', g, 'transform', xform
+                print ' gains', gains, 'transform', xform
                 print ' fd', fd
                 print ''
             except its.error.Error:
@@ -52,10 +52,14 @@
             if k == 'full_3a':
                 assert s > 0
                 assert e > 0
-                assert len(g) == 4
+                assert len(gains) == 4
+                for g in gains:
+                    assert not np.isnan(g)
                 assert len(xform) == 9
+                for x in xform:
+                    assert not np.isnan(x)
                 assert fd >= 0
-                assert numpy.isclose(g[2], GREEN_GAIN, GREEN_GAIN_TOL)
+                assert np.isclose(gains[2], GREEN_GAIN, GREEN_GAIN_TOL)
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index ec41bcd..870dc62 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -30,8 +30,8 @@
 THRESHOLD_MAX_LEVEL = 0.9
 THRESHOLD_MAX_LEVEL_DIFF = 0.045
 THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
-THRESHOLD_ROUND_DOWN_GAIN = 0.1
-THRESHOLD_ROUND_DOWN_EXP = 0.05
+THRESH_ROUND_DOWN_GAIN = 0.1
+THRESH_ROUND_DOWN_EXP = 0.05
 
 
 def get_raw_active_array_size(props):
@@ -90,12 +90,16 @@
             e_test = s_e_product / s_test
             print 'Testing s:', s_test, 'e:', e_test
             req = its.objects.manual_capture_request(
-                s_test, e_test, 0.0, True, props)
+                    s_test, e_test, 0.0, True, props)
             cap = cam.do_capture(req, fmt)
             s_res = cap['metadata']['android.sensor.sensitivity']
             e_res = cap['metadata']['android.sensor.exposureTime']
-            assert 0 <= s_test - s_res < s_test * THRESHOLD_ROUND_DOWN_GAIN
-            assert 0 <= e_test - e_res < e_test * THRESHOLD_ROUND_DOWN_EXP
+            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_test/1.0E6, e_res/1.0E6, THRESH_ROUND_DOWN_EXP*100)
+            assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
+            assert 0 <= e_test - e_res < e_test * THRESH_ROUND_DOWN_EXP, e_msg
             s_e_product_res = s_res * e_res
             request_result_ratio = s_e_product / s_e_product_res
             print 'Capture result s:', s_test, 'e:', e_test
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index 2d8c678..94cef80 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -12,17 +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
-import os.path
+
+NAME = os.path.basename(__file__).split('.')[0]
+Y_DELTA = 0.1
+GRADIENT_DELTA = 0.1
+
 
 def main():
-    """Test that the android.flash.mode parameter is applied.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
+    """Test that the android.flash.mode parameter is applied."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -32,7 +35,8 @@
 
         flash_modes_reported = []
         flash_states_reported = []
-        g_means = []
+        means = []
+        grads = []
 
         # Manually set the exposure to be a little on the dark side, so that
         # it should be obvious whether the flash fired or not, and use a
@@ -45,29 +49,31 @@
             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"]
+        e, s = its.target.get_target_exposure_combos(cam)['midExposureTime']
         e /= 4
         req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
-        for f in [0,1,2]:
-            req["android.flash.mode"] = f
+        for f in [0, 1, 2]:
+            req['android.flash.mode'] = f
             cap = cam.do_capture(req, fmt)
-            flash_modes_reported.append(cap["metadata"]["android.flash.mode"])
-            flash_states_reported.append(cap["metadata"]["android.flash.state"])
-            img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, "%s_mode=%d.jpg" % (NAME, f))
-            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-            rgb = its.image.compute_image_means(tile)
-            g_means.append(rgb[1])
+            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)
+            its.image.write_image(y, '%s_%d.jpg' % (NAME, f))
+            tile = its.image.get_image_patch(y, 0.375, 0.375, 0.25, 0.25)
+            its.image.write_image(tile, '%s_%d_tile.jpg' % (NAME, f))
+            means.append(its.image.compute_image_means(tile)[0])
+            grads.append(its.image.compute_image_max_gradients(tile)[0])
 
-        assert(flash_modes_reported == [0,1,2])
-        assert(flash_states_reported[0] not in [3,4])
-        assert(flash_states_reported[1] in [3,4])
-        assert(flash_states_reported[2] in [3,4])
+        assert flash_modes_reported == [0, 1, 2]
+        assert flash_states_reported[0] not in [3, 4]
+        assert flash_states_reported[1] in [3, 4]
+        assert flash_states_reported[2] in [3, 4]
 
-        print "G brightnesses:", g_means
-        assert(g_means[1] > g_means[0])
-        assert(g_means[2] > g_means[0])
+        print 'Brightnesses:', means
+        print 'Max gradients: ', grads
+        assert means[1]-means[0] > Y_DELTA or grads[1]-grads[0] > GRADIENT_DELTA
+        assert means[2]-means[0] > Y_DELTA or grads[2]-grads[0] > GRADIENT_DELTA
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_raw_exposure.py b/apps/CameraITS/tests/scene1/test_raw_exposure.py
new file mode 100644
index 0000000..3a0dcf6
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_raw_exposure.py
@@ -0,0 +1,160 @@
+# 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 its.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import numpy as np
+from matplotlib import pylab
+import matplotlib.pyplot
+
+IMG_STATS_GRID = 9  # find used to find the center 11.11%
+NAME = os.path.basename(__file__).split(".")[0]
+NUM_ISO_STEPS = 5
+SATURATION_TOL = 0.01
+BLK_LVL_TOL = 0.1
+# Test 3 steps per 2x exposure
+EXP_MULT = pow(2, 1.0/3)
+INCREASING_THR = 0.99
+# slice captures into burst of SLICE_LEN requests
+SLICE_LEN = 10
+
+def main():
+    """Capture a set of raw images with increasing exposure time and measure the pixel values.
+    """
+
+    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.read_3a(props) and
+                             its.caps.per_frame_control(props))
+        debug = its.caps.debug_mode()
+
+        # Expose for the scene with min sensitivity
+        exp_min, exp_max = props["android.sensor.info.exposureTimeRange"]
+        sens_min, _ = props["android.sensor.info.sensitivityRange"]
+        # Digital gains might not be visible on RAW data
+        sens_max = props["android.sensor.maxAnalogSensitivity"]
+        sens_step = (sens_max - sens_min) / NUM_ISO_STEPS
+        white_level = float(props["android.sensor.info.whiteLevel"])
+        black_levels = [its.image.get_black_level(i,props) for i in range(4)]
+        # Get the active array width and height.
+        aax = props["android.sensor.info.activeArraySize"]["left"]
+        aay = props["android.sensor.info.activeArraySize"]["top"]
+        aaw = props["android.sensor.info.activeArraySize"]["right"]-aax
+        aah = props["android.sensor.info.activeArraySize"]["bottom"]-aay
+        raw_stat_fmt = {"format": "rawStats",
+                        "gridWidth": aaw/IMG_STATS_GRID,
+                        "gridHeight": aah/IMG_STATS_GRID}
+
+        e_test = []
+        mult = 1.0
+        while exp_min*mult < exp_max:
+            e_test.append(int(exp_min*mult))
+            mult *= EXP_MULT
+        if e_test[-1] < exp_max * INCREASING_THR:
+            e_test.append(int(exp_max))
+        e_test_ms = [e / 1000000.0 for e in e_test]
+
+        for s in range(sens_min, sens_max, sens_step):
+            means = []
+            means.append(black_levels)
+            reqs = [its.objects.manual_capture_request(s, e, 0) for e in e_test]
+            # Capture raw in debug mode, rawStats otherwise
+            caps = []
+            for i in range(len(reqs) / SLICE_LEN):
+                if debug:
+                    caps += cam.do_capture(reqs[i*SLICE_LEN:(i+1)*SLICE_LEN], cam.CAP_RAW)
+                else:
+                    caps += cam.do_capture(reqs[i*SLICE_LEN:(i+1)*SLICE_LEN], raw_stat_fmt)
+            last_n = len(reqs) % SLICE_LEN
+            if last_n == 1:
+                if debug:
+                    caps += [cam.do_capture(reqs[-last_n:], cam.CAP_RAW)]
+                else:
+                    caps += [cam.do_capture(reqs[-last_n:], raw_stat_fmt)]
+            elif last_n > 0:
+                if debug:
+                    caps += cam.do_capture(reqs[-last_n:], cam.CAP_RAW)
+                else:
+                    caps += cam.do_capture(reqs[-last_n:], raw_stat_fmt)
+
+            # Measure the mean of each channel.
+            # Each shot should be brighter (except underexposed/overexposed scene)
+            for i,cap in enumerate(caps):
+                if debug:
+                    planes = its.image.convert_capture_to_planes(cap, props)
+                    tiles = [its.image.get_image_patch(p, 0.445, 0.445, 0.11, 0.11) for p in planes]
+                    mean = [m * white_level for tile in tiles
+                            for m in its.image.compute_image_means(tile)]
+                    img = its.image.convert_capture_to_rgb_image(cap, props=props)
+                    its.image.write_image(img, "%s_s=%d_e=%05d.jpg" % (NAME, s, e_test))
+                else:
+                    mean_image, _ = its.image.unpack_rawstats_capture(cap)
+                    mean = mean_image[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+
+                print "ISO=%d, exposure time=%.3fms, mean=%s" % (
+                        s, e_test[i] / 1000000.0, str(mean))
+                means.append(mean)
+
+
+            # means[0] is black level value
+            r = [m[0] for m in means[1:]]
+            gr = [m[1] for m in means[1:]]
+            gb = [m[2] for m in means[1:]]
+            b = [m[3] for m in means[1:]]
+
+            pylab.plot(e_test_ms, r, "r.-")
+            pylab.plot(e_test_ms, b, "b.-")
+            pylab.plot(e_test_ms, gr, "g.-")
+            pylab.plot(e_test_ms, gb, "k.-")
+            pylab.xscale('log')
+            pylab.yscale('log')
+            pylab.title("%s ISO=%d" % (NAME, s))
+            pylab.xlabel("Exposure time (ms)")
+            pylab.ylabel("Center patch pixel mean")
+            matplotlib.pyplot.savefig("%s_s=%d.png" % (NAME, s))
+            pylab.clf()
+
+            allow_under_saturated = True
+            for i in xrange(1, len(means)):
+                prev_mean = means[i-1]
+                mean = means[i]
+
+                if np.isclose(max(mean), white_level, rtol=SATURATION_TOL):
+                    print "Saturated: white_level %f, max_mean %f"% (white_level, max(mean))
+                    break;
+
+                if allow_under_saturated and np.allclose(mean, black_levels, rtol=BLK_LVL_TOL):
+                    # All channel means are close to black level
+                    continue
+
+                allow_under_saturated = False
+                # Check pixel means are increasing (with small tolerance)
+                channels = ["Red", "Gr", "Gb", "Blue"]
+                for chan in range(4):
+                    err_msg = "ISO=%d, %s, exptime %3fms mean: %.2f, %s mean: %.2f, TOL=%.f%%" % (
+                            s, channels[chan],
+                            e_test_ms[i-1], mean[chan],
+                            "black level" if i == 1 else "exptime %3fms"%e_test_ms[i-2],
+                            prev_mean[chan],
+                            INCREASING_THR*100)
+                    assert mean[chan] > prev_mean[chan] * INCREASING_THR, err_msg
+
+if __name__ == "__main__":
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
index 343c960..dd7ef21 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -12,20 +12,20 @@
 # 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 RAW and YUV outputs.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.035
+    """Test capturing a single frame as both RAW and YUV outputs."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -38,34 +38,35 @@
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
-        if 0 in props['android.shading.availableModes']:
-            req["android.shading.mode"] = 0
+        mode = req["android.shading.mode"]
+        print "shading mode:", mode
 
-        max_raw_size = \
-                its.objects.get_available_output_sizes("raw", props)[0]
-        w,h = its.objects.get_available_output_sizes(
+        max_raw_size = its.objects.get_available_output_sizes("raw", props)[0]
+        w, h = its.objects.get_available_output_sizes(
                 "yuv", props, (1920, 1080), max_raw_size)[0]
-        out_surfaces = [{"format":"raw"},
-                        {"format":"yuv", "width":w, "height":h}]
+        out_surfaces = [{"format": "raw"},
+                        {"format": "yuv", "width": w, "height": h}]
         cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
-        its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
+        its.image.write_image(img, "%s_shading=%d_yuv.jpg" % (NAME, mode), True)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         rgb0 = its.image.compute_image_means(tile)
 
         # Raw shots are 1/2 x 1/2 smaller after conversion to RGB, but tile
         # cropping is relative.
         img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
-        its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+        its.image.write_image(img, "%s_shading=%d_raw.jpg" % (NAME, mode), True)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         rgb1 = its.image.compute_image_means(tile)
 
         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)
+        print msg
+        assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
index 6ecdca7..9c0c69b 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -12,20 +12,20 @@
 # 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 RAW10 and YUV outputs.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.035
+    """Test capturing a single frame as both RAW10 and YUV outputs."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -38,34 +38,36 @@
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
-        if 0 in props['android.shading.availableModes']:
-            req["android.shading.mode"] = 0
+        mode = req["android.shading.mode"]
+        print "shading mode:", mode
 
-        max_raw10_size = \
-                its.objects.get_available_output_sizes("raw10", props)[0]
-        w,h = its.objects.get_available_output_sizes(
+        max_raw10_size = its.objects.get_available_output_sizes("raw10",
+                                                                props)[0]
+        w, h = its.objects.get_available_output_sizes(
                 "yuv", props, (1920, 1080), max_raw10_size)[0]
-        cap_raw, cap_yuv = cam.do_capture(req,
-                [{"format":"raw10"},
-                 {"format":"yuv", "width":w, "height":h}])
+        out_surfaces = [{"format": "raw10"},
+                        {"format": "yuv", "width": w, "height": h}]
+        cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
-        its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
+        its.image.write_image(img, "%s_shading=%d_yuv.jpg" % (NAME, mode), True)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         rgb0 = its.image.compute_image_means(tile)
 
         # Raw shots are 1/2 x 1/2 smaller after conversion to RGB, but tile
         # cropping is relative.
         img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
-        its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+        its.image.write_image(img, "%s_shading=%d_raw.jpg" % (NAME, mode), True)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         rgb1 = its.image.compute_image_means(tile)
 
         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)
+        print msg
+        assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf b/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
new file mode 100644
index 0000000..7b64817
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2/test_faces.py
index 4e30fc1..2acce85 100644
--- a/apps/CameraITS/tests/scene2/test_faces.py
+++ b/apps/CameraITS/tests/scene2/test_faces.py
@@ -60,7 +60,7 @@
                 # but it should detect at least one face in last frame
                 if i == NUM_TEST_FRAMES - 1:
                     img = its.image.convert_capture_to_rgb_image(cap, props=props)
-                    img = its.image.flip_mirror_img_per_argv(img)
+                    img = its.image.rotate_img_per_argv(img)
                     img_name = "%s_fd_mode_%s.jpg" % (NAME, fd_mode)
                     its.image.write_image(img, img_name)
                     if len(faces) == 0:
diff --git a/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
new file mode 100644
index 0000000..a3e18e2
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene3/test_3a_consistency.py b/apps/CameraITS/tests/scene3/test_3a_consistency.py
index f43b3eb..2dbee4c 100644
--- a/apps/CameraITS/tests/scene3/test_3a_consistency.py
+++ b/apps/CameraITS/tests/scene3/test_3a_consistency.py
@@ -41,14 +41,14 @@
         fds = []
         for _ in range(NUM_TEST_ITERATIONS):
             try:
-                s, e, g, xform, fd = cam.do_3a(get_results=True)
+                s, e, gains, xform, fd = cam.do_3a(get_results=True)
                 print ' sensitivity', s, 'exposure', e
-                print ' gains', g, 'transform', xform
+                print ' gains', gains, 'transform', xform
                 print ' fd', fd
                 print ''
                 exps.append(e)
                 senses.append(s)
-                g_gains.append(g[2])
+                g_gains.append(gains[2])
                 fds.append(fd)
             except its.error.Error:
                 print ' FAIL\n'
@@ -57,6 +57,10 @@
         assert np.isclose(np.amax(senses), np.amin(senses), SENS_TOL)
         assert np.isclose(np.amax(g_gains), np.amin(g_gains), GGAIN_TOL)
         assert np.isclose(np.amax(fds), np.amin(fds), FD_TOL)
+        for g in gains:
+            assert not np.isnan(g)
+        for x in xform:
+            assert not np.isnan(x)
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
new file mode 100644
index 0000000..f0d17ec
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -0,0 +1,135 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import cv2
+
+import its.caps
+import its.cv2image
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+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
+(X_CROP, Y_CROP) = (0.5, 0.5)  # crop center area of ISO12233 chart
+
+
+def test_flip_mirror(cam, props, fmt, chart):
+    """Return if image is flipped or mirrored.
+
+    Args:
+        cam (class): An open device session
+        props (class): Properties of cam
+        fmt (dict): Capture format
+        chart (class): Object with chart properties
+
+    Returns:
+        boolean: True if flipped, False if not
+    """
+
+    # determine if in debug mode
+    debug = its.caps.debug_mode()
+
+    # get a local copy of the chart template
+    template = cv2.imread(CHART_FILE, cv2.IMREAD_ANYDEPTH)
+
+    # take img, crop chart, scale and prep for cv2 template match
+    s, e, _, _, fd = cam.do_3a(get_results=True)
+    req = its.objects.manual_capture_request(s, e, fd)
+    cap = cam.do_capture(req, fmt)
+    y, _, _ = its.image.convert_capture_to_planes(cap, props)
+    y = its.image.rotate_img_per_argv(y)
+    patch = its.image.get_image_patch(y, chart.xnorm, chart.ynorm,
+                                      chart.wnorm, chart.hnorm)
+    patch = 255 * its.cv2image.gray_scale_img(patch)
+    patch = its.cv2image.scale_img(patch.astype(np.uint8), chart.scale)
+
+    # sanity check on image
+    assert np.max(patch)-np.min(patch) > 255/8
+
+    # save full images if in debug
+    if debug:
+        its.image.write_image(template[:, :, np.newaxis]/255.0,
+                              '%s_template.jpg' % NAME)
+
+    # save patch
+    its.image.write_image(patch[:, :, np.newaxis]/255.0,
+                          '%s_scene_patch.jpg' % NAME)
+
+    # crop center areas and strip off any extra rows/columns
+    template = its.image.get_image_patch(template, (1-X_CROP)/2, (1-Y_CROP)/2,
+                                         X_CROP, Y_CROP)
+    patch = its.image.get_image_patch(patch, (1-X_CROP)/2,
+                                      (1-Y_CROP)/2, X_CROP, Y_CROP)
+    patch = patch[0:min(patch.shape[0], template.shape[0]),
+                  0:min(patch.shape[1], template.shape[1])]
+    comp_chart = patch
+
+    # determine optimum orientation
+    opts = []
+    for orientation in CHART_ORIENTATIONS:
+        if orientation == 'flip':
+            comp_chart = np.flipud(patch)
+        elif orientation == 'mirror':
+            comp_chart = np.fliplr(patch)
+        elif orientation == 'rotate':
+            comp_chart = np.flipud(np.fliplr(patch))
+        correlation = cv2.matchTemplate(comp_chart, template, cv2.TM_CCOEFF)
+        _, opt_val, _, _ = cv2.minMaxLoc(correlation)
+        if debug:
+            cv2.imwrite('%s_%s.jpg' % (NAME, orientation), comp_chart)
+        print ' %s correlation value: %d' % (orientation, opt_val)
+        opts.append(opt_val)
+
+    # determine if 'nominal' or 'rotated' is best orientation
+    assert_flag = (opts[0] == max(opts) or opts[3] == max(opts))
+    assert assert_flag, ('Optimum orientation is %s' %
+                         CHART_ORIENTATIONS[np.argmax(opts)])
+    # print warning if rotated
+    if opts[3] == max(opts):
+        print 'Image is rotated 180 degrees. Try "rotate" flag.'
+
+
+def main():
+    """Test if image is properly oriented."""
+
+    print '\nStarting test_flip_mirror.py'
+
+    # 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)
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.read_3a(props))
+        fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+
+        # test that image is not flipped, mirrored, or rotated
+        test_flip_mirror(cam, props, fmt, chart)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index cd563be..aaa8484 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -37,19 +37,20 @@
 CHART_SCALE_STEP = 0.025
 
 
-def test_lens_movement_reporting(cam, props, fmt, sensitivity, exp, af_fd):
+def test_lens_movement_reporting(cam, props, fmt, gain, exp, af_fd, chart):
     """Return fd, sharpness, lens state of the output images.
 
     Args:
         cam: An open device session.
         props: Properties of cam
         fmt: dict; capture format
-        sensitivity: Sensitivity for the 3A request as defined in
+        gain: Sensitivity for the 3A request as defined in
             android.sensor.sensitivity
         exp: Exposure time for the 3A request as defined in
             android.sensor.exposureTime
         af_fd: Focus distance for the 3A request as defined in
             android.lens.focusDistance
+        chart: Object that contains chart information
 
     Returns:
         Object containing reported sharpness of the output image, keyed by
@@ -57,15 +58,6 @@
             'sharpness'
     """
 
-    # initialize chart class
-    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                               CHART_SCALE_START, CHART_SCALE_STOP,
-                               CHART_SCALE_STEP)
-
-    # find chart location
-    xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
-                                              exp, af_fd)
-
     # initialize variables and take data sets
     data_set = {}
     white_level = int(props['android.sensor.info.whiteLevel'])
@@ -74,7 +66,7 @@
     fds = sorted(fds * NUM_IMGS)
     reqs = []
     for i, fd in enumerate(fds):
-        reqs.append(its.objects.manual_capture_request(sensitivity, exp))
+        reqs.append(its.objects.manual_capture_request(gain, exp))
         reqs[i]['android.lens.focusDistance'] = fd
     caps = cam.do_capture(reqs, fmt)
     for i, cap in enumerate(caps):
@@ -92,12 +84,12 @@
         print ' current lens location (diopters): %.3f' % data['loc']
         print ' lens moving %r' % data['lens_moving']
         y, _, _ = its.image.convert_capture_to_planes(cap, props)
-        y = its.image.flip_mirror_img_per_argv(y)
-        chart = its.image.normalize_img(its.image.get_image_patch(y,
-                                                                  xnorm, ynorm,
-                                                                  wnorm, hnorm))
-        its.image.write_image(chart, '%s_i=%d_chart.jpg' % (NAME, i))
-        data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+        y = its.image.rotate_img_per_argv(y)
+        chart.img = its.image.normalize_img(its.image.get_image_patch(
+                y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+        its.image.write_image(chart.img, '%s_i=%d_chart.jpg' % (NAME, i))
+        data['sharpness'] = white_level*its.image.compute_image_sharpness(
+                chart.img)
         print 'Chart sharpness: %.1f\n' % data['sharpness']
         data_set[i] = data
     return data_set
@@ -110,10 +102,16 @@
     """
 
     print '\nStarting test_lens_movement_reporting.py'
+    # initialize chart class
+    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+                               CHART_SCALE_START, CHART_SCALE_STOP,
+                               CHART_SCALE_STEP)
+
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(not its.caps.fixed_focus(props))
-        its.caps.skip_unless(its.caps.lens_approx_calibrated(props))
+        its.caps.skip_unless(its.caps.read_3a(props) and
+                             its.caps.lens_approx_calibrated(props))
         min_fd = props['android.lens.info.minimumFocusDistance']
         fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
 
@@ -121,7 +119,7 @@
         s, e, _, _, fd = cam.do_3a(get_results=True)
 
         # Get sharpness for each focal distance
-        d = test_lens_movement_reporting(cam, props, fmt, s, e, fd)
+        d = test_lens_movement_reporting(cam, props, fmt, s, e, fd, chart)
         for k in sorted(d):
             print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
                    'sharpness: %.1f  \tlens_moving: %r \t'
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index f850e3d..feb28a8 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -38,7 +38,7 @@
 CHART_SCALE_STEP = 0.025
 
 
-def test_lens_position(cam, props, fmt, sensitivity, exp, af_fd):
+def test_lens_position(cam, props, fmt, sensitivity, exp, chart):
     """Return fd, sharpness, lens state of the output images.
 
     Args:
@@ -49,8 +49,7 @@
             android.sensor.sensitivity
         exp: Exposure time for the 3A request as defined in
             android.sensor.exposureTime
-        af_fd: Focus distance for the 3A request as defined in
-            android.lens.focusDistance
+        chart: Object with chart properties
 
     Returns:
         Dictionary of results for different focal distance captures
@@ -58,15 +57,6 @@
         d_static, d_moving
     """
 
-    # initialize chart class
-    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                                CHART_SCALE_START, CHART_SCALE_STOP,
-                                CHART_SCALE_STEP)
-
-    # find chart location
-    xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
-                                              exp, af_fd)
-
     # initialize variables and take data sets
     data_static = {}
     data_moving = {}
@@ -89,11 +79,11 @@
         print ' focus distance (diopters): %.3f' % data['fd']
         print ' current lens location (diopters): %.3f' % data['loc']
         y, _, _ = its.image.convert_capture_to_planes(cap, props)
-        chart = its.image.normalize_img(its.image.get_image_patch(y,
-                                                                  xnorm, ynorm,
-                                                                  wnorm, hnorm))
-        its.image.write_image(chart, '%s_stat_i=%d_chart.jpg' % (NAME, i))
-        data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+        chart.img = its.image.normalize_img(its.image.get_image_patch(
+                y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+        its.image.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (NAME, i))
+        data['sharpness'] = white_level*its.image.compute_image_sharpness(
+                chart.img)
         print 'Chart sharpness: %.1f\n' % data['sharpness']
         data_static[i] = data
     # take moving data set
@@ -115,12 +105,12 @@
         print ' focus distance (diopters): %.3f' % data['fd']
         print ' current lens location (diopters): %.3f' % data['loc']
         y, _, _ = its.image.convert_capture_to_planes(cap, props)
-        y = its.image.flip_mirror_img_per_argv(y)
-        chart = its.image.normalize_img(its.image.get_image_patch(y,
-                                                                  xnorm, ynorm,
-                                                                  wnorm, hnorm))
-        its.image.write_image(chart, '%s_move_i=%d_chart.jpg' % (NAME, i))
-        data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+        y = its.image.rotate_img_per_argv(y)
+        chart.img = its.image.normalize_img(its.image.get_image_patch(
+                y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+        its.image.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (NAME, i))
+        data['sharpness'] = white_level*its.image.compute_image_sharpness(
+                chart.img)
         print 'Chart sharpness: %.1f\n' % data['sharpness']
         data_moving[i] = data
     return data_static, data_moving
@@ -128,19 +118,24 @@
 
 def main():
     """Test if focus position is properly reported for moving lenses."""
-
     print '\nStarting test_lens_position.py'
+    # initialize chart class
+    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+                               CHART_SCALE_START, CHART_SCALE_STOP,
+                               CHART_SCALE_STEP)
+
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(not its.caps.fixed_focus(props))
-        its.caps.skip_unless(its.caps.lens_calibrated(props))
+        its.caps.skip_unless(its.caps.read_3a(props) and
+                             its.caps.lens_calibrated(props))
         fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
 
-        # Get proper sensitivity, exposure time, and focus distance with 3A.
-        s, e, _, _, fd = cam.do_3a(get_results=True)
+        # Get proper sensitivity and exposure time with 3A
+        s, e, _, _, _ = cam.do_3a(get_results=True)
 
         # Get sharpness for each focal distance
-        d_stat, d_move = test_lens_position(cam, props, fmt, s, e, fd)
+        d_stat, d_move = test_lens_position(cam, props, fmt, s, e, chart)
         print 'Lens stationary'
         for k in sorted(d_stat):
             print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
diff --git a/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf
new file mode 100644
index 0000000..7fb1e42
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/scene4_0.67_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 13dbe84..5e011eb 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -12,18 +12,28 @@
 # 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 os.path
 import cv2
+import its.caps
+import its.device
+import its.image
+import its.objects
 import numpy as np
 
+NAME = os.path.basename(__file__).split(".")[0]
+NUM_DISTORT_PARAMS = 6
+LARGE_SIZE = 2000   # Define the size of a large image
+THRESH_L_AR = 0.02  # aspect ratio test threshold of large images
+THRESH_XS_AR = 0.05  # aspect ratio test threshold of mini images
+THRESH_L_CP = 0.02  # Crop test threshold of large images
+THRESH_XS_CP = 0.05  # Crop test threshold of mini images
+THRESH_MIN_PIXEL = 4  # Crop test allowed offset
+PREVIEW_SIZE = (1920, 1080)  # preview size
+
 
 def main():
-    """ Test aspect ratio and check if images are cropped correctly under each
-    output size
+    """Test aspect ratio & check if images are cropped correctly for each fmt.
+
     Aspect ratio test runs on level3, full and limited devices. Crop test only
     runs on full and level3 devices.
     The test image is a black circle inside a black square. When raw capture is
@@ -35,23 +45,10 @@
     the circle should be close to 1. Considering shooting position error, aspect
     ratio greater than 1.05 or smaller than 0.95 will fail the test.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-    LARGE_SIZE = 2000   # Define the size of a large image
-    # pass/fail threshold of large size images for aspect ratio test
-    THRES_L_AR_TEST = 0.02
-    # pass/fail threshold of mini size images for aspect ratio test
-    THRES_XS_AR_TEST = 0.05
-    # pass/fail threshold of large size images for crop test
-    THRES_L_CP_TEST = 0.02
-    # pass/fail threshold of mini size images for crop test
-    THRES_XS_CP_TEST = 0.05
-    # Crop test will allow at least THRES_MIN_PIXEL offset
-    THRES_MIN_PIXEL = 4
-    PREVIEW_SIZE = (1920, 1080) # preview size
     aspect_ratio_gt = 1  # ground truth
     failed_ar = []  # streams failed the aspect ration test
-    failed_crop = [] # streams failed the crop test
-    format_list = [] # format list for multiple capture objects.
+    failed_crop = []  # streams failed the crop 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"
     # Do single-capture to cover untouched sizes in multi-capture when needed.
@@ -67,9 +64,6 @@
                         "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        # Todo: test for radial distortion enabled devices has not yet been
-        # implemented
-        its.caps.skip_unless(not its.caps.radial_distortion_correction(props))
         its.caps.skip_unless(its.caps.read_3a(props))
         full_device = its.caps.full_or_better(props)
         limited_device = its.caps.limited(props)
@@ -103,17 +97,59 @@
                                          cap_raw["height"])
             img_raw = its.image.convert_capture_to_rgb_image(cap_raw,
                                                              props=props)
+            if its.caps.radial_distortion_correction(props):
+                # 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,
+                # and s is skew of sensor plane vs lens plane.
+                print "Applying intrinsic calibration and distortion params"
+                ical = np.array(props["android.lens.intrinsicCalibration"])
+                msg = "Cannot include radial distortion without intrinsic cal!"
+                assert len(ical) == 5, msg
+                sensor_h = props["android.sensor.info.physicalSize"]["height"]
+                sensor_w = props["android.sensor.info.physicalSize"]["width"]
+                pixel_h = props["android.sensor.info.pixelArraySize"]["height"]
+                pixel_w = props["android.sensor.info.pixelArraySize"]["width"]
+                fd = float(props["android.lens.info.availableFocalLengths"][0])
+                fd_w_pix = pixel_w * fd / sensor_w
+                fd_h_pix = pixel_h * fd / sensor_h
+                # transformation matrix
+                # k = [[f_x, s, c_x],
+                #      [0, f_y, c_y],
+                #      [0,   0,   1]]
+                k = np.array([[ical[0], ical[4], ical[2]],
+                              [0, ical[1], ical[3]],
+                              [0, 0, 1]])
+                print "k:", k
+                e_msg = "fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%" % (
+                        fd_w_pix, ical[0])
+                assert np.isclose(fd_w_pix, ical[0], rtol=0.20), e_msg
+                e_msg = "fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%" % (
+                        fd_h_pix, ical[0])
+                assert np.isclose(fd_h_pix, ical[1], rtol=0.20), e_msg
+
+                # distortion
+                rad_dist = props["android.lens.radialDistortion"]
+                print "android.lens.radialDistortion:", rad_dist
+                e_msg = "%s param(s) found. %d expected." % (len(rad_dist),
+                                                             NUM_DISTORT_PARAMS)
+                assert len(rad_dist) == NUM_DISTORT_PARAMS, e_msg
+                opencv_dist = np.array([rad_dist[1], rad_dist[2],
+                                        rad_dist[4], rad_dist[5],
+                                        rad_dist[3]])
+                print "dist:", opencv_dist
+                img_raw = cv2.undistort(img_raw, k, opencv_dist)
             size_raw = img_raw.shape
-            img_name = "%s_%s_w%d_h%d.png" \
-                       % (NAME, "raw", size_raw[1], size_raw[0])
+            w_raw = size_raw[1]
+            h_raw = size_raw[0]
+            img_name = "%s_%s_w%d_h%d.png" % (NAME, "raw", w_raw, h_raw)
             aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
-                                                         img_raw, 1, img_name,
-                                                         debug)
+                    img_raw, 1, img_name, debug)
             # Normalize the circle size to 1/4 of the image size, so that
             # circle size won"t affect the crop test result
             factor_cp_thres = (min(size_raw[0:1])/4.0) / max(circle_size_raw)
-            thres_l_cp_test = THRES_L_CP_TEST * factor_cp_thres
-            thres_xs_cp_test = THRES_XS_CP_TEST * factor_cp_thres
+            thres_l_cp_test = THRESH_L_CP * factor_cp_thres
+            thres_xs_cp_test = THRESH_XS_CP * factor_cp_thres
 
         # Take pictures of each settings with all the image sizes available.
         for fmt in format_list:
@@ -124,7 +160,7 @@
             if dual_target:
                 sizes = its.objects.get_available_output_sizes(
                         fmt_cmpr, props, fmt["cmpr_size"])
-                if len(sizes) == 0: # device might not support RAW
+                if len(sizes) == 0:  # device might not support RAW
                     continue
                 size_cmpr = sizes[0]
             for size_iter in its.objects.get_available_output_sizes(
@@ -133,10 +169,9 @@
                 h_iter = size_iter[1]
                 # Skip testing same format/size combination
                 # ITS does not handle that properly now
-                if dual_target and \
-                        w_iter == size_cmpr[0] and \
-                        h_iter == size_cmpr[1] and \
-                        fmt_iter == fmt_cmpr:
+                if (dual_target and w_iter == size_cmpr[0]
+                            and h_iter == size_cmpr[1]
+                            and fmt_iter == fmt_cmpr):
                     continue
                 out_surface = [{"width": w_iter,
                                 "height": h_iter,
@@ -150,29 +185,38 @@
                     frm_iter = cap[0]
                 else:
                     frm_iter = cap
-                assert (frm_iter["format"] == fmt_iter)
-                assert (frm_iter["width"] == w_iter)
-                assert (frm_iter["height"] == h_iter)
-                print "Captured %s with %s %dx%d" \
-                        % (fmt_iter, fmt_cmpr, w_iter, h_iter)
+                assert frm_iter["format"] == fmt_iter
+                assert frm_iter["width"] == w_iter
+                assert frm_iter["height"] == h_iter
+                print "Captured %s with %s %dx%d" % (fmt_iter, fmt_cmpr,
+                                                     w_iter, h_iter)
                 img = its.image.convert_capture_to_rgb_image(frm_iter)
-                img_name = "%s_%s_with_%s_w%d_h%d.png" \
-                           % (NAME, fmt_iter, fmt_cmpr, w_iter, h_iter)
-                aspect_ratio, cc_ct, (cc_w, cc_h) = \
-                        measure_aspect_ratio(img, raw_avlb, img_name,
-                                             debug)
+                if its.caps.radial_distortion_correction(props):
+                    w_scale = float(w_iter)/w_raw
+                    h_scale = float(h_iter)/h_raw
+                    k_scale = np.array([[ical[0]*w_scale, ical[4],
+                                         ical[2]*w_scale],
+                                        [0, ical[1]*h_scale, ical[3]*h_scale],
+                                        [0, 0, 1]])
+                    print "k_scale:", k_scale
+                    img = cv2.undistort(img, k_scale, opencv_dist)
+                img_name = "%s_%s_with_%s_w%d_h%d.png" % (NAME,
+                                                          fmt_iter, fmt_cmpr,
+                                                          w_iter, h_iter)
+                aspect_ratio, cc_ct, (cc_w, cc_h) = measure_aspect_ratio(
+                        img, raw_avlb, img_name, debug)
                 # check pass/fail for aspect ratio
-                # image size >= LARGE_SIZE: use THRES_L_AR_TEST
-                # image size == 0 (extreme case): THRES_XS_AR_TEST
-                # 0 < image size < LARGE_SIZE: scale between THRES_XS_AR_TEST
-                # and THRES_L_AR_TEST
-                thres_ar_test = max(THRES_L_AR_TEST,
-                        THRES_XS_AR_TEST + max(w_iter, h_iter) *
-                        (THRES_L_AR_TEST-THRES_XS_AR_TEST)/LARGE_SIZE)
+                # image size >= LARGE_SIZE: use THRESH_L_AR
+                # image size == 0 (extreme case): THRESH_XS_AR
+                # 0 < image size < LARGE_SIZE: scale between THRESH_XS_AR
+                # and THRESH_L_AR
+                thres_ar_test = max(
+                        THRESH_L_AR, THRESH_XS_AR + max(w_iter, h_iter) *
+                        (THRESH_L_AR-THRESH_XS_AR)/LARGE_SIZE)
                 thres_range_ar = (aspect_ratio_gt-thres_ar_test,
                                   aspect_ratio_gt+thres_ar_test)
-                if aspect_ratio < thres_range_ar[0] \
-                        or aspect_ratio > thres_range_ar[1]:
+                if (aspect_ratio < thres_range_ar[0] or
+                            aspect_ratio > thres_range_ar[1]):
                     failed_ar.append({"fmt_iter": fmt_iter,
                                       "fmt_cmpr": fmt_cmpr,
                                       "w": w_iter, "h": h_iter,
@@ -185,29 +229,29 @@
                     # image size == 0 (extreme case): thres_xs_cp_test
                     # 0 < image size < LARGE_SIZE: scale between
                     # thres_xs_cp_test and thres_l_cp_test
-                    # Also, allow at least THRES_MIN_PIXEL off to
+                    # Also, allow at least THRESH_MIN_PIXEL off to
                     # prevent threshold being too tight for very
                     # small circle
-                    thres_hori_cp_test = max(thres_l_cp_test,
-                            thres_xs_cp_test + w_iter *
+                    thres_hori_cp_test = max(
+                            thres_l_cp_test, thres_xs_cp_test + w_iter *
                             (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
-                    min_threshold_h = THRES_MIN_PIXEL / cc_w
+                    min_threshold_h = THRESH_MIN_PIXEL / cc_w
                     thres_hori_cp_test = max(thres_hori_cp_test,
-                            min_threshold_h)
+                                             min_threshold_h)
                     thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
                                         cc_ct_gt["hori"]+thres_hori_cp_test)
-                    thres_vert_cp_test = max(thres_l_cp_test,
-                            thres_xs_cp_test + h_iter *
+                    thres_vert_cp_test = max(
+                            thres_l_cp_test, thres_xs_cp_test + h_iter *
                             (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
-                    min_threshold_v = THRES_MIN_PIXEL / cc_h
+                    min_threshold_v = THRESH_MIN_PIXEL / cc_h
                     thres_vert_cp_test = max(thres_vert_cp_test,
-                            min_threshold_v)
+                                             min_threshold_v)
                     thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
                                         cc_ct_gt["vert"]+thres_vert_cp_test)
-                    if cc_ct["hori"] < thres_range_h_cp[0] \
-                            or cc_ct["hori"] > thres_range_h_cp[1] \
-                            or cc_ct["vert"] < thres_range_v_cp[0] \
-                            or cc_ct["vert"] > thres_range_v_cp[1]:
+                    if (cc_ct["hori"] < thres_range_h_cp[0]
+                                or cc_ct["hori"] > thres_range_h_cp[1]
+                                or cc_ct["vert"] < thres_range_v_cp[0]
+                                or cc_ct["vert"] > thres_range_v_cp[1]):
                         failed_crop.append({"fmt_iter": fmt_iter,
                                             "fmt_cmpr": fmt_cmpr,
                                             "w": w_iter, "h": h_iter,
@@ -223,33 +267,34 @@
             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; valid range: %.3f ~ %.3f" % \
-                  (fa["fmt_iter"], fa["fmt_cmpr"], fa["w"], fa["h"], fa["ar"],
-                   fa["valid_range"][0], fa["valid_range"][1])
+            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 crop test results
         failed_image_number_for_crop_test = len(failed_crop)
         if failed_image_number_for_crop_test > 0:
             print "\nCrop test summary"
             print "Images failed in the crop test:"
-            print "Circle center position, (horizontal x vertical), listed " \
-                  "below is relative to the image center."
+            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; " \
-                    "valid horizontal range: %.3f ~ %.3f; " \
-                    "valid vertical range: %.3f ~ %.3f" \
-                    % (fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
-                    fc["ct_hori"], fc["ct_vert"], fc["valid_range_h"][0],
-                    fc["valid_range_h"][1], fc["valid_range_v"][0],
-                    fc["valid_range_v"][1])
+            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_aspect_ratio_test == 0
         if level3_device:
-            assert (failed_image_number_for_crop_test == 0)
+            assert failed_image_number_for_crop_test == 0
 
 
 def measure_aspect_ratio(img, raw_avlb, img_name, debug):
-    """ Measure the aspect ratio of the black circle in the test image.
+    """Measure the aspect ratio of the black circle in the test image.
 
     Args:
         img: Numpy float image array in RGB, with pixel values in [0,1].
@@ -263,17 +308,22 @@
         (circle_w, circle_h): tuple of the circle size
     """
     size = img.shape
-    img = img * 255
+    img *= 255
     # Gray image
-    img_gray = 0.299 * img[:,:,2] + 0.587 * img[:,:,1] + 0.114 * img[:,:,0]
+    img_gray = 0.299*img[:, :, 2] + 0.587*img[:, :, 1] + 0.114*img[:, :, 0]
 
     # otsu threshold to binarize the image
-    ret3, img_bw = cv2.threshold(np.uint8(img_gray), 0, 255,
-            cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+    _, img_bw = cv2.threshold(np.uint8(img_gray), 0, 255,
+                              cv2.THRESH_BINARY + cv2.THRESH_OTSU)
 
     # connected component
-    contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
-            cv2.CHAIN_APPROX_SIMPLE)
+    cv2_version = cv2.__version__
+    if cv2_version.startswith("2.4."):
+        contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
+                                               cv2.CHAIN_APPROX_SIMPLE)
+    elif cv2_version.startswith("3.2."):
+        _, contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
+                                                  cv2.CHAIN_APPROX_SIMPLE)
 
     # Check each component and find the black circle
     min_cmpt = size[0] * size[1] * 0.005
@@ -286,8 +336,8 @@
         # Parental component should exist and the area is acceptable.
         # The coutour of a circle should have at least 5 points
         child_area = cv2.contourArea(ct)
-        if hrch[3] == -1 or child_area < min_cmpt or child_area > max_cmpt or \
-                len(ct) < 15:
+        if (hrch[3] == -1 or child_area < min_cmpt or child_area > max_cmpt
+                    or len(ct) < 15):
             continue
         # Check the shapes of current component and its parent
         child_shape = component_shape(ct)
@@ -303,17 +353,17 @@
         # 5. 0.25*Parent"s area < Child"s area < 0.45*Parent"s area
         # 6. Child is a black, and Parent is white
         # 7. Center of Child and center of parent should overlap
-        if prt_shape["width"] * 0.56 < child_shape["width"] \
-                < prt_shape["width"] * 0.76 \
-                and prt_shape["height"] * 0.56 < child_shape["height"] \
-                < prt_shape["height"] * 0.76 \
-                and child_shape["width"] > 0.1 * size[1] \
-                and child_shape["height"] > 0.1 * size[0] \
-                and 0.30 * prt_area < child_area < 0.50 * prt_area \
-                and img_bw[child_shape["cty"]][child_shape["ctx"]] == 0 \
-                and img_bw[child_shape["top"]][child_shape["left"]] == 255 \
-                and dist_x < 0.1 * child_shape["width"] \
-                and dist_y < 0.1 * child_shape["height"]:
+        if (prt_shape["width"] * 0.56 < child_shape["width"]
+                    < prt_shape["width"] * 0.76
+                    and prt_shape["height"] * 0.56 < child_shape["height"]
+                    < prt_shape["height"] * 0.76
+                    and child_shape["width"] > 0.1 * size[1]
+                    and child_shape["height"] > 0.1 * size[0]
+                    and 0.30 * prt_area < child_area < 0.50 * prt_area
+                    and img_bw[child_shape["cty"]][child_shape["ctx"]] == 0
+                    and img_bw[child_shape["top"]][child_shape["left"]] == 255
+                    and dist_x < 0.1 * child_shape["width"]
+                    and dist_y < 0.1 * child_shape["height"]):
             # If raw capture is not available, check the camera is placed right
             # in front of the test page:
             # 1. Distances between parent and child horizontally on both side,0
@@ -325,13 +375,11 @@
                 dist_right = prt_shape["right"] - child_shape["right"]
                 dist_top = child_shape["top"] - prt_shape["top"]
                 dist_bottom = prt_shape["bottom"] - child_shape["bottom"]
-                if abs(dist_left-dist_right) > 0.05 * child_shape["width"] or \
-                        abs(dist_top-dist_bottom) > \
-                        0.05 * child_shape["height"]:
+                if (abs(dist_left-dist_right) > 0.05 * child_shape["width"]
+                            or abs(dist_top-dist_bottom) > 0.05 * child_shape["height"]):
                     continue
             # Calculate aspect ratio
-            aspect_ratio = float(child_shape["width"]) / \
-                           float(child_shape["height"])
+            aspect_ratio = float(child_shape["width"]) / child_shape["height"]
             circle_ctx = child_shape["ctx"]
             circle_cty = child_shape["cty"]
             circle_w = float(child_shape["width"])
@@ -345,15 +393,15 @@
 
     if num_circle == 0:
         its.image.write_image(img/255, img_name, True)
-        print "No black circle was detected. Please take pictures according " \
-              "to instruction carefully!\n"
-        assert (num_circle == 1)
+        print "No black circle was detected. Please take pictures according",
+        print "to instruction carefully!\n"
+        assert num_circle == 1
 
     if num_circle > 1:
         its.image.write_image(img/255, img_name, True)
-        print "More than one black circle was detected. Background of scene " \
-              "may be too complex.\n"
-        assert (num_circle == 1)
+        print "More than one black circle was detected. Background of scene",
+        print "may be too complex.\n"
+        assert num_circle == 1
 
     # draw circle center and image center, and save the image
     line_width = max(1, max(size)/500)
@@ -390,13 +438,13 @@
         its.image.write_image(img/255, img_name, True)
 
     print "Aspect ratio: %.3f" % aspect_ratio
-    print "Circle center position regarding to image center: %.3fx%.3f" % \
-            (cc_ct["vert"], cc_ct["hori"])
+    print "Circle center position regarding to image center:",
+    print "%.3fx%.3f" % (cc_ct["vert"], cc_ct["hori"])
     return aspect_ratio, cc_ct, (circle_w, circle_h)
 
 
 def component_shape(contour):
-    """ Measure the shape for a connected component in the aspect ratio test
+    """Measure the shape for a connected component in the aspect ratio test.
 
     Args:
         contour: return from cv2.findContours. A list of pixel coordinates of
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index e8a5b81..faacaf9 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -116,7 +116,7 @@
             # Split by comma and convert each dimension to int.
             [w, h] = map(int, s[9:].split(","))
         elif s[:12] == "test_length=" and len(s) > 12:
-            test_length = int(s[12:])
+            test_length = float(s[12:])
 
     # Collect or load the camera+gyro data. All gyro events as well as camera
     # timestamps are in the "events" dictionary, and "frames" is a list of
@@ -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)
@@ -447,13 +447,12 @@
         fmt = {"format": "yuv", "width": w, "height": h}
         s, e, _, _, _ = cam.do_3a(get_results=True, do_af=False)
         req = its.objects.manual_capture_request(s, e)
-        fps = 30
         req["android.lens.focusDistance"] = 1 / (CHART_DISTANCE * CM_TO_M)
         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" % (
-            w, h, s, e*NSEC_TO_MSEC)
-        caps = cam.do_capture([req]*fps*test_length, fmt)
+        print "Capturing %dx%d with sens. %d, exp. time %.1fms at %dfps" % (
+            w, h, s, e*NSEC_TO_MSEC, fps)
+        caps = cam.do_capture([req]*int(fps*test_length), fmt)
 
         # Get the gyro events.
         print "Reading out sensor events"
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
index 4e245f4..b46a311 100644
--- a/apps/CameraITS/tools/load_scene.py
+++ b/apps/CameraITS/tools/load_scene.py
@@ -21,13 +21,16 @@
 
 def main():
     """Load charts on device and display."""
-    camera_id = -1
     scene = None
     for s in sys.argv[1:]:
         if s[:6] == 'scene=' and len(s) > 6:
             scene = s[6:]
         elif s[:7] == 'screen=' and len(s) > 7:
             screen_id = s[7:]
+        elif s[:5] == 'dist=' and len(s) > 5:
+            chart_distance = float(re.sub('cm', '', s[5:]))
+        elif s[:4] == 'fov=' and len(s) > 4:
+            camera_fov = float(s[4:])
 
     cmd = ('adb -s %s shell am force-stop com.google.android.apps.docs' %
            screen_id)
@@ -43,8 +46,13 @@
 
     remote_scene_file = '/sdcard/Download/%s.pdf' % scene
     local_scene_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
-                                    scene, scene+'.pdf')
-    print 'Loading %s on %s' % (remote_scene_file, screen_id)
+                                    scene)
+    if chart_distance == 20 and camera_fov < 90:
+        local_scene_file = os.path.join(local_scene_file,
+                                        scene+'_0.67_scaled.pdf')
+    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)
     subprocess.Popen(cmd.split())
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 47f7296..e4c20a8 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -13,21 +13,50 @@
 # limitations under the License.
 
 import copy
+import math
 import os
 import os.path
-import tempfile
+import re
 import subprocess
-import time
 import sys
+import tempfile
+import time
 
 import its.caps
+import its.cv2image
 import its.device
 from its.device import ItsSession
+import its.image
 
 CHART_DELAY = 1  # seconds
+CHART_DISTANCE = 30.0  # cm
+CHART_HEIGHT = 13.5  # cm
+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')
 SKIP_RET_CODE = 101  # note this must be same as tests/scene*/test_*
+VGA_HEIGHT = 480
+VGA_WIDTH = 640
+
+
+def calc_camera_fov():
+    """Determine the camera field of view from internal params."""
+    with ItsSession() as cam:
+        props = cam.get_camera_properties()
+    try:
+        focal_l = props['android.lens.info.availableFocalLengths'][0]
+        sensor_size = props['android.sensor.info.physicalSize']
+        diag = math.sqrt(sensor_size['height'] ** 2 +
+                         sensor_size['width'] ** 2)
+        fov = str(round(2 * math.degrees(math.atan(diag / (2 * focal_l))), 2))
+    except ValueError:
+        fov = str(0)
+    print 'Calculated FoV: %s' % fov
+    return fov
 
 
 def evaluate_socket_failure(err_file_path):
@@ -36,6 +65,7 @@
     with open(err_file_path, 'r') as ferr:
         for line in ferr:
             if (line.find('socket.error') != -1 or
+                line.find('socket.timeout') != -1 or
                 line.find('Problem with socket') != -1):
                 socket_fail = True
     return socket_fail
@@ -79,6 +109,7 @@
         tmp_dir: location of temp directory for output files
         skip_scene_validation: force skip scene validation. Used when test scene
                  is setup up front and don't require tester validation.
+        dist:    [Experimental] chart distance in cm.
     """
 
     # Not yet mandated tests
@@ -100,6 +131,7 @@
             ],
         "scene3": [
             "test_3a_consistency",
+            "test_flip_mirror",
             "test_lens_movement_reporting",
             "test_lens_position"
             ],
@@ -139,6 +171,10 @@
     rot_rig_id = None
     tmp_dir = None
     skip_scene_validation = False
+    chart_distance = CHART_DISTANCE
+    chart_height = CHART_HEIGHT
+    approx_equal = lambda a, b, t: abs(a - b) < t
+
     for s in sys.argv[1:]:
         if s[:7] == "camera=" and len(s) > 7:
             camera_ids = s[7:].split(',')
@@ -155,6 +191,8 @@
             tmp_dir = s[8:]
         elif s == 'skip_scene_validation':
             skip_scene_validation = True
+        elif s[:5] == 'dist=' and len(s) > 5:
+            chart_distance = float(re.sub('cm', '', s[5:]))
 
     auto_scene_switch = chart_host_id is not None
     merge_result_switch = result_device_id is not None
@@ -183,7 +221,7 @@
                     break
 
         if not valid_scenes:
-            print "Unknown scene specifiied:", s
+            print 'Unknown scene specified:', s
             assert False
         scenes = temp_scenes
 
@@ -245,6 +283,7 @@
             assert wake_code == 0
 
     for camera_id in camera_ids:
+        camera_fov = calc_camera_fov()
         # Loop capturing images until user confirm test scene is correct
         camera_id_arg = "camera=" + camera_id
         print "Preparing to run ITS on camera", camera_id
@@ -280,9 +319,11 @@
                     if (not merge_result_switch or
                             (merge_result_switch and camera_ids[0] == '0')):
                         scene_arg = 'scene=' + scene
+                        chart_dist_arg = 'dist= ' + str(chart_distance)
+                        fov_arg = 'fov=' + camera_fov
                         cmd = ['python',
                                os.path.join(os.getcwd(), 'tools/load_scene.py'),
-                               scene_arg, screen_id_arg]
+                               scene_arg, chart_dist_arg, fov_arg, screen_id_arg]
                     else:
                         time.sleep(CHART_DELAY)
                 else:
@@ -299,6 +340,18 @@
                     valid_scene_code = subprocess.call(cmd, cwd=topdir)
                     assert valid_scene_code == 0
             print "Start running ITS on camera %s, %s" % (camera_id, scene)
+            # Extract chart from scene for scene3 once up front
+            chart_loc_arg = ''
+            if scene == 'scene3':
+                if float(camera_fov) < 90 and approx_equal(chart_distance, 20,
+                                                           1E-6):
+                    chart_height *= 0.67
+                chart = its.cv2image.Chart(SCENE3_FILE, chart_height,
+                                           chart_distance, CHART_SCALE_START,
+                                           CHART_SCALE_STOP, CHART_SCALE_STEP)
+                chart_loc_arg = 'chart_loc=%.2f,%.2f,%.2f,%.2f,%.3f' % (
+                        chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm,
+                        chart.scale)
             # Run each test, capturing stdout and stderr.
             for (testname, testpath) in tests:
                 if auto_scene_switch:
@@ -330,7 +383,7 @@
                             test_code = skip_code
                     if skip_code is not SKIP_RET_CODE:
                         cmd = ['python', os.path.join(os.getcwd(), testpath)]
-                        cmd += sys.argv[1:] + [camera_id_arg]
+                        cmd += sys.argv[1:] + [camera_id_arg] + [chart_loc_arg]
                         with open(outpath, 'w') as fout, open(errpath, 'w') as ferr:
                             test_code = subprocess.call(
                                 cmd, stderr=ferr, stdout=fout, cwd=outdir)
diff --git a/apps/CameraITS/tools/run_sensor_fusion_box.py b/apps/CameraITS/tools/run_sensor_fusion_box.py
index ec16b3d..41f1180 100644
--- a/apps/CameraITS/tools/run_sensor_fusion_box.py
+++ b/apps/CameraITS/tools/run_sensor_fusion_box.py
@@ -82,10 +82,6 @@
         elif s[:8] == 'tmp_dir=' and len(s) > 8:
             tmp_dir = s[8:]
 
-    if camera_id not in ['0', '1']:
-        print 'Need to specify camera 0 or 1'
-        sys.exit()
-
     # Make output directories to hold the generated files.
     tmpdir = tempfile.mkdtemp(dir=tmp_dir)
     print 'Saving output files to:', tmpdir, '\n'
@@ -94,6 +90,12 @@
     device_id_arg = 'device=' + device_id
     print 'Testing device ' + device_id
 
+    # ensure camera_id is valid
+    avail_camera_ids = find_avail_camera_ids(device_id_arg, tmpdir)
+    if camera_id not in avail_camera_ids:
+        print 'Need to specify valid camera_id in ', avail_camera_ids
+        sys.exit()
+
     camera_id_arg = 'camera=' + camera_id
     if rotator_ids:
         rotator_id_arg = 'rotator=' + rotator_ids
@@ -104,6 +106,7 @@
 
     fps_arg = 'fps=' + fps
     test_length_arg = 'test_length=' + test_length
+    print 'Capturing at %sfps' % fps
 
     os.mkdir(os.path.join(tmpdir, camera_id))
 
@@ -217,6 +220,28 @@
                 return line
     return None
 
+def find_avail_camera_ids(device_id_arg, tmpdir):
+    """Find the available camera IDs.
+
+    Args:
+        devices_id_arg(str):    device=###
+        tmpdir(str):            generated tmp dir for run
+    Returns:
+        list of available cameras
+    """
+    avail_camera_ids = []
+    camera_ids_path = os.path.join(tmpdir, 'camera_ids.txt')
+    out_arg = 'out=' + camera_ids_path
+    cmd = ['python',
+           os.path.join(os.getcwd(), 'tools/get_camera_ids.py'), out_arg,
+           device_id_arg]
+    cam_code = subprocess.call(cmd, cwd=tmpdir)
+    assert cam_code == 0
+    with open(camera_ids_path, "r") as f:
+        for line in f:
+            avail_camera_ids.append(line.replace('\n', ''))
+    return avail_camera_ids
+
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 9fcbdb1..6cc678a 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,6 +25,9 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
+LOCAL_AIDL_INCLUDES := \
+    frameworks/native/aidl/gui
+
 LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
                                compatibility-common-util-devicesidelib \
                                cts-sensors-tests \
@@ -38,11 +41,18 @@
                                mockito-target-minus-junit4 \
                                mockwebserver \
                                compatibility-device-util \
-                               platform-test-annotations
+                               platform-test-annotations \
+                               cts-security-test-support-library
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES += telephony-common
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
+LOCAL_JAVA_LIBRARIES += android.test.mock.stubs
+LOCAL_JAVA_LIBRARIES += bouncycastle
+LOCAL_JAVA_LIBRARIES += voip-common
 
 LOCAL_PACKAGE_NAME := CtsVerifier
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
 		libaudioloopback_jni \
@@ -50,8 +60,6 @@
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
-LOCAL_SDK_VERSION := test_current
-
 LOCAL_DEX_PREOPT := false
 -include cts/error_prone_rules_tests.mk
 include $(BUILD_PACKAGE)
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 10ed4ae..707310d 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -32,6 +32,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.FULLSCREEN" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
@@ -46,7 +47,6 @@
                   android:required="false" />
     <uses-feature android:name="android.hardware.camera.autofocus"
                   android:required="false" />
-    <uses-feature android:name="android.software.vr.mode" android:required="false" />
     <uses-feature android:name="android.hardware.vr.high_performance" android:required="false"/>
     <uses-feature android:name="android.software.companion_device_setup" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -67,6 +67,12 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+
     <!-- Needed by UsbTest tapjacking -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
@@ -82,6 +88,11 @@
             android:largeHeap="true"
             android:theme="@android:style/Theme.DeviceDefault">
 
+        <provider android:name="android.location.cts.MmsPduProvider"
+                android:authorities="emergencycallverifier"
+                android:grantUriPermissions="true" />
+        <uses-library android:name="android.test.runner" />
+
         <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
 
         <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -167,8 +178,13 @@
                        android:value="android.software.companion_device_setup" />
         </activity>
 
-        <!-- A generic activity for intent based tests -->
-        <activity android:name=".IntentDrivenTestActivity"/>
+        <!-- A generic activity for intent based tests.
+        stateNotNeeded is defined ot prevent IntentDrivenTestActivity from being killed when
+        switching users. IntentDrivenTestActivity does not implement onSaveInstanceState() so it is
+        fine to ignore onSaveInstanceState() not being called.
+        -->
+        <activity android:name=".IntentDrivenTestActivity"
+                android:stateNotNeeded="true"/>
 
         <activity android:name=".admin.DeviceAdminKeyguardDisabledFeaturesActivity"
                 android:label="@string/da_kg_disabled_features_test"
@@ -983,6 +999,20 @@
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
             <meta-data android:name="test_required_features" android:value="android.hardware.fingerprint" />
         </activity>
+
+        <activity android:name=".security.FingerprintDialogBoundKeysTest"
+            android:label="@string/sec_fingerprint_dialog_bound_key_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" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.fingerprint" />
+        </activity>
+
         <activity android:name=".security.ScreenLockBoundKeysTest"
                 android:label="@string/sec_lock_bound_key_test"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
@@ -994,6 +1024,7 @@
             <meta-data android:name="test_required_features"
                     android:value="android.software.device_admin" />
         </activity>
+
         <activity android:name=".security.LockConfirmBypassTest"
                 android:label="@string/lock_confirm_test_title"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
@@ -1099,6 +1130,43 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
         </activity>
 
+        <activity android:name=".location.EmergencyCallWifiTestsActivity"
+            android:label="@string/location_emergency_call_wifi_test"
+            android:screenOrientation="locked">
+            <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_hardware"/>
+            <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+        </activity>
+
+        <activity android:name=".location.EmergencyCallMessageTestsActivity"
+            android:label="@string/location_emergency_call_message_test"
+            android:screenOrientation="locked">
+            <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_hardware"/>
+            <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.telephony"/>
+        </activity>
+
+        <activity android:name=".location.EmergencyCallGNSSTestsActivity"
+            android:label="@string/location_emergency_call_gps_test"
+            android:screenOrientation="locked">
+            <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_hardware"/>
+            <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+        </activity>
+
         <activity android:name=".location.GnssMeasurementWhenNoLocationTestsActivity"
             android:label="@string/location_gnss_measure_no_location_test"
             android:screenOrientation="locked">
@@ -1829,6 +1897,14 @@
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
         </activity>
 
+        <receiver android:name=".notifications.BlockChangeReceiver">
+            <intent-filter>
+                <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"/>
+                <action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"/>
+                <action android:name="android.app.action.APP_BLOCK_STATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+
         <activity android:name=".notifications.ConditionProviderVerifierActivity"
                   android:label="@string/cp_test">
             <intent-filter>
@@ -1890,7 +1966,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_vr" />
             <meta-data android:name="test_required_features"
-                       android:value="android.software.vr.mode" />
+                       android:value="android.hardware.vr.high_performance" />
         </activity>
 
         <activity android:name=".vr.MockVrActivity"
@@ -2151,6 +2227,28 @@
                        android:value="android.hardware.sensor.accelerometer" />
         </activity>
 
+        <activity
+            android:name="com.android.cts.verifier.sensors.EventSanitizationTestActivity"
+            android:label="@string/snsr_event_sanitization_test"
+            android:screenOrientation="nosensor" >
+
+            <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_sensors">
+            </meta-data>
+
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.sensor.proximity:android.hardware.sensor.accelerometer">
+            </meta-data>
+
+        </activity>
+
         <receiver android:name=".widget.WidgetCtsProvider">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -2266,6 +2364,15 @@
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
         </activity>
 
+        <activity android:name=".managedprovisioning.ManagedUserPositiveTestActivity"
+                  android:label="@string/managed_user_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_AFFILIATED_PROFILE_OWNER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".managedprovisioning.DeviceOwnerRequestingBugreportTestActivity"
                 android:label="@string/device_owner_requesting_bugreport_tests">
             <intent-filter>
@@ -2278,14 +2385,6 @@
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
         </activity>
 
-        <activity android:name=".managedprovisioning.DeviceOwnerPositiveTestActivity$CommandReceiver"
-                android:exported="false"
-                android:theme="@android:style/Theme.NoDisplay"
-                android:noHistory="true"
-                android:autoRemoveFromRecents="true"
-                android:stateNotNeeded="true">
-        </activity>
-
         <activity android:name=".managedprovisioning.KeyguardDisabledFeaturesActivity"
                 android:label="@string/provisioning_byod_keyguard_disabled_features">
         </activity>
@@ -2294,6 +2393,14 @@
                 android:label="@string/provisioning_byod_disallow_apps_control">
         </activity>
 
+        <activity android:name=".managedprovisioning.LockTaskUiTestActivity"
+                android:label="@string/device_owner_lock_task_ui_test">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".managedprovisioning.WifiLockdownTestActivity"
                 android:label="@string/device_owner_wifi_lockdown_test">
         </activity>
@@ -2321,6 +2428,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".managedprovisioning.KeyChainTestActivity"
+                android:label="@string/provisioning_byod_keychain">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.KEYCHAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".managedprovisioning.PermissionLockdownTestActivity"
                 android:label="@string/device_profile_owner_permission_lockdown_test">
             <intent-filter>
@@ -2649,6 +2764,10 @@
                 <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
             </intent-filter>
         </receiver>
+        <service android:name=".managedprovisioning.DeviceAdminTestReceiver$PrimaryUserService"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_DEVICE_ADMIN">
+        </service>
 
 <!-- Comment out until b/28406044 is addressed
         <activity android:name=".jobscheduler.IdleConstraintTestActivity" android:label="@string/js_idle_test">
diff --git a/apps/CtsVerifier/res/layout-small/positive_managed_user.xml b/apps/CtsVerifier/res/layout-small/positive_managed_user.xml
new file mode 100644
index 0000000..84fa363
--- /dev/null
+++ b/apps/CtsVerifier/res/layout-small/positive_managed_user.xml
@@ -0,0 +1,45 @@
+<?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"
+    style="@style/RootLayoutPadding"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/positive_managed_user_instructions"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/managed_user_positive_tests_instructions"
+                android:textSize="16dip" />
+
+            <ListView
+                android:id="@+id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="800dip" />
+
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml b/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml
new file mode 100644
index 0000000..00dee1a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml
@@ -0,0 +1,53 @@
+<?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"
+              style="@style/RootLayoutPadding"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fillViewport="true">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/device_owner_lock_task_ui_instructions"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/device_owner_lock_task_ui_test_info"
+                android:textSize="18dip" />
+
+            <Button
+                android:id="@+id/start_lock_task_button"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/start_lock_task_button_label" />
+
+            <ListView
+                android:id="@+id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml b/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml
new file mode 100644
index 0000000..fae5167
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        style="@style/RootLayoutPadding">
+
+        <TextView android:id="@+id/info"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textSize="18sp"
+            android:padding="5dp"
+            android:text="@string/emergency_call_confirm_info"/>
+        <EditText
+            android:id="@+id/emergency_number"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:hint="@string/emergency_call_emergency_number_hint_text"
+            android:inputType="phone"
+            android:text="@string/emergency_call_emergency_number_text"/>
+        <Button
+            android:id="@+id/dial_button"
+            android:layout_width="200px"
+            android:layout_height="wrap_content"
+            android:text="@string/emergency_call_dial_text"
+            android:paddingLeft="5dp"
+            android:paddingRight="5dp"
+            android:layout_marginTop="5dp"
+            android:layout_marginRight="5dp"/>
+    </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml b/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml
new file mode 100644
index 0000000..071e2bf
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        style="@style/RootLayoutPadding">
+
+        <TextView android:id="@+id/info"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textSize="18sp"
+            android:padding="5dp"
+            android:text="@string/emergency_call_confirm_info"/>
+        <EditText
+            android:id="@+id/emergency_number"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:hint="@string/emergency_call_emergency_number_hint_text"
+            android:inputType="phone"
+            android:text="@string/emergency_call_emergency_number_text"/>
+        <EditText
+            android:id="@+id/local_phone_number"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:hint="@string/emergency_call_current_number_hint_text"
+            android:inputType="phone"/>
+        <Button
+            android:id="@+id/dial_button"
+            android:layout_width="200px"
+            android:layout_height="wrap_content"
+            android:text="@string/emergency_call_dial_text"
+            android:paddingLeft="5dp"
+            android:paddingRight="5dp"
+            android:layout_marginTop="5dp"
+            android:layout_marginRight="5dp"/>
+    </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/keychain_test.xml b/apps/CtsVerifier/res/layout/keychain_test.xml
new file mode 100644
index 0000000..09708c7
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/keychain_test.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="320dip"
+            android:layout_weight="2">
+        <TextView
+                android:id="@+id/provisioning_byod_keychain_instructions"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="10dip"
+                android:text="@string/provisioning_byod_keychain_info_start"
+                android:textSize="18dip" />
+    </ScrollView>
+
+    <TextView
+          android:id="@+id/provisioning_byod_keychain_test_log"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:padding="10dip"
+          android:textSize="18dip" />
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/prepare_test_button"
+            android:layout_width="204dip"
+            android:layout_height="wrap_content"
+            android:text="@string/provisioning_byod_keyguard_disabled_features_prepare_button"/>
+
+        <Button
+            android:id="@+id/run_test_button"
+            android:layout_width="204dip"
+            android:layout_height="wrap_content"
+            android:text="@string/go_button_text"/>
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/positive_managed_user.xml b/apps/CtsVerifier/res/layout/positive_managed_user.xml
new file mode 100644
index 0000000..4afdf63
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/positive_managed_user.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/RootLayoutPadding"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fillViewport="true">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/positive_managed_user_instructions"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/managed_user_positive_tests_instructions"
+                android:textSize="18dip" />
+
+            <ListView
+                android:id="@+id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 72a1c5a..660ff84 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -168,6 +168,7 @@
     <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>
 
     <!-- Strings for BluetoothActivity -->
     <string name="bluetooth_test">Bluetooth Test</string>
@@ -566,10 +567,32 @@
         (for example, stationary on a windowsill.  If needed, try again, outside, also with the
         device stationary, and with at least some view of the sky.) and then press Next to run
         the automated tests.</string>
+    <string name="location_emergency_call_test_info">This test verifies whether basic features
+        (wifi, sms, gps) works correctly during the emergency call. Make sure the device is using
+        a special white listed sim card. The wifi and GPS should be on and have internet connection.
+    </string>
+    <string name="emergency_call_confirm_info">This test will dial 911! Please make sure to use a
+        whitelisted sim card to run this test!
+    </string>
+    <string name="emergency_call_emergency_number_hint_text">
+        Emergency Number:
+    </string>
+    <string name="emergency_call_current_number_hint_text">
+        Current Number:
+    </string>
+    <string name="emergency_call_dial_text">
+        Dial 911
+    </string>
+    <string name="emergency_call_emergency_number_text">
+        911
+    </string>
     <string name="location_gnss_test_retry_info">If this test fails, please make sure the device
         has line of sight to GNSS satellites (for example, stationary on a windowsill. If needed,
         try again, outside, also with the device stationary, with as much view of the sky as
         possible.) </string>
+    <string name="location_emergency_call_gps_test">Emergency Call GPS Test</string>
+    <string name="location_emergency_call_message_test">Emergency Call Message Test</string>
+    <string name="location_emergency_call_wifi_test">Emergency Call Wifi Test</string>
 
     <!-- Strings for net.ConnectivityScreenOffTestActivity -->
     <string name="network_screen_off_test">Network Connectivity Screen Off Test</string>
@@ -926,6 +949,17 @@
     You will need to walk to ensure that Significant Motion triggers. The screen will turn on and a sound will be played once the test completes.</string>
     <string name="snsr_device_did_not_wake_up_at_trigger">Device did not wakeup at trigger time. wakeTime=%1$d ms triggerTime=%2$d ms</string>
 
+    <!-- Event sanitization for idle UIDs -->
+    <string name="snsr_event_sanitization_test">Event sanitization for idle UID test</string>
+    <string name="snsr_event_sanitization_test_setup">Run the \'adb shell cmd sensorservice set-uid-state com.android.cts.verifier idle\' shell command
+        to emulate the CtsVerifier UID being idle.</string>
+    <string name="snsr_event_sanitization_test_cleanup">Run the \'adb shell cmd sensorservice reset-uid-state com.android.cts.verifier\' shell command
+        to stop emulating the CtsVerifier UID being idle. Failing to do that would lead to other tests failing!</string>
+    <string name="snsr_significant_motion_test_uid_idle">Move around with the device to try triggering significant motion</string>
+    <string name="snsr_significant_motion_test_uid_idle_expectation">No trigger events should be generated while idle</string>
+    <string name="snsr_proximity_test_uid_idle">Touch the proximity sensor to try triggering it</string>
+    <string name="snsr_proximity_test_uid_idle_expectation">No on-change events should be generated while idle</string>
+
     <!-- Low Latency Off-Body Detect -->
     <string name="snsr_offbody_sensor_test">Off Body Sensor Tests</string>
     <string name="snsr_offbody_sensor_registration">Registration failed for low latency offbody detect sensor.\n</string>
@@ -965,7 +999,7 @@
     <string name="usb_tapjacking_test_instructions">
         1. Connect device via usb to computer.\n
         2. Click \"Show overlay\" button.  Settings may appear if the CTS Verifier app doesn\'t have display over apps permission.  Enable this permission and then click back to navigate back to the app.\n
-        3. Trigger USB debugging dialog (from computer terminal): \"adb shell am start -e fingerprints placeholder -e key placeholder com.android.systemui/.usb.UsbDebuggingActivity\"\n
+        3. Trigger USB debugging dialog (from computer terminal): \"adb shell am start -e fingerprints placeholder -e key placeholder com.android.systemui/.UsbDebuggingActivityAlias\"\n
         4. USB debugging dialog should appear with the overlay on top saying \"This message covers the USB debugging RSA prompt\" to appear.\n
         5. Try clicking OK. \n
         Test pass if you cannot click OK when the text quoted above is on top of the USB debugging dialog.  Toast should appear saying there is an overlay so Settings cannot verify your response. \n
@@ -1520,17 +1554,15 @@
     <string name="test_mute_dnd_affected_streams">Test mute streams</string>
     <string name="test_ringer_manager">Test RingtoneManager</string>
     <string name="enable_sound_effects">Please enable sound effects in Sound settings.</string>
-
     <string name="attention_ready">I\'m done</string>
     <string name="attention_filter_any">Please enable \"Do not disturb\" by tapping the Quick Settings tile.</string>
     <string name="attention_filter_all">Please disable \"Do not disturb\" by tapping the Quick Settings tile.</string>
-    <string name="attention_filter_priority">Please select \"Priority only\" in the dialog that appears
-        when you tap the \"Do not disturb\" tile in Quick Settings, and customize the setting to allow messages from
-        starred contacts only by tapping "More settings".</string>
-    <string name="attention_filter_alarms">Please select \"Alarms only\" in the dialog that appears
-        when you tap the \"Do not disturb\" tile in Quick Settings.</string>
-    <string name="attention_filter_none">Please select \"Total silence\" in the dialog that appears
-        when you tap the \"Do not disturb\" tile in Quick Settings.</string>
+    <string name="attention_filter_priority">Please enable \"Do not disturb\" by tapping the Quick
+        Settings tile.  Then, long press the same tile and customize the setting to allow messages
+        from starred contacts only.</string>
+    <string name="attention_filter_priority_mimic_alarms_only">Please enable Priority-Only \"Do not disturb\"
+        by tapping the Quick Settings tile.  Then, long press the same tile and customize the setting to allow sounds
+        from Alarms and Media (if applicable) only.</string>
     <string name="attention_create_contacts">Create contacts for notification annotations.</string>
     <string name="attention_delete_contacts">Delete test contacts.</string>
     <string name="attention_default_order">Check that ranker defaults to time order.</string>
@@ -1541,9 +1573,13 @@
     <string name="attention_phone_order">Check that ranker respects telephone URIs for contacts.</string>
     <string name="attention_interruption_order">Check that ranker temporarily boosts interruptions.
     This test takes 30 seconds to complete.</string>
-    <string name="attention_none_are_filtered">Check that \"All\" mode doesn\'t filter any notifications.</string>
-    <string name="attention_some_are_filtered">Check that \"Priority\" mode doesn\'t filter priority notifications.</string>
+    <string name="attention_none_are_filtered_messages">Check that \"All\" mode doesn\'t filter any notifications (messages).</string>
+    <string name="attention_none_are_filtered_diff_categories">Check that \"All\" mode doesn\'t filter any notifications (event, reminder, alarm).</string>
+    <string name="attention_some_are_filtered_messages">Check that \"Priority\" mode doesn\'t filter priority notifications (messages from starred contacts).</string>
+    <string name="attention_some_are_filtered_alarms">Check that \"Priority\" mode doesn\'t filter priority notifications (alarms).</string>
+    <string name="attention_some_are_filtered_media_system_other">Check that \"Priority\" mode doesn\'t filter priority notifications (media, system, other).</string>
     <string name="attention_all_are_filtered">Check that \"None\" mode filters all notifications.</string>
+    <string name="attention_cannot_disallow_alarms_or_media">Check that apps targeted with Pre-P SDK can\'t disallow alarms or media from bypassing DND.</string>
     <string name="nls_test">Notification Listener Test</string>
     <string name="nas_test">Notification Assistant Test</string>
     <string name="nls_service_name">Notification Listener for CTS Verifier</string>
@@ -1580,6 +1616,10 @@
         under Apps > Gear Icon > Default > Notification Assistant and return here.</string>
     <string name="nls_enable_service">Please enable \"Notification Listener for CTS Verifier\"
         under Apps > Gear Icon > Special Access > Notification Access and return here.</string>
+    <string name="nls_block_app">Please block the linked application and return here.</string>
+    <string name="nls_unblock_app">Please unblock the linked application and return here.</string>
+    <string name="nls_block_channel">Please block the linked notification channel and return here.</string>
+    <string name="nls_block_group">Please block the linked notification channel group and return here.</string>
     <string name="nls_cannot_enable_service">Please make sure you cannot enable
         \"Notification Listener for CTS Verifier\" and return here.</string>
     <string name="nls_disable_service">Please disable \"Notification Listener for CTS Verifier\"
@@ -1598,6 +1638,7 @@
     <string name="nas_snooze_context">Check that the Assistant can snooze a notification until a given context.</string>
     <string name="nls_clear_one">Check that service can clear a notification.</string>
     <string name="nls_clear_one_reason">Check that service can clear a notification and receive the correct reason for dismissal.</string>
+    <string name="nls_clear_one_stats">Check that service does not receive notification stats.</string>
     <string name="nls_clear_all">Check that service can clear all notifications.</string>
     <string name="nls_service_stopped">Service should stop once disabled.</string>
     <string name="nls_note_missed">Check that notification was not received.</string>
@@ -1896,7 +1937,8 @@
         This test verifies that if a work profile is locked with a separate password, Recents views
         for applications in the work profile are redacted.\n
         Some devices may not lock as soon as the screen is turned off by default. On such devices,
-        use the button below when requested to lock the work profile.
+        use the button below when requested to lock the work profile. Please skip these tests if
+        "Recents" is absent.
     </string>
     <string name="provisioning_byod_recents_lock_now">Lock now</string>
 
@@ -1925,6 +1967,35 @@
         The work profile still has a separate password. Please remove this before continuing.
     </string>
 
+    <string name="provisioning_byod_keychain">KeyChain test</string>
+    <string name="provisioning_byod_keychain_info_start">
+        In this test, you\'ll verify that keys generated by KeyChain keys are as usable as keys
+        installed into KeyChain and that they can be hidden from users.\n
+        The test has two parts:\n
+        1) Testing that a generated key can be selectable by the user.\n
+        2) Testing that a generated key can be hidden from users.\n
+        \n
+        Tap \"Prepare Test\" button below to begin.\n
+        \n
+        NOTE: A screen lock must be configured for this test. Otherwise, test preparation
+        will fail to generate a key for use by the test.
+    </string>
+    <string name="provisioning_byod_keychain_info_first_test">
+        Once you press \'Go\', a prompt titled \"Choose certificate\" should appear.\n
+        Verify that the list in this dialog has one item, starting with \'cts-verifier-gen\'.
+        Press \'Select\' to select it.\n
+        If the test passes, you\'ll see the text \"Second test ready\" at the bottom.\n
+        \n
+        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
+        Mark the test as passed if the text at the bottom shows \"PASSED (2/2)\"\n
+    </string>
+
     <!-- Strings for DeskClock -->
     <string name="deskclock_tests">Alarms and Timers Tests</string>
     <string name="deskclock_tests_info">
@@ -2308,7 +2379,13 @@
         - Both Personal and Work categories exist.\n
         - \"Remove work profile\" or \"Uninstall\" exists under the Work category.\n
         \n
-        Furthermore, verify that:\n
+        Use the Back button (or navigate back to this app using Recents) to return to this page.
+    </string>
+    <string name="provisioning_byod_user_settings">Profile-aware user settings</string>
+    <string name="provisioning_byod_user_settings_instruction">
+        Please press the Go button to open the Settings page.
+        (If this device has a separate app for work settings, ignore the Go button and open that app manually from the launcher.)\n
+        Navigate to Users (or \"Multiple Users\") and confirm that:\n
         - There are two auto-sync options present, one for personal and one for work data (either on the screen or in the overflow menu).\n
         - De-selecting either option prompts a warning dialog.\n
         \n
@@ -2412,9 +2489,9 @@
     <string name="provisioning_byod_nfc_beam_allowed_instruction">
         Please press the Go button to test if Nfc beam can be triggered in the work profile.\n
         \n
-        For the first test, press \"Send manual beam\" to trigger a beam, then bump into another device to send the file. Verify that the file is successfully received.\n
+        For the first test, press \"Send manual beam\" to trigger a beam, then bump into another device to send the tag. Verify that the tag is successfully received.\n
         \n
-        For the second test, press \"Send share intent\" to trigger a beam, then bump into another device to send the file. Verify that the file is successfully received.\n
+        For the second test, press \"Send share intent\" to trigger a beam, then bump into another device to send the tag. Verify that the tag is successfully received.\n
         \n
         Then use the Back button to return to this test and mark accordingly.
     </string>
@@ -2475,57 +2552,52 @@
         3. Go back to the cts-verifier tests using the back button, then mark the test accordingly.\n
     </string>
 
-    <string name="provisioning_byod_turn_off_work">Turn off work mode</string>
-    <string name="provisioning_byod_turn_off_work_info">This test verifies device behaviors when turning off work mode.</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">
         This test verifies the device behavior when work profile is turned off.\n
         Please exercise the following tests in sequence.\n
-        The button below can be used to open the Settings page where you can toggle work mode.\n
+        The button below can be used to open the Settings page where you can toggle work profile.\n
         (If this device has a separate app for work settings, ignore the button and open that app manually from the launcher).\n
     </string>
-    <string name="provisioning_byod_turn_off_work_prepare_button">Open Settings to toggle work mode</string>
+    <string name="provisioning_byod_turn_off_work_prepare_button">Open Settings to toggle work profile</string>
 
     <string name="provisioning_byod_turn_off_work_prepare_notifications">Prepare a work notification</string>
     <string name="provisioning_byod_turn_off_work_prepare_notifications_instruction">
         This is a test setup step.\n
         1. Press the go button to send a work notification.\n
         2. Verify that the notification is displayed and mark this test as passed.\n
-        (Note: in the following test, you will be asked to verify the notification disappears after work mode is turned off.)
+        (Note: in the following test, you will be asked to verify the notification disappears after work profile is turned off.)
     </string>
 
-    <string name="provisioning_byod_turn_off_work_turned_off">Please turn off work mode</string>
-    <string name="provisioning_byod_turn_off_work_turned_off_toast">Open settings to turn off work mode, using the button above.</string>
+    <string name="provisioning_byod_turn_off_work_turned_off">Please turn off work profile</string>
+    <string name="provisioning_byod_turn_off_work_turned_off_toast">Open settings to turn off work profile, using the button above.</string>
 
-    <string name="provisioning_byod_turn_off_work_notifications">Notifications when work mode is off</string>
+    <string name="provisioning_byod_turn_off_work_notifications">Notifications when work profile is off</string>
     <string name="provisioning_byod_turn_off_work_notifications_instruction">
         Verify that the previously-shown work notification has now disappeared.
     </string>
 
-    <string name="provisioning_byod_turn_off_work_icon">Status bar icon when work mode is off</string>
-    <string name="provisioning_byod_turn_off_work_icon_instruction">
-        Now that work mode is off, please verify that the status bar shows an icon indicating that work mode is off.\n
-    </string>
-
-    <string name="provisioning_byod_turn_off_work_launcher">Starting work apps when work mode is off</string>
+    <string name="provisioning_byod_turn_off_work_launcher">Starting work apps when work profile is off</string>
     <string name="provisioning_byod_turn_off_work_launcher_instruction">
-        This test verifies that work applications cannot be started if work mode is off.\n
+        This test verifies that work applications cannot be started if work profile is off.\n
         1. Press home to go to the launcher.\n
         2. Verify that work applications are greyed out.\n
         3. Tap on a work application.\n
         4. Verify that the application does not start.\n
     </string>
 
-    <string name="provisioning_byod_turn_off_work_turned_on">Please turn work mode back on</string>
-    <string name="provisioning_byod_turn_off_work_turned_on_toast">Open settings to turn work mode back on, either manually or using the button above.</string>
+    <string name="provisioning_byod_turn_off_work_turned_on">Please turn work profile back on</string>
+    <string name="provisioning_byod_turn_off_work_turned_on_toast">Open settings to turn work profile back on, either manually or using the button above.</string>
 
-    <string name="provisioning_byod_turn_on_work_icon">Status bar icon when work mode is on</string>
+    <string name="provisioning_byod_turn_on_work_icon">Status bar icon when work profile is on</string>
     <string name="provisioning_byod_turn_on_work_icon_instruction">
-        Now that work mode is back on, please verify that the status bar icon for work mode off is no longer visible.
+        Now that work profile is back on, please verify that the status bar icon for work profile off is no longer visible.
     </string>
 
-    <string name="provisioning_byod_turn_on_work_launcher">Starting work apps when work mode is on</string>
+    <string name="provisioning_byod_turn_on_work_launcher">Starting work apps when work profile is on</string>
     <string name="provisioning_byod_turn_on_work_launcher_instruction">
-        Now that work mode is back on, please go to the launcher and verify that you can start a work application.
+        Now that work profile is back on, please go to the launcher and verify that you can start a work application.
     </string>
 
     <string name="provisioning_byod_organization_info">Organization Info</string>
@@ -2685,6 +2757,142 @@
     </string>
     <string name="device_owner_disable_keyguard_button">Disable keyguard</string>
     <string name="device_owner_reenable_keyguard_button">Reenable keyguard</string>
+    <string name="device_owner_lock_task_ui_test">LockTask UI</string>
+    <string name="device_owner_lock_task_ui_test_info">
+            The following tests verify the configurable UI during LockTask, a special mode that
+            prevents the user from leaving the current application.\n\n
+            Please make sure the lock screen is turned on before the test. Press the button below to
+            start LockTask mode. Then mark each item as \'pass\' or \'fail\' according to the
+            instructions.\n\n
+            Finally, execute the last test item to leave LockTask mode.
+    </string>
+    <string name="start_lock_task_button_label">Start LockTask mode</string>
+    <string name="device_owner_lock_task_ui_default_test">Default LockTask UI</string>
+    <string name="device_owner_lock_task_ui_default_test_info">
+            Observe the following UI restrictions. Mark the test as \'pass\' only if ALL of the
+            requirements below are met.\n\n
+            1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+            battery status, clock, etc.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar.\n
+            3) The Home button is hidden.\n
+            4) The Recents button is hidden.\n
+            5) Long-press the power button. The power button menu, which usually shows the power-off
+            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.
+    </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">
+            Press the button below to enable system info. Observe the system info area of the status
+            bar is now enabled. This includes the clock, connectivity info, battery info, etc.\n\n
+            The rest of the UI restrictions should still apply:\n
+            1) Notification icons are still hidden on the status bar.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar.\n
+            3) The Home button is hidden.\n
+            4) The Recents button is hidden.\n
+            5) Long-press the power button. The power button menu, which usually shows the power-off
+            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
+            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>
+    <string name="device_owner_lock_task_ui_notifications_test_info">
+            Press the button below to enable notifications. Observe the notification icons on the
+            status bar are now enabled. The status bar can also be expanded to show the
+            notifications. However, all Settings UI should remain invisible, including Quick
+            Settings and any link to the Settings app.\n\n
+            The rest of the UI restrictions should still apply:\n
+            1) System info area is still hidden on the status bar.\n
+            2) The Home button is hidden.\n
+            3) The Recents button is hidden.\n
+            4) Long-press the power button. The power button menu, which usually shows the power-off
+            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
+            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>
+    <string name="device_owner_lock_task_ui_home_test_info">
+            Press the button below to enable the Home button. Observe the Home button is now
+            enabled.\n\n
+            The rest of the UI restrictions should still apply:\n
+            1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+            battery status, clock, etc.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar.\n
+            3) The Recents button is hidden.\n
+            4) Long-press the power button. The power button menu, which usually shows the power-off
+            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
+            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 Recents button</string>
+    <string name="device_owner_lock_task_ui_recents_test_info">
+            Press the button below to enable the Recents button. Observe the Recents button is now
+            enabled. Press the Recents button and verify the Recents view can be opened.\n\n
+            The rest of the UI restrictions should still apply:\n
+            1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+            battery status, clock, etc.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar.\n
+            3) The Home button is hidden.\n
+            4) Long-press the power button. The power button menu, which usually shows the power-off
+            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
+            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>
+    <string name="device_owner_lock_task_ui_global_actions_test_info">
+            Press the button below to enable global actions (a.k.a. power button menu). Long-press
+            the power button and verify a menu containing power-off and restart buttons is shown.
+            This menu can\'t contain any UI that allows the user to change system settings (such as
+            airplane mode switch) or access the Settings app.\n\n
+            The rest of the UI restrictions should still apply:\n
+            1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+            battery status, clock, etc.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar.\n
+            3) The Home button is hidden.\n
+            4) The Recents button is hidden.\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
+            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>
+    <string name="device_owner_lock_task_ui_keyguard_test_info">
+            Press the button below to enable keyguard. Press the power button to turn off the screen
+            and press it again to turn the screen back on. Verify that the lock screen is shown.\n\n
+            The rest of the UI restrictions should still apply, both on the lock screen and after
+            the lock screen is dismissed:\n
+            1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+            battery status, clock, etc.\n
+            2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+            for the status bar, even on the lock screen.\n
+            3) The Home button is hidden.\n
+            4) The Recents button is hidden.\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
+            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>
+    <string name="device_owner_lock_task_ui_stop_lock_task_test_info">
+            Press the button below to exit LockTask mode.\n\n
+            Observe that the UI has returned to the normal, unrestricted state, and is no longer
+            subject to any LockTask restriction.\n\n
+            Mark the test as \'pass\' or \'fail\' accordingly.
+    </string>
     <string name="device_owner_lockscreen_secure">Please remove lockscreen password</string>
     <string name="device_profile_owner_permission_lockdown_test">Permissions lockdown</string>
     <string name="device_profile_owner_permission_lockdown_test_instructions">
@@ -2958,6 +3166,18 @@
     <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_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, changing location accuracy</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_ambient_display">Disallow ambient display</string>
+    <string name="disallow_ambient_display_action">Configuring ambient display in Display or Battery page</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>
@@ -2991,7 +3211,9 @@
     <string name="disallow_share_location">Disallow share location</string>
     <string name="disallow_share_location_action">Turning on location sharing</string>
     <string name="disallow_uninstall_apps">Disallow uninstall apps</string>
-    <string name="disallow_uninstall_apps_action">Uninstalling applications other CtsVerifier</string>
+    <string name="disallow_uninstall_apps_action">Uninstalling applications from the work profile (badged applications) other than CtsVerifier</string>
+    <string name="disallow_unified_challenge">Disallow unified challenge</string>
+    <string name="disallow_unified_challenge_action">Setting one lock for both personal and work profiles. IMPORTANT: Separate work lock should be set prior to this test in Set work lock test</string>
     <string name="disallow_keyguard_unredacted_notifications">Disallow lockscreen unredacted notification</string>
     <string name="disallow_keyguard_unredacted_notifications_set_step">Disallow unredacted notifications when device is locked by turning on the switch below</string>
     <string name="disallow_keyguard_unredacted_notifications_action">Selecting show all notification content when device is locked</string>
@@ -3037,7 +3259,7 @@
         Check that \'Dummy Input method\', along with all other non-system apps, are not enabled in Settings. Then disallow \'Dummy Input method\' from permitted input methods by turning on the switch below.
     </string>
     <string name="set_permitted_input_methods_action">
-        Enabling \'Dummy Input method\' in the list of accessibility services
+        Enabling \'Dummy Input method\' in the list of input methods
     </string>
     <string name="set_permitted_input_methods_widget_label">
         Allow only system input methods:
@@ -3349,6 +3571,69 @@
     <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>
 
+    <string name="managed_user_test">Managed User</string>
+    <string name="managed_user_positive_tests">Managed User positive tests</string>
+    <string name="managed_user_positive_tests_instructions">
+        The positive managed user tests verify policies on a managed user created by a device owner.
+        \n
+        Press Go button to create a managed user, and you will be switched to the managed user
+        automatically. Dismiss the keyguard and a \'Managed User Tests\' should launch.\n
+        Follow the test instructions and press \'pass\' or \'fail\' to return to this screen.\n
+    </string>
+    <string name="managed_user_positive_tests_info">
+        The positive managed user tests verify policies on a managed user created by a device owner.
+        Proceed to the test cases, then press \'pass\' or \'fail\' to finish this test.
+    </string>
+    <string name="managed_user_positive_category">Managed User Tests</string>
+    <string name="managed_user_check_managed_user_test">Check affiliated profile owner</string>
+    <string name="managed_user_incorrect_managed_user">Missing or incorrect affiliated profile owner: CTSVerifier is not affilaited PO!</string>
+
+    <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.
+        Confirm that:\n
+        \n
+        - Selecting uninitialized user should not trigger user switch.\n
+        \n
+        In additional, if quick settings is available, confirm that user switcher is hidden or
+        disabled.
+        Use the Back button to return to this page.
+    </string>
+    <string name="device_owner_disallow_user_switch_create_user">Create uninitialized user</string>
+
+    <string name="device_owner_user_switcher_message">User switcher message</string>
+    <string name="device_owner_user_switcher_message_info">
+        1. Please press the \'With user switcher message\' button to set the user switcher message.
+        You will then be automatically switched to a secondary user. If a user switcher dialog shows
+        up, it should read \'Start user session\'. Wait until you are automatically switched back to
+        primary, if a user switcher dialog shows up, it should read \'End user session\'.
+
+        \n
+        2. Please press the \'Without user switcher message\' button to clear the user switcher
+        message. You will then be automatically switched to a secondary user. If a user switcher
+        dialog shows up, it should read \'Switching to managed user\'. Wait until you are
+        automatically switched back to primary, if a user switcher dialog shows up, it should read
+        \'Switching to (name of primary user)\'.
+    </string>
+    <string name="device_owner_with_user_switcher_message">With user switcher message</string>
+    <string name="device_owner_without_user_switcher_message">Without user switcher message</string>
+
+    <string name="device_owner_enable_logout">Logout</string>
+    <string name="device_owner_enable_logout_info">
+        Please press the Go button to enable logout. You will then be switched to a newly created
+        user.
+        Look for a way to logout the current user without unlocking the lock screen. The control is
+        usually named \'End session\'.\n
+        The location may vary depending on manufacturer, typical locations are:\n
+        - In power button menu by long pressing power button.\n
+        - On the lock screen.\n
+        \n
+        When successfully logout and switched back to primary user, confirm that the logout control
+        is not available in primary user.
+    </string>
+
     <!-- Strings for JobScheduler Tests -->
     <string name="js_test_description">This test is mostly automated, but requires some user interaction. You can pass this test once the list items below are checked.</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 8c779c5..8893e0d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -103,7 +103,7 @@
         DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(Build.CPU_ABI,
                 Build.CPU_ABI2, abis, abis32, abis64, Build.BOARD, Build.BRAND, Build.DEVICE,
                 Build.FINGERPRINT, Build.ID, Build.MANUFACTURER, Build.MODEL, Build.PRODUCT,
-                referenceFingerprint, Build.SERIAL, Build.TAGS, Build.TYPE, versionBaseOs,
+                referenceFingerprint, Build.getSerial(), Build.TAGS, Build.TYPE, versionBaseOs,
                 Build.VERSION.RELEASE, Integer.toString(Build.VERSION.SDK_INT),
                 versionSecurityPatch, Build.VERSION.INCREMENTAL);
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
index 2886478..e5b73e8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
@@ -113,13 +113,13 @@
             return tests;
         }
         tests.add(new SetModeAllTest());
-        tests.add(new SetModeAlarmsTest());
+        tests.add(new SetModePriorityTest());
         tests.add(new TestAccessRingerModeDndOn());
         tests.add(new TestVibrateNotificationDndOn());
         tests.add(new TestVibrateRingerDndOn());
         tests.add(new TestSetRingerModePolicyAccessDndOn());
         tests.add(new TestVolumeDndAffectedStreamDndOn());
-        tests.add(new TestAdjustVolumeInAlarmsOnlyMode());
+        tests.add(new TestAdjustVolumeInPriorityOnlyAllowAlarmsMediaMode());
 
         tests.add(new SetModeAllTest());
         tests.add(new TestAccessRingerMode());
@@ -136,7 +136,8 @@
 
     private boolean supportsConditionProviders() {
         ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        return !am.isLowRamDevice();
+        return !am.isLowRamDevice()
+                || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
 
     private int getVolumeDelta(int volume) {
@@ -148,34 +149,41 @@
     }
 
     private void testStreamMuting(int stream) {
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-        assertTrue("Muting stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+        if (stream == AudioManager.STREAM_VOICE_CALL) {
+            // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertFalse("Muting stream " + stream + " should require MODIFY_PHONE_STATE permission.",
+                    mAudioManager.isStreamMute(stream));
+        } else {
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertTrue("Muting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
-        assertFalse("Unmuting stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+            assertFalse("Unmuting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-        assertTrue("Toggling mute on stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertTrue("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-        assertFalse("Toggling mute on stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertFalse("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.setStreamMute(stream, true);
-        assertTrue("Muting stream " + stream + " using setStreamMute failed",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.setStreamMute(stream, true);
+            assertTrue("Muting stream " + stream + " using setStreamMute failed",
+                    mAudioManager.isStreamMute(stream));
 
-        // mute it three more times to verify the ref counting is gone.
-        mAudioManager.setStreamMute(stream, true);
-        mAudioManager.setStreamMute(stream, true);
-        mAudioManager.setStreamMute(stream, true);
+            // mute it three more times to verify the ref counting is gone.
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
 
-        mAudioManager.setStreamMute(stream, false);
-        assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.setStreamMute(stream, false);
+            assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
+                    mAudioManager.isStreamMute(stream));
+        }
     }
 
     // Tests
@@ -215,10 +223,10 @@
         }
     }
 
-    protected class SetModeAlarmsTest extends InteractiveTestCase {
+    protected class SetModePriorityTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
-            return createRetryItem(parent, R.string.attention_filter_alarms);
+            return createRetryItem(parent, R.string.attention_filter_priority_mimic_alarms_only);
         }
 
         @Override
@@ -1076,29 +1084,35 @@
 
             int muteAffectedStreams = Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.MUTE_STREAMS_AFFECTED,
-                    // Same defaults as in AudioService. Should be kept in
-                    // sync.
-                    ((1 << AudioManager.STREAM_MUSIC) |
+                    // same defaults as in AudioService. Should be kept in sync.
+                    (1 << STREAM_MUSIC) |
                             (1 << AudioManager.STREAM_RING) |
                             (1 << AudioManager.STREAM_NOTIFICATION) |
-                            (1 << AudioManager.STREAM_SYSTEM)));
+                            (1 << AudioManager.STREAM_SYSTEM) |
+                            (1 << AudioManager.STREAM_VOICE_CALL));
+
             for (int stream : streams) {
                 // ensure each stream is on and turned up.
-                mAudioManager.setStreamVolume(stream,
-                        mAudioManager.getStreamMaxVolume(stream),
-                        0);
+                mAudioManager.setStreamVolume(stream, mAudioManager.getStreamMaxVolume(stream), 0);
                 if (((1 << stream) & muteAffectedStreams) == 0) {
-                    mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-                    assertFalse("Stream " + stream + " should not be affected by mute.",
-                            mAudioManager.isStreamMute(stream));
-                    mAudioManager.setStreamMute(stream, true);
-                    assertFalse("Stream " + stream + " should not be affected by mute.",
-                            mAudioManager.isStreamMute(stream));
-                    mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
-                            0);
-                    assertFalse("Stream " + stream + " should not be affected by mute.",
-                            mAudioManager.isStreamMute(stream));
-                    continue;
+                    if (stream == AudioManager.STREAM_VOICE_CALL) {
+                        // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
+                        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+                        assertTrue("Voice call stream (" + stream + ") should require MODIFY_PHONE_STATE "
+                                + "to mute.", mAudioManager.isStreamMute(stream));
+                    } else {
+                        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+                        assertFalse("Stream " + stream + " should not be affected by mute.",
+                                mAudioManager.isStreamMute(stream));
+                        mAudioManager.setStreamMute(stream, true);
+                        assertFalse("Stream " + stream + " should not be affected by mute.",
+                                mAudioManager.isStreamMute(stream));
+                        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
+                                0);
+                        assertFalse("Stream " + stream + " should not be affected by mute.",
+                                mAudioManager.isStreamMute(stream));
+                        continue;
+                    }
                 }
                 testStreamMuting(stream);
             }
@@ -1106,7 +1120,7 @@
         }
     }
 
-    protected class TestAdjustVolumeInAlarmsOnlyMode extends InteractiveTestCase {
+    protected class TestAdjustVolumeInPriorityOnlyAllowAlarmsMediaMode extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
             return createAutoItem(parent, R.string.test_volume_dnd_affected_stream);
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 0c69790..7fdf403 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -32,6 +32,7 @@
 
 public abstract class USBAudioPeripheralActivity extends PassFailButtons.Activity {
     private static final String TAG = "USBAudioPeripheralActivity";
+    private static final boolean DEBUG = false;
 
     // Profile
     protected ProfileManager mProfileManager = new ProfileManager();
@@ -77,6 +78,9 @@
     }
 
     private void showProfileStatus() {
+        if (DEBUG) {
+            Log.d(TAG, "showProfileStatus()" + (mSelectedProfile != null));
+        }
         if (mSelectedProfile != null) {
             mProfileNameTx.setText(mSelectedProfile.getName());
             mProfileDescriptionTx.setText(mSelectedProfile.getDescription());
@@ -88,11 +92,19 @@
 
     private void showPeripheralStatus() {
         if (mIsPeripheralAttached) {
+            String productName = "";
             if (mOutputDevInfo != null) {
-                mPeripheralNameTx.setText(mOutputDevInfo.getProductName().toString());
+                productName = mOutputDevInfo.getProductName().toString();
             } else if (mInputDevInfo != null) {
-                mPeripheralNameTx.setText(mInputDevInfo.getProductName().toString());
+                productName = mInputDevInfo.getProductName().toString();
             }
+            String ctrlText;
+            if (mSelectedProfile == null) {
+                ctrlText = productName + " - UNSUPPORTED";
+            } else {
+                ctrlText = productName;
+            }
+            mPeripheralNameTx.setText(ctrlText);
         } else {
             mPeripheralNameTx.setText("Disconnected");
         }
@@ -117,7 +129,9 @@
             }
         }
         mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
-        // Log.i(TAG, "mIsPeripheralAttached: " + mIsPeripheralAttached);
+        if (DEBUG) {
+            Log.d(TAG, "mIsPeripheralAttached: " + mIsPeripheralAttached);
+        }
 
         // any associated profiles?
         if (mIsPeripheralAttached) {
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 83e68e4..07a99da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
@@ -171,6 +171,7 @@
             mTestStatusTx.setText("No Peripheral or No Matching Profile.");
         }
 
+        // Headset not publicly available, violates CTS Verifier additional equipment guidelines.
         getPassButton().setEnabled(outPass && inPass);
     }
 }
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 98a7d18..87b2149 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,7 @@
 
 package com.android.cts.verifier.audio;
 
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.util.Log;
@@ -64,39 +65,15 @@
     }
 
     private void showButtonsState() {
-        if (mIsPeripheralAttached && mSelectedProfile != null) {
-            ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
-            if (mButtonAttributes != null) {
-                if (!mButtonAttributes.mHasBtnA) {
-                    mBtnALabelTxt.setTextColor(Color.GRAY);
-                    mBtnAStatusTxt.setTextColor(Color.GRAY);
-                } else {
-                    mBtnALabelTxt.setTextColor(Color.WHITE);
-                    mBtnAStatusTxt.setTextColor(Color.WHITE);
-                }
-                if (!mButtonAttributes.mHasBtnB) {
-                    mBtnBLabelTxt.setTextColor(Color.GRAY);
-                    mBtnBStatusTxt.setTextColor(Color.GRAY);
-                } else {
-                    mBtnBLabelTxt.setTextColor(Color.WHITE);
-                    mBtnBStatusTxt.setTextColor(Color.WHITE);
-                }
-                if (!mButtonAttributes.mHasBtnC) {
-                    mBtnCLabelTxt.setTextColor(Color.GRAY);
-                    mBtnCStatusTxt.setTextColor(Color.GRAY);
-                } else {
-                    mBtnCLabelTxt.setTextColor(Color.WHITE);
-                    mBtnCStatusTxt.setTextColor(Color.WHITE);
-                }
-            } else {
-                mBtnALabelTxt.setTextColor(Color.GRAY);
-                mBtnAStatusTxt.setTextColor(Color.GRAY);
-                mBtnBLabelTxt.setTextColor(Color.GRAY);
-                mBtnBStatusTxt.setTextColor(Color.GRAY);
-                mBtnCLabelTxt.setTextColor(Color.GRAY);
-                mBtnCStatusTxt.setTextColor(Color.GRAY);
-            }
-        }
+        int ctrlColor = mIsPeripheralAttached && mSelectedProfile != null
+                ? Color.WHITE
+                : Color.GRAY;
+        mBtnALabelTxt.setTextColor(ctrlColor);
+        mBtnAStatusTxt.setTextColor(ctrlColor);
+        mBtnBLabelTxt.setTextColor(ctrlColor);
+        mBtnBStatusTxt.setTextColor(ctrlColor);
+        mBtnCLabelTxt.setTextColor(ctrlColor);
+        mBtnCStatusTxt.setTextColor(ctrlColor);
 
         mBtnAStatusTxt.setText(getString(
             mHasBtnA ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
@@ -110,13 +87,15 @@
         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) {
+            if (match && mButtonAttributes.mHasBtnB != mHasBtnB && !interceptedVolume) {
                 match = false;
             }
-            if (match && mButtonAttributes.mHasBtnC != mHasBtnC) {
+            if (match && mButtonAttributes.mHasBtnC != mHasBtnC && !interceptedVolume) {
                 match = false;
             }
             Log.i(TAG, "match:" + match);
@@ -129,26 +108,28 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         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;
+        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();
         }
 
-        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 5389afb..640d489 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
@@ -50,9 +50,10 @@
 
     //
     // USBAudioPeripheralActivity
-    //
+    // Headset not publicly available, violates CTS Verifier additional equipment guidelines.
     public void updateConnectStatus() {
-        getPassButton().setEnabled(mOutputDevInfo != null);
+        mPlayBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
+        getPassButton().setEnabled(mSelectedProfile != null && mOutputDevInfo != null);
     }
 
     public class LocalClickListener implements View.OnClickListener {
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 f05bc9e..5772461 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -136,7 +136,9 @@
     // USBAudioPeripheralActivity
     //
     public void updateConnectStatus() {
-        getPassButton().setEnabled(mOutputDevInfo != null);
+        mRecordBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
+        mRecordLoopbackBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
+        getPassButton().setEnabled(mSelectedProfile != null && mOutputDevInfo != null);
     }
 
     public class LocalClickListener implements View.OnClickListener {
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 dc73543..d9094e5 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
@@ -22,6 +22,8 @@
 
 import android.util.Log;
 
+import java.lang.Math;
+
 /**
  * Records audio data to a stream.
  */
@@ -115,12 +117,10 @@
     }
 
     private boolean open_internal(int numChans, int sampleRate) {
-        Log.i(TAG, "StreamRecorder.open_internal(chans:" + numChans + ", rate:" + sampleRate);
-
         mNumChannels = numChans;
         mSampleRate = sampleRate;
 
-        int chanMask = AudioUtils.countToIndexMask(numChans);
+        int chanPosMask = AudioUtils.countToInPositionMask(numChans);
         int bufferSizeInBytes = 2048;   // Some, non-critical value
 
         try {
@@ -128,7 +128,7 @@
                     .setAudioFormat(new AudioFormat.Builder()
                             .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
                             .setSampleRate(mSampleRate)
-                            .setChannelIndexMask(chanMask)
+                            .setChannelMask(chanPosMask)
                             .build())
                     .setBufferSizeInBytes(bufferSizeInBytes)
                     .build();
@@ -146,6 +146,11 @@
         if (sucess) {
             mNumBurstFrames = numBurstFrames;
             mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
+            // put some non-zero data in the burst buffer.
+            // this is to verify that the record is putting SOMETHING into each channel.
+            for(int index = 0; index < mBurstBuffer.length; index++) {
+                mBurstBuffer[index] = (float)(Math.random() * 2.0) - 1.0f;
+            }
         }
 
         return sucess;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
index 4c3fbb8..a0cff31 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
@@ -148,7 +148,6 @@
     private static final String kAttr_HasBtnA = "HasBtnA";
     private static final String kAttr_HasBtnB = "HasBtnB";
     private static final String kAttr_HasBtnC = "HasBtnC";
-    private static final String kAttr_HasBtnD = "HasBtnD";
 
     private void parseProfileAttributes(ProfileAttributes attribs, String elementName,
                                         Attributes xmlAtts) {
@@ -164,7 +163,6 @@
         buttonAttributes.mHasBtnA = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnA)) == 1;
         buttonAttributes.mHasBtnB = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnB)) == 1;
         buttonAttributes.mHasBtnC = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnC)) == 1;
-        buttonAttributes.mHasBtnD = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnD)) == 1;
     }
 
     //
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
index ffe5e6d..8bc4872 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
@@ -20,5 +20,4 @@
     public boolean mHasBtnA;
     public boolean mHasBtnB;
     public boolean mHasBtnC;
-    public boolean mHasBtnD;
 }
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 59b20b0..41b0013 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
@@ -18,6 +18,7 @@
 
 import android.os.Environment;
 import android.support.annotation.Nullable;
+import android.util.Log;
 import android.util.Xml;
 
 import org.xml.sax.Attributes;
@@ -40,35 +41,30 @@
 import javax.xml.parsers.SAXParserFactory;
 
 public class ProfileManager {
+    private static final String TAG = "ProfileManager";
+    private static final boolean DEBUG = false;
+
     private static final String mBuiltInprofiles =
-            "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
-            "<ProfileList Version=\"1.0.0\">" +
-              "<PeripheralProfile ProfileName=\"Headset\" ProfileDescription=\"Microsoft LX-3000\" ProductName=\"USB-Audio - Microsoft LifeChat LX-3000\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
-                "<ButtonInfo HasBtnA=\"0\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"0\" />" +
-            "</PeripheralProfile>" +
+        "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
+        "<ProfileList Version=\"1.0.0\">" +
             "<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\" />" +
+                "<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>" +
             "<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\" />" +
+                "<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\" />" +
             "</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\" />" +
+                "<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\" />" +
             "</PeripheralProfile>" +
-            "<PeripheralProfile ProfileName=\"gen1-headset\" ProfileDescription=\"Reference USB Headset\" ProductName=\"USB-Audio - Skylab\">" +
-            "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2,4\" SampleRates=\"8000,16000,32000,44100,48000\" />" +
-            "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"8000,16000,32000,44100,48000\" />" +
-            "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"1\" />" +
-          "</PeripheralProfile>" +
-          "<PeripheralProfile ProfileName=\"mir\" ProfileDescription=\"Reference USB Dongle\" ProductName=\"USB-Audio - USB Audio\">" +
-            "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
-          "</PeripheralProfile>" +
-          "</ProfileList>";
+            "<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\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
+                "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"1\" />" +
+            "</PeripheralProfile>" +
+        "</ProfileList>";
 
     // XML Tags and Attributes
     private final static String kTag_ProfileList = "ProfileList";
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 697ad93..81226a6 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
@@ -16,6 +16,9 @@
 
 package com.android.cts.verifier.camera.its;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +35,7 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -51,6 +55,7 @@
 import android.util.Log;
 import android.util.Rational;
 import android.util.Size;
+import android.util.SparseArray;
 import android.view.Surface;
 
 import com.android.ex.camera2.blocking.BlockingCameraManager;
@@ -59,6 +64,7 @@
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 
 import com.android.cts.verifier.camera.its.StatsImage;
+import com.android.cts.verifier.R;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
@@ -80,8 +86,10 @@
 import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingDeque;
@@ -93,6 +101,9 @@
 public class ItsService extends Service implements SensorEventListener {
     public static final String TAG = ItsService.class.getSimpleName();
 
+    private final int SERVICE_NOTIFICATION_ID = 37; // random int that is unique within app
+    private NotificationChannel mChannel;
+
     // Timeouts, in seconds.
     private static final int TIMEOUT_CALLBACK = 20;
     private static final int TIMEOUT_3A = 10;
@@ -135,6 +146,7 @@
     private CameraDevice mCamera = null;
     private CameraCaptureSession mSession = null;
     private ImageReader[] mOutputImageReaders = null;
+    private SparseArray<String> mPhysicalStreamMap = new SparseArray<String>();
     private ImageReader mInputImageReader = null;
     private CameraCharacteristics mCameraCharacteristics = null;
 
@@ -201,8 +213,11 @@
     private HandlerThread mSensorThread = null;
     private Handler mSensorHandler = null;
 
+    private static final int SERIALIZER_SURFACES_ID = 2;
+    private static final int SERIALIZER_PHYSICAL_METADATA_ID = 3;
+
     public interface CaptureCallback {
-        void onCaptureAvailable(Image capture);
+        void onCaptureAvailable(Image capture, String physicalCameraId);
     }
 
     public abstract class CaptureResultListener extends CameraCaptureSession.CaptureCallback {}
@@ -270,6 +285,15 @@
         } catch (ItsException e) {
             Logt.e(TAG, "Service failed to start: ", e);
         }
+
+        NotificationManager notificationManager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        mChannel = new NotificationChannel(
+                "ItsServiceChannel", "ItsService", NotificationManager.IMPORTANCE_LOW);
+        // Configure the notification channel.
+        mChannel.setDescription("ItsServiceChannel");
+        mChannel.enableVibration(false);
+        notificationManager.createNotificationChannel(mChannel);
     }
 
     @Override
@@ -285,6 +309,13 @@
             } else {
                 Logt.e(TAG, "Starting ItsService in bad state");
             }
+
+            Notification notification = new Notification.Builder(this, mChannel.getId())
+                    .setContentTitle("CameraITS Service")
+                    .setContentText("CameraITS Service is running")
+                    .setSmallIcon(R.drawable.icon)
+                    .setOngoing(true).build();
+            startForeground(SERVICE_NOTIFICATION_ID, notification);
         } catch (java.lang.InterruptedException e) {
             Logt.e(TAG, "Error starting ItsService (interrupted)", e);
         }
@@ -394,9 +425,20 @@
                             jsonObj.put("captureResult", ItsSerializer.serialize(
                                     (CaptureResult)obj));
                         } else if (obj instanceof JSONArray) {
-                            jsonObj.put("outputs", (JSONArray)obj);
+                            if (tag == "captureResults") {
+                                if (i == SERIALIZER_SURFACES_ID) {
+                                    jsonObj.put("outputs", (JSONArray)obj);
+                                } else if (i == SERIALIZER_PHYSICAL_METADATA_ID) {
+                                    jsonObj.put("physicalResults", (JSONArray)obj);
+                                } else {
+                                    throw new ItsException(
+                                            "Unsupported JSONArray for captureResults");
+                                }
+                            } else {
+                                jsonObj.put("outputs", (JSONArray)obj);
+                            }
                         } else {
-                            throw new ItsException("Invalid object received for serialiation");
+                            throw new ItsException("Invalid object received for serialization");
                         }
                     }
                     if (tag == null) {
@@ -608,6 +650,8 @@
                     closeCameraDevice();
                 } else if ("getCameraProperties".equals(cmdObj.getString("cmdName"))) {
                     doGetProps();
+                } else if ("getCameraPropertiesById".equals(cmdObj.getString("cmdName"))) {
+                    doGetPropsById(cmdObj);
                 } else if ("startSensorEvents".equals(cmdObj.getString("cmdName"))) {
                     doStartSensorEvents();
                 } else if ("getSensorEvents".equals(cmdObj.getString("cmdName"))) {
@@ -724,7 +768,7 @@
 
         public void sendResponseCaptureResult(CameraCharacteristics props,
                                               CaptureRequest request,
-                                              CaptureResult result,
+                                              TotalCaptureResult result,
                                               ImageReader[] readers)
                 throws ItsException {
             try {
@@ -762,12 +806,19 @@
                     jsonSurfaces.put(jsonSurface);
                 }
 
-                Object objs[] = new Object[5];
+                Map<String, CaptureResult> physicalMetadata =
+                        result.getPhysicalCameraResults();
+                JSONArray jsonPhysicalMetadata = new JSONArray();
+                for (Map.Entry<String, CaptureResult> pair : physicalMetadata.entrySet()) {
+                    JSONObject jsonOneMetadata = new JSONObject();
+                    jsonOneMetadata.put(pair.getKey(), ItsSerializer.serialize(pair.getValue()));
+                    jsonPhysicalMetadata.put(jsonOneMetadata);
+                }
+                Object objs[] = new Object[4];
                 objs[0] = "captureResults";
-                objs[1] = props;
-                objs[2] = request;
-                objs[3] = result;
-                objs[4] = jsonSurfaces;
+                objs[1] = result;
+                objs[SERIALIZER_SURFACES_ID] = jsonSurfaces;
+                objs[SERIALIZER_PHYSICAL_METADATA_ID] = jsonPhysicalMetadata;
                 mSerializerQueue.put(objs);
             } catch (org.json.JSONException e) {
                 throw new ItsException("JSON error: ", e);
@@ -785,7 +836,13 @@
                 Image i = null;
                 try {
                     i = reader.acquireNextImage();
-                    listener.onCaptureAvailable(i);
+                    String physicalCameraId = new String();
+                    for (int idx = 0; idx < mOutputImageReaders.length; idx++) {
+                        if (mOutputImageReaders[idx] == reader) {
+                            physicalCameraId = mPhysicalStreamMap.get(idx);
+                        }
+                    }
+                    listener.onCaptureAvailable(i, physicalCameraId);
                 } finally {
                     if (i != null) {
                         i.close();
@@ -825,6 +882,34 @@
         mSocketRunnableObj.sendResponse(mCameraCharacteristics);
     }
 
+    private void doGetPropsById(JSONObject params) throws ItsException {
+        String[] devices;
+        try {
+            devices = mCameraManager.getCameraIdList();
+            if (devices == null || devices.length == 0) {
+                throw new ItsException("No camera devices");
+            }
+        } catch (CameraAccessException e) {
+            throw new ItsException("Failed to get device ID list", e);
+        }
+
+        try {
+            String cameraId = params.getString("cameraId");
+            if (Arrays.asList(devices).contains(cameraId)) {
+                CameraCharacteristics characteristics =
+                        mCameraManager.getCameraCharacteristics(cameraId);
+                mSocketRunnableObj.sendResponse(characteristics);
+            } else {
+                Log.e(TAG, "Invalid camera ID: " + cameraId);
+                throw new ItsException("Invalid cameraId:" + cameraId);
+            }
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        } catch (CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+    }
+
     private void doGetCameraIds() throws ItsException {
         String[] devices;
         try {
@@ -1118,6 +1203,7 @@
         Size outputSizes[];
         int outputFormats[];
         int numSurfaces = 0;
+        mPhysicalStreamMap.clear();
 
         if (jsonOutputSpecs != null) {
             try {
@@ -1186,6 +1272,10 @@
                     if (height <= 0) {
                         height = ItsUtils.getMaxSize(sizes).getHeight();
                     }
+                    String physicalCameraId = surfaceObj.optString("physicalCamera");
+                    if (physicalCameraId != null) {
+                        mPhysicalStreamMap.put(i, physicalCameraId);
+                    }
 
                     // The stats computation only applies to the active array region.
                     int aaw = ItsUtils.getActiveArrayCropRegion(mCameraCharacteristics).width();
@@ -1238,7 +1328,8 @@
 
                 int newCount = mCountCallbacksRemaining.get();
                 if (newCount == currentCount) {
-                    throw new ItsException("No callback received within timeout");
+                    throw new ItsException("No callback received within timeout " +
+                            timeoutMs + "ms");
                 }
                 currentCount = newCount;
             }
@@ -1277,11 +1368,18 @@
                 numSurfaces = mOutputImageReaders.length;
                 numCaptureSurfaces = numSurfaces - (backgroundRequest ? 1 : 0);
 
-                List<Surface> outputSurfaces = new ArrayList<Surface>(numSurfaces);
+                List<OutputConfiguration> outputConfigs =
+                        new ArrayList<OutputConfiguration>(numSurfaces);
                 for (int i = 0; i < numSurfaces; i++) {
-                    outputSurfaces.add(mOutputImageReaders[i].getSurface());
+                    OutputConfiguration config = new OutputConfiguration(
+                            mOutputImageReaders[i].getSurface());
+                    if (mPhysicalStreamMap.get(i) != null) {
+                        config.setPhysicalCameraId(mPhysicalStreamMap.get(i));
+                    }
+                    outputConfigs.add(config);
                 }
-                mCamera.createCaptureSession(outputSurfaces, sessionListener, mCameraHandler);
+                mCamera.createCaptureSessionByOutputConfigurations(outputConfigs,
+                        sessionListener, mCameraHandler);
                 mSession = sessionListener.waitAndGetSession(TIMEOUT_IDLE_MS);
 
                 for (int i = 0; i < numSurfaces; i++) {
@@ -1546,7 +1644,7 @@
 
     private final CaptureCallback mCaptureCallback = new CaptureCallback() {
         @Override
-        public void onCaptureAvailable(Image capture) {
+        public void onCaptureAvailable(Image capture, String physicalCameraId) {
             try {
                 int format = capture.getFormat();
                 if (format == ImageFormat.JPEG) {
@@ -1559,20 +1657,21 @@
                     Logt.i(TAG, "Received YUV capture");
                     byte[] img = ItsUtils.getDataFromImage(capture, mSocketQueueQuota);
                     ByteBuffer buf = ByteBuffer.wrap(img);
-                    int count = mCountYuv.getAndIncrement();
-                    mSocketRunnableObj.sendResponseCaptureBuffer("yuvImage", buf);
+                    mSocketRunnableObj.sendResponseCaptureBuffer(
+                            "yuvImage"+physicalCameraId, buf);
                 } else if (format == ImageFormat.RAW10) {
                     Logt.i(TAG, "Received RAW10 capture");
                     byte[] img = ItsUtils.getDataFromImage(capture, mSocketQueueQuota);
                     ByteBuffer buf = ByteBuffer.wrap(img);
                     int count = mCountRaw10.getAndIncrement();
-                    mSocketRunnableObj.sendResponseCaptureBuffer("raw10Image", buf);
+                    mSocketRunnableObj.sendResponseCaptureBuffer(
+                            "raw10Image"+physicalCameraId, buf);
                 } else if (format == ImageFormat.RAW12) {
                     Logt.i(TAG, "Received RAW12 capture");
                     byte[] img = ItsUtils.getDataFromImage(capture, mSocketQueueQuota);
                     ByteBuffer buf = ByteBuffer.wrap(img);
                     int count = mCountRaw12.getAndIncrement();
-                    mSocketRunnableObj.sendResponseCaptureBuffer("raw12Image", buf);
+                    mSocketRunnableObj.sendResponseCaptureBuffer("raw12Image"+physicalCameraId, buf);
                 } else if (format == ImageFormat.RAW_SENSOR) {
                     Logt.i(TAG, "Received RAW16 capture");
                     int count = mCountRawOrDng.getAndIncrement();
@@ -1580,7 +1679,8 @@
                         byte[] img = ItsUtils.getDataFromImage(capture, mSocketQueueQuota);
                         if (! mCaptureRawIsStats) {
                             ByteBuffer buf = ByteBuffer.wrap(img);
-                            mSocketRunnableObj.sendResponseCaptureBuffer("rawImage", buf);
+                            mSocketRunnableObj.sendResponseCaptureBuffer(
+                                    "rawImage" + physicalCameraId, buf);
                         } else {
                             // Compute the requested stats on the raw frame, and return the results
                             // in a new "stats image".
@@ -1703,8 +1803,8 @@
                     logMsg.append(String.format(
                             "sens=%d, exp=%.1fms, dur=%.1fms, ",
                             result.get(CaptureResult.SENSOR_SENSITIVITY),
-                            result.get(CaptureResult.SENSOR_EXPOSURE_TIME).intValue() / 1000000.0f,
-                            result.get(CaptureResult.SENSOR_FRAME_DURATION).intValue() /
+                            result.get(CaptureResult.SENSOR_EXPOSURE_TIME).longValue() / 1000000.0f,
+                            result.get(CaptureResult.SENSOR_FRAME_DURATION).longValue() /
                                         1000000.0f));
                 }
                 if (result.get(CaptureResult.COLOR_CORRECTION_GAINS) != null) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java
new file mode 100644
index 0000000..1e24e6a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.location;
+
+import android.location.cts.GnssMeasurementValuesTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS GnssMeasurementValuesTest while dialing emergency number.
+ * It is a wrapper for {@link GnssMeasurementValuesTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallGNSSTestsActivity extends EmergencyCallBaseTestActivity {
+  // GNSS test has a longer timeout
+  private static final long PHONE_CALL_DURATION_MS = TimeUnit.MINUTES.toMillis(2);
+
+  public EmergencyCallGNSSTestsActivity() {
+    super(GnssMeasurementValuesTest.class);
+  }
+
+  @Override
+  protected long getPhoneCallDurationMs() {
+    return PHONE_CALL_DURATION_MS;
+  }
+
+  @Override
+  protected boolean showLocalNumberInputbox() {
+    return false;
+  }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java
new file mode 100644
index 0000000..7377065
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.location;
+
+import android.location.cts.EmergencyCallMessageTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS EmergencyCallMessageTest while dialing emergency number.
+ * It is a wrapper for {@link EmergencyCallMessageTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallMessageTestsActivity extends EmergencyCallBaseTestActivity {
+  private static final long PHONE_CALL_DURATION_MS = TimeUnit.SECONDS.toMillis(35);
+  public EmergencyCallMessageTestsActivity() {
+    super(EmergencyCallMessageTest.class);
+  }
+
+  @Override
+  protected long getPhoneCallDurationMs() {
+    return PHONE_CALL_DURATION_MS;
+  }
+
+  @Override
+  protected boolean showLocalNumberInputbox() {
+    return true;
+  }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java
new file mode 100644
index 0000000..634863d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.location;
+
+import android.location.cts.EmergencyCallWifiTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS EmergencyCallWifiTest while dialing emergency number.
+ * It is a wrapper for {@link EmergencyCallWifiTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallWifiTestsActivity extends EmergencyCallBaseTestActivity {
+    private static final long PHONE_CALL_DURATION_MS = TimeUnit.SECONDS.toMillis(35);
+    public EmergencyCallWifiTestsActivity() {
+        super(EmergencyCallWifiTest.class);
+    }
+
+    @Override
+    protected long getPhoneCallDurationMs() {
+      return PHONE_CALL_DURATION_MS;
+    }
+
+    @Override
+    protected boolean showLocalNumberInputbox() {
+      return false;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java
new file mode 100644
index 0000000..ef88e9b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.location.base;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.location.cts.GnssTestCase;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.LayoutInflater;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import com.android.cts.verifier.R;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Activity that allows Gnss CTS tests to be executed inside CtsVerifier.
+ *
+ * Sub-classes pass the test class as part of construction.
+ * One JUnit test class is executed per Activity, the test class can still be executed outside
+ * CtsVerifier.
+ */
+public abstract class EmergencyCallBaseTestActivity extends GnssCtsTestActivity {
+    private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
+    private static final String defaultPhonePackageName = "com.google.android.dialer";
+
+    /**
+     * Constructor for a CTS test executor. It will execute a standalone CTS test class.
+     *
+     * @param testClass The test class to execute, it must be a subclass of {@link AndroidTestCase}.
+     */
+    protected EmergencyCallBaseTestActivity(Class<? extends GnssTestCase> testClass) {
+        super(testClass);
+    }
+
+    protected abstract long getPhoneCallDurationMs();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // override the test info
+        mTextView.setText(R.string.location_emergency_call_test_info);
+        EmergencyCallUtil.setDefaultDialer(this, this.getPackageName());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        EmergencyCallUtil.setDefaultDialer(this, defaultPhonePackageName);
+    }
+
+    protected abstract boolean showLocalNumberInputbox();
+
+    @Override
+    public void onClick(View target) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        final FrameLayout frameView = new FrameLayout(this);
+        builder.setView(frameView);
+
+        final boolean enableLocalNumberInputBox = showLocalNumberInputbox();
+        final AlertDialog alertDialog = builder.create();
+        LayoutInflater inflater = alertDialog.getLayoutInflater();
+
+        View dialogView;
+        if (enableLocalNumberInputBox) {
+            dialogView =
+                inflater.inflate(R.layout.emergency_call_msg_test_confirm_dialog, frameView);
+        } else {
+            dialogView = inflater.inflate(R.layout.emergency_call_confirm_dialog, frameView);
+        }
+        final EditText targetNumberEditText =
+            (EditText) dialogView.findViewById(R.id.emergency_number);
+        final Button dialButton = (Button) dialogView.findViewById(R.id.dial_button);
+        dialButton.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (enableLocalNumberInputBox) {
+                    final EditText currentNumberEditText =
+                        (EditText) dialogView.findViewById(R.id.local_phone_number);
+                    String currentNumber = currentNumberEditText.getText().toString();
+                    // pass the number to cts tests for cts verifier UI, through System property.
+                    System.setProperty(PHONE_NUMBER_KEY, currentNumber);
+                }
+                int targetPhoneNumber =
+                    Integer.parseInt(targetNumberEditText.getText().toString());
+                long callDurationMs = EmergencyCallBaseTestActivity.this.getPhoneCallDurationMs();
+                EmergencyCallUtil.makePhoneCall(
+                    EmergencyCallBaseTestActivity.this, targetPhoneNumber);
+                EmergencyCallBaseTestActivity.super.onClick(target);
+                EmergencyCallUtil.endCallWithDelay(
+                    EmergencyCallBaseTestActivity.this.getApplicationContext(), callDurationMs);
+                alertDialog.dismiss();
+            }
+        });
+        alertDialog.show();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java
new file mode 100644
index 0000000..12b7ac2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java
@@ -0,0 +1,120 @@
+/*
+ * 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.location.base;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.text.InputType;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import com.android.cts.verifier.R;
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The EmergencyCallUtil class provides util functions related to the emergency call.
+ */
+public class EmergencyCallUtil {
+    private static final int REQUEST_CODE_SET_DEFAULT_DIALER = 1;
+    private static final String TAG = "EmergencyCallUtil";
+    private static final long WAIT_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(3);
+
+    /*
+     * This method is used to set default dialer app.
+     * To dial 911, it requires to set the dialer to be the system default dial app.
+     *
+     * @param activity current Activity.
+     * @param packageName dialer package name.
+     */
+    public static void setDefaultDialer(Activity activity, String packageName) {
+        final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
+        intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+        activity.startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER);
+    }
+
+    public static void makePhoneCall(Activity activity, int phoneNumber) {
+        Intent callIntent = new Intent(Intent.ACTION_CALL);
+        callIntent.setData(Uri.parse("tel:" + phoneNumber));
+        try {
+            callIntent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+            activity.startActivityForResult(callIntent, REQUEST_CODE_SET_DEFAULT_DIALER);
+        } catch (SecurityException ex) {
+            Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+        }
+        // sleep 3sec to make sure call is connected
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(WAIT_FOR_CONNECTION_MS);
+                } catch (InterruptedException ex) {
+                    Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+                }
+            }
+        });
+    }
+
+    public static void endCallWithDelay(Context context, long delayMs) {
+        Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(delayMs);
+                    endCall(context);
+                } catch (InterruptedException ex) {
+                    Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+                }
+            }
+        };
+        new Thread(runnable).start();
+    }
+
+    private static void endCall(Context context) {
+        try {
+            TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+            Class<?> classTelephony = Class.forName(telephonyManager.getClass().getName());
+            Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony");
+            methodGetITelephony.setAccessible(true);
+
+            Object telephonyInterface = methodGetITelephony.invoke(telephonyManager);
+
+            Class<?> telephonyInterfaceClass =
+                Class.forName(telephonyInterface.getClass().getName());
+            Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall");
+
+            methodEndCall.invoke(telephonyInterface);
+
+        } catch (Exception e) {
+            Log.d(TAG, "Failed to cancel the call: " + e.toString());
+        }
+    }
+
+    private static String getCurrentPhoneNumber(Activity activity) {
+        TelephonyManager tMgr =
+            (TelephonyManager)activity.getSystemService(Context.TELEPHONY_SERVICE);
+        return tMgr.getLine1Number();
+    }
+
+}
\ No newline at end of file
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 15808a7..52b8027 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -78,6 +78,7 @@
     private DialogTestListItem mWorkNotificationBadgedTest;
     private DialogTestListItem mWorkStatusBarIconTest;
     private DialogTestListItem mWorkStatusBarToastTest;
+    private DialogTestListItem mUserSettingsVisibleTest;
     private DialogTestListItem mAppSettingsVisibleTest;
     private DialogTestListItem mLocationSettingsVisibleTest;
     private DialogTestListItem mWiFiDataUsageSettingsVisibleTest;
@@ -101,6 +102,7 @@
     private DialogTestListItem mConfirmWorkCredentials;
     private DialogTestListItem mParentProfilePassword;
     private TestListItem mVpnTest;
+    private TestListItem mKeyChainTest;
     private TestListItem mAlwaysOnVpnSettingsTest;
     private TestListItem mRecentsTest;
     private TestListItem mDisallowAppsControlTest;
@@ -286,6 +288,12 @@
                 R.string.provisioning_byod_profile_visible_instruction,
                 new Intent(Settings.ACTION_SETTINGS));
 
+        mUserSettingsVisibleTest = new DialogTestListItem(this,
+            R.string.provisioning_byod_user_settings,
+            "BYOD_UserSettingsVisibleTest",
+            R.string.provisioning_byod_user_settings_instruction,
+            new Intent(Settings.ACTION_SETTINGS));
+
         mAppSettingsVisibleTest = new DialogTestListItem(this,
                 R.string.provisioning_byod_app_settings,
                 "BYOD_AppSettingsVisibleTest",
@@ -415,6 +423,12 @@
                 new Intent(this, OrganizationInfoTestActivity.class),
                 null);
 
+        mKeyChainTest = TestListItem.newTest(this,
+                R.string.provisioning_byod_keychain,
+                KeyChainTestActivity.class.getName(),
+                new Intent(KeyChainTestActivity.ACTION_KEYCHAIN),
+                null);
+
         mParentProfilePassword = new DialogTestListItem(this,
                 R.string.provisioning_byod_parent_profile_password,
                 "BYOD_ParentProfilePasswordTest",
@@ -449,6 +463,7 @@
         adapter.add(mProfileAccountVisibleTest);
         adapter.add(mDeviceAdminVisibleTest);
         adapter.add(mCredSettingsVisibleTest);
+        adapter.add(mUserSettingsVisibleTest);
         adapter.add(mAppSettingsVisibleTest);
         adapter.add(mLocationSettingsVisibleTest);
         adapter.add(mPrintSettingsVisibleTest);
@@ -563,6 +578,7 @@
                 }
             };
             adapter.add(mDisableNfcBeamTest);
+            adapter.add(mKeyChainTest);
         }
 
         /* If there is an application that handles RECORD_SOUND_ACTION, test that it handles it
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
index 5053a90..bfa65b7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
@@ -42,7 +42,8 @@
                 AlwaysOnVpnSettingsTestActivity.class.getName(),
                 RecentsRedactionActivity.class.getName(),
                 CommandReceiverActivity.class.getName(),
-                SetSupportMessageActivity.class.getName()
+                SetSupportMessageActivity.class.getName(),
+                KeyChainTestActivity.class.getName()
         };
         for (String component : components) {
             mPackageManager.setComponentEnabledSetting(new ComponentName(mContext, component),
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 1d4d13a..a3c0e28 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.verifier.managedprovisioning;
 
+import static android.app.admin.DevicePolicyManager.MAKE_USER_EPHEMERAL;
+import static android.app.admin.DevicePolicyManager.SKIP_SETUP_WIZARD;
+
 import android.Manifest;
 import android.app.Activity;
 import android.app.KeyguardManager;
@@ -30,24 +33,28 @@
 import android.graphics.BitmapFactory;
 import android.net.ProxyInfo;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
 
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.managedprovisioning.Utils;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 public class CommandReceiverActivity extends Activity {
     private static final String TAG = "CommandReceiverActivity";
@@ -68,6 +75,7 @@
     public static final String COMMAND_SET_KEYGUARD_DISABLED = "set-keyguard-disabled";
     public static final String COMMAND_SET_LOCK_SCREEN_INFO = "set-lock-screen-info";
     public static final String COMMAND_SET_STATUSBAR_DISABLED = "set-statusbar-disabled";
+    public static final String COMMAND_SET_LOCK_TASK_FEATURES = "set-lock-task-features";
     public static final String COMMAND_ALLOW_ONLY_SYSTEM_INPUT_METHODS =
             "allow-only-system-input-methods";
     public static final String COMMAND_ALLOW_ONLY_SYSTEM_ACCESSIBILITY_SERVICES =
@@ -102,6 +110,13 @@
             "clear-maximum-password-attempts";
     public static final String COMMAND_SET_DEFAULT_IME = "set-default-ime";
     public static final String COMMAND_CLEAR_DEFAULT_IME = "clear-default-ime";
+    public static final String COMMAND_CREATE_MANAGED_USER = "create-managed-user";
+    public static final String COMMAND_CREATE_MANAGED_USER_WITHOUT_SETUP =
+            "create-managed-user-without-setup";
+    public static final String COMMAND_WITH_USER_SWITCHER_MESSAGE = "with-user-switcher-message";
+    public static final String COMMAND_WITHOUT_USER_SWITCHER_MESSAGE =
+            "without-user-switcher-message";
+    public static final String COMMAND_ENABLE_LOGOUT = "enable-logout";
 
     public static final String EXTRA_USER_RESTRICTION =
             "com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -171,9 +186,8 @@
                     Context.DEVICE_POLICY_SERVICE);
             mUm = (UserManager) getSystemService(Context.USER_SERVICE);
             mAdmin = DeviceAdminTestReceiver.getReceiverComponentName();
-            Log.i(TAG, "Command: " + intent);
-
             final String command = getIntent().getStringExtra(EXTRA_COMMAND);
+            Log.i(TAG, "Command: " + command);
             switch (command) {
                 case COMMAND_SET_USER_RESTRICTION: {
                     String restrictionKey = intent.getStringExtra(EXTRA_USER_RESTRICTION);
@@ -220,9 +234,15 @@
                     boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
                     mDpm.setStatusBarDisabled(mAdmin, enforced);
                 } break;
+                case COMMAND_SET_LOCK_TASK_FEATURES: {
+                    int flags = intent.getIntExtra(EXTRA_VALUE,
+                            DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                    mDpm.setLockTaskFeatures(mAdmin, flags);
+                } break;
                 case COMMAND_ALLOW_ONLY_SYSTEM_INPUT_METHODS: {
                     boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
-                    mDpm.setPermittedInputMethods(mAdmin, enforced ? new ArrayList() : null);
+                    mDpm.setPermittedInputMethods(mAdmin,
+                            enforced ? getEnabledNonSystemImes() : null);
                 } break;
                 case COMMAND_ALLOW_ONLY_SYSTEM_ACCESSIBILITY_SERVICES: {
                     boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
@@ -458,7 +478,44 @@
                         return;
                     }
                     mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
-                }
+                } break;
+                case COMMAND_CREATE_MANAGED_USER:{
+                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                        return;
+                    }
+                    PersistableBundle extras = new PersistableBundle();
+                    extras.putBoolean(DeviceAdminTestReceiver.EXTRA_MANAGED_USER_TEST, true);
+                    UserHandle userHandle = mDpm.createAndManageUser(mAdmin, "managed user", mAdmin,
+                            extras,
+                            SKIP_SETUP_WIZARD | MAKE_USER_EPHEMERAL);
+                    mDpm.setAffiliationIds(mAdmin,
+                            Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+                    mDpm.startUserInBackground(mAdmin, userHandle);
+                } break;
+                case COMMAND_CREATE_MANAGED_USER_WITHOUT_SETUP:{
+                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                        return;
+                    }
+                    PersistableBundle extras = new PersistableBundle();
+                    extras.putBoolean(DeviceAdminTestReceiver.EXTRA_MANAGED_USER_TEST, true);
+                    mDpm.createAndManageUser(mAdmin, "managed user", mAdmin, extras, /* flags */ 0);
+                } break;
+                case COMMAND_WITH_USER_SWITCHER_MESSAGE: {
+                    createAndSwitchUserWithMessage("Start user session", "End user session");
+                } break;
+                case COMMAND_WITHOUT_USER_SWITCHER_MESSAGE: {
+                    createAndSwitchUserWithMessage(null, null);
+                } break;
+                case COMMAND_ENABLE_LOGOUT: {
+                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                        return;
+                    }
+                    mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_USER_SWITCH);
+                    mDpm.setLogoutEnabled(mAdmin, true);
+                    UserHandle userHandle = mDpm.createAndManageUser(mAdmin, "managed user", mAdmin,
+                            null, SKIP_SETUP_WIZARD | MAKE_USER_EPHEMERAL);
+                    mDpm.switchUser(mAdmin, userHandle);
+                } break;
             }
         } catch (Exception e) {
             Log.e(TAG, "Failed to execute command: " + intent, e);
@@ -506,6 +563,7 @@
         mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_CONFIG_BLUETOOTH);
         mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_CONFIG_VPN);
         mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_DATA_ROAMING);
+        mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_USER_SWITCH);
 
         mDpm.setDeviceOwnerLockScreenInfo(mAdmin, null);
         mDpm.setKeyguardDisabled(mAdmin, false);
@@ -527,6 +585,10 @@
         mDpm.uninstallCaCert(mAdmin, TEST_CA.getBytes());
         mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
         mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
+        mDpm.setAffiliationIds(mAdmin, Collections.emptySet());
+        mDpm.setStartUserSessionMessage(mAdmin, null);
+        mDpm.setEndUserSessionMessage(mAdmin, null);
+        mDpm.setLogoutEnabled(mAdmin, false);
 
         uninstallHelperPackage();
         removeManagedProfile();
@@ -562,4 +624,44 @@
             mDpm.removeUser(mAdmin, userHandle);
         }
     }
+
+    public static Intent createSetUserRestrictionIntent(String restriction, boolean enforced) {
+        return new Intent(ACTION_EXECUTE_COMMAND)
+                .putExtra(EXTRA_COMMAND,COMMAND_SET_USER_RESTRICTION)
+                .putExtra(EXTRA_USER_RESTRICTION, restriction)
+                .putExtra(EXTRA_ENFORCED, enforced);
+    }
+
+    private List<String> getEnabledNonSystemImes() {
+        InputMethodManager inputMethodManager = getSystemService(InputMethodManager.class);
+        final List<InputMethodInfo> inputMethods = inputMethodManager.getEnabledInputMethodList();
+        return inputMethods.stream()
+                .filter(inputMethodInfo -> !isSystemInputMethodInfo(inputMethodInfo))
+                .map(inputMethodInfo -> inputMethodInfo.getPackageName())
+                .filter(packageName -> !packageName.equals(getPackageName()))
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    private boolean isSystemInputMethodInfo(InputMethodInfo inputMethodInfo) {
+        return inputMethodInfo.getServiceInfo().applicationInfo.isSystemApp();
+    }
+
+    private void createAndSwitchUserWithMessage(String startUserSessionMessage,
+            String endUserSessionMessage) {
+        if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+            return;
+        }
+        mDpm.setStartUserSessionMessage(mAdmin, startUserSessionMessage);
+        mDpm.setEndUserSessionMessage(mAdmin, endUserSessionMessage);
+        mDpm.setAffiliationIds(mAdmin,
+                Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+
+        PersistableBundle extras = new PersistableBundle();
+        extras.putBoolean(DeviceAdminTestReceiver.EXTRA_LOGOUT_ON_START, true);
+        UserHandle userHandle = mDpm.createAndManageUser(mAdmin, "managed user", mAdmin,
+                extras,
+                SKIP_SETUP_WIZARD | MAKE_USER_EPHEMERAL);
+        mDpm.switchUser(mAdmin, userHandle);
+    }
 }
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 f54e567..e9f62a1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -18,18 +18,28 @@
 
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
 
+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.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
 import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.location.LocationListenerActivity;
 
+import java.util.Collections;
+import java.util.function.Consumer;
+
 /**
  * Profile owner receiver for BYOD flow test.
  * Setup cross-profile intent filter after successful provisioning.
@@ -43,6 +53,11 @@
             DEVICE_OWNER_PKG + ".managedprovisioning.DeviceAdminTestReceiver";
     private static final ComponentName RECEIVER_COMPONENT_NAME = new ComponentName(
             DEVICE_OWNER_PKG, ADMIN_RECEIVER_TEST_CLASS);
+    public static final String EXTRA_MANAGED_USER_TEST =
+            "com.android.cts.verifier.managedprovisioning.extra.MANAGED_USER_TEST";
+    public static final String EXTRA_LOGOUT_ON_START =
+            "com.android.cts.verifier.managedprovisioning.extra.LOGOUT_ON_START";
+    public static final String AFFILIATION_ID = "affiliationId";
 
     public static ComponentName getReceiverComponentName() {
         return RECEIVER_COMPONENT_NAME;
@@ -76,6 +91,46 @@
                 R.string.bugreport_failed_completing), Utils.BUGREPORT_NOTIFICATION_ID);
     }
 
+    @Override
+    public void onLockTaskModeEntering(Context context, Intent intent, String pkg) {
+        Log.i(TAG, "Entering LockTask mode: " + pkg);
+        LocalBroadcastManager.getInstance(context)
+                .sendBroadcast(new Intent(LockTaskUiTestActivity.ACTION_LOCK_TASK_STARTED));
+    }
+
+    @Override
+    public void onLockTaskModeExiting(Context context, Intent intent) {
+        Log.i(TAG, "Exiting LockTask mode");
+        LocalBroadcastManager.getInstance(context)
+                .sendBroadcast(new Intent(LockTaskUiTestActivity.ACTION_LOCK_TASK_STOPPED));
+    }
+
+    @Override
+    public void onEnabled(Context context, Intent intent) {
+        Log.i(TAG, "Device admin enabled");
+        if (intent.getBooleanExtra(EXTRA_MANAGED_USER_TEST, false)) {
+            DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+            ComponentName admin = getReceiverComponentName();
+            dpm.setAffiliationIds(admin,
+                    Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+            context.startActivity(new Intent(context, ManagedUserPositiveTestActivity.class));
+
+            bindPrimaryUserService(context, iCrossUserService -> {
+                try {
+                    iCrossUserService.switchUser(Process.myUserHandle());
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Error when calling primary user", re);
+                }
+            });
+        } else if (intent.getBooleanExtra(EXTRA_LOGOUT_ON_START, false)) {
+            DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+            ComponentName admin = getReceiverComponentName();
+            dpm.setAffiliationIds(admin,
+                    Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+            dpm.logoutUser(admin);
+        }
+    }
+
     private void setupProfile(Context context) {
         DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         dpm.setProfileEnabled(new ComponentName(context.getApplicationContext(), getClass()));
@@ -117,6 +172,7 @@
         filter.addAction(ByodHelperActivity.ACTION_SET_ORGANIZATION_INFO);
         filter.addAction(ByodHelperActivity.ACTION_TEST_PARENT_PROFILE_PASSWORD);
         filter.addAction(SetSupportMessageActivity.ACTION_SET_SUPPORT_MSG);
+        filter.addAction(KeyChainTestActivity.ACTION_KEYCHAIN);
         filter.addAction(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
         dpm.addCrossProfileIntentFilter(getWho(context), filter,
                 DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
@@ -143,4 +199,43 @@
             getManager(context).wipeData(0);
         }
     }
+
+    private void bindPrimaryUserService(Context context, Consumer<ICrossUserService> consumer) {
+        DevicePolicyManager devicePolicyManager = context.getSystemService(
+                DevicePolicyManager.class);
+        UserHandle primaryUser = devicePolicyManager.getBindDeviceAdminTargetUsers(
+                getReceiverComponentName()).get(0);
+
+        Log.d(TAG, "Calling primary user: " + primaryUser);
+        final ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.d(TAG, "onServiceConnected is called");
+                consumer.accept(ICrossUserService.Stub.asInterface(service));
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                Log.d(TAG, "onServiceDisconnected is called");
+            }
+        };
+        final Intent serviceIntent = new Intent(context, PrimaryUserService.class);
+        devicePolicyManager.bindDeviceAdminServiceAsUser(getReceiverComponentName(), serviceIntent,
+                serviceConnection, Context.BIND_AUTO_CREATE, primaryUser);
+    }
+
+    public static final class PrimaryUserService extends Service {
+        private final ICrossUserService.Stub mBinder = new ICrossUserService.Stub() {
+            public void switchUser(UserHandle userHandle) {
+                Log.d(TAG, "switchUser: " + userHandle);
+                getSystemService(DevicePolicyManager.class).switchUser(getReceiverComponentName(),
+                        userHandle);
+            }
+        };
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return mBinder;
+        }
+    }
 }
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 0cae6be..ae9dd03 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -21,7 +21,6 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -29,12 +28,10 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 
 import com.android.cts.verifier.ArrayTestListAdapter;
-import com.android.cts.verifier.IntentDrivenTestActivity;
 import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -60,6 +57,7 @@
     private static final String WIFI_LOCKDOWN_TEST_ID = WifiLockdownTestActivity.class.getName();
     private static final String DISABLE_STATUS_BAR_TEST_ID = "DISABLE_STATUS_BAR";
     private static final String DISABLE_KEYGUARD_TEST_ID = "DISABLE_KEYGUARD";
+    private static final String LOCK_TASK_UI_TEST_ID = "LOCK_TASK_UI";
     private static final String CHECK_PERMISSION_LOCKDOWN_TEST_ID =
             PermissionLockdownTestActivity.class.getName();
     private static final String DISALLOW_CONFIG_BT_ID = "DISALLOW_CONFIG_BT";
@@ -72,7 +70,11 @@
     private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
     private static final String ENTERPRISE_PRIVACY_TEST_ID = "ENTERPRISE_PRIVACY";
     private static final String NETWORK_LOGGING_UI_TEST_ID = "NETWORK_LOGGING_UI";
-    public static final String COMP_TEST_ID = "COMP_UI";
+    private static final String DISALLOW_USER_SWITCH_TEST_ID = "DISALLOW_USER_SWITCH";
+    private static final String USER_SWITCHER_MESSAGE_TEST_ID = "USER_SWITCHER_MESSAGE";
+    private static final String ENABLE_LOGOUT_TEST_ID = "ENABLE_LOGOUT";
+    private static final String COMP_TEST_ID = "COMP_UI";
+    private static final String MANAGED_USER_TEST_ID = "MANAGED_USER_UI";
     private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
 
     @Override
@@ -86,6 +88,9 @@
             DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
                     Context.DEVICE_POLICY_SERVICE);
             if (dpm.isDeviceOwnerApp(getPackageName())) {
+                // Set DISALLOW_ADD_USER on behalf of ManagedProvisioning.
+                dpm.addUserRestriction(DeviceAdminTestReceiver.getReceiverComponentName(),
+                        UserManager.DISALLOW_ADD_USER);
                 TestResult.setPassedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
                         null, null);
             } else {
@@ -171,8 +176,8 @@
                     new ButtonInfo[] {
                             new ButtonInfo(
                                     R.string.device_owner_user_restriction_set,
-                                    createSetUserRestrictionIntent(
-                                            UserManager.DISALLOW_CONFIG_WIFI)),
+                                    CommandReceiverActivity.createSetUserRestrictionIntent(
+                                            UserManager.DISALLOW_CONFIG_WIFI, true)),
                             new ButtonInfo(
                                     R.string.device_owner_settings_go,
                                     new Intent(Settings.ACTION_WIFI_SETTINGS))}));
@@ -185,8 +190,8 @@
                 new ButtonInfo[] {
                         new ButtonInfo(
                                 R.string.device_owner_user_vpn_restriction_set,
-                                createSetUserRestrictionIntent(
-                                        UserManager.DISALLOW_CONFIG_VPN)),
+                                CommandReceiverActivity.createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_CONFIG_VPN, true)),
                         new ButtonInfo(
                                 R.string.device_owner_settings_go,
                                 new Intent(Settings.ACTION_VPN_SETTINGS)),
@@ -202,8 +207,8 @@
                     new ButtonInfo[] {
                             new ButtonInfo(
                                     R.string.device_owner_user_restriction_set,
-                                    createSetUserRestrictionIntent(
-                                            UserManager.DISALLOW_DATA_ROAMING)),
+                                    CommandReceiverActivity.createSetUserRestrictionIntent(
+                                            UserManager.DISALLOW_DATA_ROAMING, true)),
                             new ButtonInfo(
                                     R.string.device_owner_settings_go,
                                     new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
@@ -216,8 +221,8 @@
                 new ButtonInfo[] {
                         new ButtonInfo(
                                 R.string.device_owner_user_restriction_set,
-                                createSetUserRestrictionIntent(
-                                        UserManager.DISALLOW_FACTORY_RESET))}));
+                                CommandReceiverActivity.createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_FACTORY_RESET, true))}));
 
         // DISALLOW_CONFIG_BLUETOOTH
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
@@ -227,8 +232,8 @@
                     new ButtonInfo[] {
                             new ButtonInfo(
                                     R.string.device_owner_user_restriction_set,
-                                    createSetUserRestrictionIntent(
-                                            UserManager.DISALLOW_CONFIG_BLUETOOTH)),
+                                    CommandReceiverActivity.createSetUserRestrictionIntent(
+                                            UserManager.DISALLOW_CONFIG_BLUETOOTH, true)),
                             new ButtonInfo(
                                     R.string.device_owner_settings_go,
                                     new Intent(Settings.ACTION_BLUETOOTH_SETTINGS))}));
@@ -241,8 +246,8 @@
                 new ButtonInfo[] {
                         new ButtonInfo(
                                 R.string.device_owner_user_restriction_set,
-                                createSetUserRestrictionIntent(
-                                        UserManager.DISALLOW_USB_FILE_TRANSFER)),
+                                CommandReceiverActivity.createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_USB_FILE_TRANSFER, true)),
                 }));
 
         // DISABLE_STATUS_BAR_TEST
@@ -279,6 +284,14 @@
                                         CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
                                                 false))}));
 
+        // setLockTaskFeatures
+        final Intent lockTaskUiTestIntent = new Intent(this, LockTaskUiTestActivity.class);
+        lockTaskUiTestIntent.putExtra(LockTaskUiTestActivity.EXTRA_TEST_ID, LOCK_TASK_UI_TEST_ID);
+        adapter.add(createTestItem(this, LOCK_TASK_UI_TEST_ID,
+                R.string.device_owner_lock_task_ui_test,
+                lockTaskUiTestIntent));
+
+        // setUserIcon
         adapter.add(createInteractiveTestItem(this, SET_USER_ICON_TEST_ID,
                 R.string.device_owner_set_user_icon,
                 R.string.device_owner_set_user_icon_instruction,
@@ -317,6 +330,7 @@
                 R.string.enterprise_privacy_test,
                 enterprisePolicyTestIntent));
 
+        // COMP
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
             Intent compIntent = new Intent(this, CompTestActivity.class)
                     .putExtra(PolicyTransparencyTestActivity.EXTRA_TEST_ID, COMP_TEST_ID);
@@ -325,6 +339,55 @@
                     compIntent));
         }
 
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)
+                && UserManager.supportsMultipleUsers()) {
+            // Managed user
+            adapter.add(createInteractiveTestItem(this, MANAGED_USER_TEST_ID,
+                    R.string.managed_user_test,
+                    R.string.managed_user_positive_tests_instructions,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    createCreateManagedUserIntent())}));
+
+            // User switcher message
+            adapter.add(createInteractiveTestItem(this, USER_SWITCHER_MESSAGE_TEST_ID,
+                    R.string.device_owner_user_switcher_message,
+                    R.string.device_owner_user_switcher_message_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_with_user_switcher_message,
+                                    createWithUserSwitcherMessageIntent()),
+                            new ButtonInfo(
+                                    R.string.device_owner_without_user_switcher_message,
+                                    createWithoutUserSwitcherMessageIntent())}));
+
+            // Enable logout
+            adapter.add(createInteractiveTestItem(this, ENABLE_LOGOUT_TEST_ID,
+                    R.string.device_owner_enable_logout,
+                    R.string.device_owner_enable_logout_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    createEnableLogoutIntent())}));
+
+            // DISALLOW_USER_SWITCH
+            adapter.add(createInteractiveTestItem(this, DISALLOW_USER_SWITCH_TEST_ID,
+                    R.string.device_owner_disallow_user_switch,
+                    R.string.device_owner_disallow_user_switch_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_disallow_user_switch_create_user,
+                                    createCreateManagedUserWithoutSetupIntent()),
+                            new ButtonInfo(
+                                    R.string.device_owner_user_restriction_set,
+                                    CommandReceiverActivity.createSetUserRestrictionIntent(
+                                            UserManager.DISALLOW_USER_SWITCH, true)),
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    new Intent(Settings.ACTION_SETTINGS))}));
+        }
+
         // Network logging UI
         adapter.add(createInteractiveTestItem(this, NETWORK_LOGGING_UI_TEST_ID,
                 R.string.device_owner_network_logging_ui,
@@ -365,14 +428,6 @@
                 .putExtra(CommandReceiverActivity.EXTRA_ENFORCED, value);
     }
 
-    private Intent createSetUserRestrictionIntent(String restriction) {
-        return new Intent(this, CommandReceiverActivity.class)
-                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
-                        CommandReceiverActivity.COMMAND_SET_USER_RESTRICTION)
-                .putExtra(CommandReceiverActivity.EXTRA_USER_RESTRICTION, restriction)
-                .putExtra(CommandReceiverActivity.EXTRA_ENFORCED, true);
-    }
-
     private Intent createSetUserIconIntent() {
         return new Intent(this, CommandReceiverActivity.class)
                 .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
@@ -391,6 +446,36 @@
                         CommandReceiverActivity.COMMAND_DISABLE_NETWORK_LOGGING);
     }
 
+    private Intent createCreateManagedUserIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_CREATE_MANAGED_USER);
+    }
+
+    private Intent createWithUserSwitcherMessageIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_WITH_USER_SWITCHER_MESSAGE);
+    }
+
+    private Intent createWithoutUserSwitcherMessageIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_WITHOUT_USER_SWITCHER_MESSAGE);
+    }
+
+    private Intent createEnableLogoutIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_ENABLE_LOGOUT);
+    }
+
+    private Intent createCreateManagedUserWithoutSetupIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_CREATE_MANAGED_USER_WITHOUT_SETUP);
+    }
+
     private boolean isStatusBarEnabled() {
       // Watches don't support the status bar so this is an ok proxy, but this is not the most
       // general test for that. TODO: add a test API to do a real check for status bar support.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index 71c4421..3a16297 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -269,19 +269,18 @@
                                             CommandReceiverActivity.
                                             COMMAND_REMOVE_MANAGED_PROFILE))}));
         }
-        // Disabled for API 26 due to b/63696536.
-        // adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
-        //         R.string.enterprise_privacy_failed_password_wipe,
-        //         R.string.enterprise_privacy_failed_password_wipe_info,
-        //         new ButtonInfo[] {
-        //                 new ButtonInfo(R.string.enterprise_privacy_open_settings,
-        //                         new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
-        //                 new ButtonInfo(R.string.enterprise_privacy_set_limit,
-        //                         buildCommandIntent(CommandReceiverActivity
-        //                                 .COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
-        //                 new ButtonInfo(R.string.enterprise_privacy_finish,
-        //                         buildCommandIntent(CommandReceiverActivity
-        //                                 .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
+        adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
+                R.string.enterprise_privacy_failed_password_wipe,
+                R.string.enterprise_privacy_failed_password_wipe_info,
+                new ButtonInfo[] {
+                        new ButtonInfo(R.string.enterprise_privacy_open_settings,
+                                new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+                        new ButtonInfo(R.string.enterprise_privacy_set_limit,
+                                buildCommandIntent(CommandReceiverActivity
+                                        .COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
+                        new ButtonInfo(R.string.enterprise_privacy_finish,
+                                buildCommandIntent(CommandReceiverActivity
+                                        .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
         if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
             adapter.add(createInteractiveTestItem(this,
                     ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ICrossUserService.aidl b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ICrossUserService.aidl
new file mode 100644
index 0000000..8894745
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ICrossUserService.aidl
@@ -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 com.android.cts.verifier.managedprovisioning;
+
+import android.os.UserHandle;
+
+interface ICrossUserService {
+    void switchUser(in UserHandle userHandle);
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
new file mode 100644
index 0000000..a59261c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.keystore.cts.CertificateUtils.createCertificate;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import java.security.GeneralSecurityException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Activity to test KeyChain key generation. The following flows are tested: * Generating a key. *
+ * Installing a (self-signed) certificate associated with the key, visible to users. * Setting
+ * visibility of the certificate to not be visible to user.
+ *
+ * <p>After the key generation and certificate installation, it should be possible for a user to
+ * select the key from the certificate selection prompt when {@code KeyChain.choosePrivateKeyAlias}
+ * is called. The test then tests that the key is indeed usable for signing.
+ *
+ * <p>After the visibility is set to not-user-visible, the prompt is shown again, this time the
+ * testes is asked to verify no keys are selectable and cancel the dialog.
+ */
+public class KeyChainTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = "ByodKeyChainActivity";
+
+    public static final String ACTION_KEYCHAIN =
+            "com.android.cts.verifier.managedprovisioning.KEYCHAIN";
+
+    public static final String ALIAS = "cts-verifier-gen-rsa-1";
+    public static final String KEY_ALGORITHM = "RSA";
+
+    private DevicePolicyManager mDevicePolicyManager;
+    private AttestedKeyPair mAttestedKeyPair;
+    private X509Certificate mCert;
+    private TextView mLogView;
+    private TextView mInstructionsView;
+    private Button mSetupButton;
+    private Button mGoButton;
+
+    // Callback interface for when a key is generated.
+    static interface KeyGenerationListener {
+        void onKeyPairGenerated(AttestedKeyPair keyPair);
+    }
+
+    // Task for generating a key pair using {@code DevicePolicyManager.generateKeyPair}.
+    // The listener, if provided, will be invoked after the key has been generated successfully.
+    class GenerateKeyTask extends AsyncTask<KeyGenParameterSpec, Integer, AttestedKeyPair> {
+        KeyGenerationListener mListener;
+
+        public GenerateKeyTask(KeyGenerationListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        protected AttestedKeyPair doInBackground(KeyGenParameterSpec... specs) {
+            Log.i(TAG, "Generating key pair.");
+            try {
+                AttestedKeyPair kp =
+                        mDevicePolicyManager.generateKeyPair(
+                                DeviceAdminTestReceiver.getReceiverComponentName(),
+                                KEY_ALGORITHM,
+                                specs[0],
+                                0);
+                if (kp != null) {
+                    mLogView.setText("Key generated successfully.");
+                } else {
+                    mLogView.setText("Failed generating key.");
+                }
+                return kp;
+            } catch (SecurityException e) {
+                mLogView.setText("Security exception while generating key.");
+                Log.w(TAG, "Security exception", e);
+            }
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(AttestedKeyPair kp) {
+            super.onPostExecute(kp);
+            if (mListener != null && kp != null) {
+                mListener.onKeyPairGenerated(kp);
+            }
+        }
+    }
+
+    // Helper for generating and installing a self-signed certificate.
+    class CertificateInstaller implements KeyGenerationListener {
+        @Override
+        public void onKeyPairGenerated(AttestedKeyPair keyPair) {
+            mAttestedKeyPair = keyPair;
+            X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
+            X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
+            try {
+                mCert = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
+                boolean installResult = installCertificate(mCert, true);
+                // called from onPostExecute so safe to interact with the UI here.
+                if (installResult) {
+                    mLogView.setText("Test ready");
+                    mInstructionsView.setText(R.string.provisioning_byod_keychain_info_first_test);
+                    mGoButton.setEnabled(true);
+                } else {
+                    mLogView.setText("FAILED certificate installation.");
+                }
+            } catch (Exception e) {
+                Log.w(TAG, "Failed installing certificate", e);
+                mLogView.setText("Error generating a certificate.");
+            }
+        }
+    }
+
+    // Helper for calling {@code DevicePolicyManager.setKeyPairCertificate} with the user-visibility
+    // specified in the constructor. Returns true if the call was successful (and no exceptions
+    // were thrown).
+    protected boolean installCertificate(X509Certificate cert, boolean isUserVisible) {
+        try {
+            return mDevicePolicyManager.setKeyPairCertificate(
+                    DeviceAdminTestReceiver.getReceiverComponentName(),
+                    ALIAS,
+                    Arrays.asList(new X509Certificate[] {cert}),
+                    isUserVisible);
+        } catch (SecurityException e) {
+            logStatus("Security exception while installing cert.");
+            Log.w(TAG, "Security exception", e);
+        }
+        return false;
+    }
+
+    // Invokes choosePrivateKeyAlias.
+    void selectCertificate(KeyChainAliasCallback callback) {
+        String[] keyTypes = new String[] {KEY_ALGORITHM};
+        Principal[] issuers = new Principal[0];
+        KeyChain.choosePrivateKeyAlias(
+                KeyChainTestActivity.this, callback, keyTypes, issuers, null, null);
+    }
+
+    class TestPreparator implements View.OnClickListener {
+        @Override
+        public void onClick(View v) {
+            mLogView.setText("Starting key generation");
+            KeyGenParameterSpec spec =
+                    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)
+                            .build();
+            new GenerateKeyTask(new CertificateInstaller()).execute(spec);
+        }
+    }
+
+    class SelectCertificate implements View.OnClickListener, KeyChainAliasCallback {
+        @Override
+        public void onClick(View v) {
+            Log.i(TAG, "Selecting certificate");
+            mLogView.setText("Waiting for prompt");
+            selectCertificate(this);
+        }
+
+        @Override
+        public void alias(String alias) {
+            Log.i(TAG, "Got alias: " + alias);
+            if (alias == null) {
+                logStatus("FAILED (no alias)");
+                return;
+            } else if (!alias.equals(ALIAS)) {
+                logStatus("FAILED (wrong alias)");
+                return;
+            }
+            logStatus("Got right alias.");
+            try {
+                PrivateKey privateKey = KeyChain.getPrivateKey(KeyChainTestActivity.this, alias);
+                byte[] data = new String("hello").getBytes();
+                Signature sign = Signature.getInstance("SHA256withRSA");
+                sign.initSign(privateKey);
+                sign.update(data);
+                if (sign.sign() != null) {
+                    prepareSecondTest();
+                } else {
+                    logStatus("FAILED (cannot sign)");
+                }
+            } catch (GeneralSecurityException | KeyChainException | InterruptedException e) {
+                Log.w(TAG, "Failed using the key", e);
+                logStatus("FAILED (key unusable)");
+            }
+        }
+    }
+
+    class SelectCertificateExpectingNone implements View.OnClickListener, KeyChainAliasCallback {
+        @Override
+        public void onClick(View v) {
+            Log.i(TAG, "Selecting certificate");
+            mLogView.setText("Waiting for prompt");
+            selectCertificate(this);
+        }
+
+        @Override
+        public void alias(String alias) {
+            Log.i(TAG, "Got alias: " + alias);
+            if (alias != null) {
+                logStatus("FAILED: Should have no certificate.");
+            } else {
+                logStatus("PASSED (2/2)");
+                runOnUiThread(
+                        () -> {
+                            getPassButton().setEnabled(true);
+                        });
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.keychain_test);
+        setPassFailButtonClickListeners();
+        mDevicePolicyManager =
+                (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+        mLogView = (TextView) findViewById(R.id.provisioning_byod_keychain_test_log);
+        mLogView.setMovementMethod(new ScrollingMovementMethod());
+
+        mInstructionsView = (TextView) findViewById(R.id.provisioning_byod_keychain_instructions);
+
+        mSetupButton = (Button) findViewById(R.id.prepare_test_button);
+        mSetupButton.setOnClickListener(new TestPreparator());
+
+        mGoButton = (Button) findViewById(R.id.run_test_button);
+        mGoButton.setOnClickListener(new SelectCertificate());
+        mGoButton.setEnabled(false);
+
+        // Disable the pass button here, only enable it when the 2nd test passes.
+        getPassButton().setEnabled(false);
+    }
+
+    protected void prepareSecondTest() {
+        Runnable uiChanges;
+        if (installCertificate(mCert, false)) {
+            uiChanges =
+                    () -> {
+                        mLogView.setText("Second test ready.");
+                        mInstructionsView.setText(
+                                R.string.provisioning_byod_keychain_info_second_test);
+                        mGoButton.setText("Run 2nd test");
+                        mGoButton.setOnClickListener(new SelectCertificateExpectingNone());
+                    };
+        } else {
+            uiChanges =
+                    () -> {
+                        mLogView.setText("FAILED second test setup.");
+                        mGoButton.setEnabled(false);
+                    };
+        }
+
+        runOnUiThread(uiChanges);
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        try {
+            mDevicePolicyManager.removeKeyPair(
+                    DeviceAdminTestReceiver.getReceiverComponentName(), ALIAS);
+            Log.i(TAG, "Deleted alias " + ALIAS);
+        } catch (SecurityException e) {
+            Log.w(TAG, "Failed deleting alias", e);
+        }
+    }
+
+    private void logStatus(String status) {
+        runOnUiThread(
+                () -> {
+                    mLogView.setText(status);
+                });
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
new file mode 100644
index 0000000..5447536
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
@@ -0,0 +1,354 @@
+/*
+ * 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.managedprovisioning;
+
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
+
+import static com.android.cts.verifier.managedprovisioning.Utils.createInteractiveTestItem;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.DataSetObserver;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+import com.android.cts.verifier.TestResult;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)}.
+ */
+public class LockTaskUiTestActivity extends PassFailButtons.TestListActivity {
+
+    private static final String TAG = LockTaskUiTestActivity.class.getSimpleName();
+
+    public static final String EXTRA_TEST_ID =
+            "com.android.cts.verifier.managedprovisioning.extra.TEST_ID";
+
+    /** Broadcast action sent by {@link DeviceAdminTestReceiver} when LockTask starts. */
+    static final String ACTION_LOCK_TASK_STARTED =
+            "com.android.cts.verifier.managedprovisioning.action.LOCK_TASK_STARTED";
+    /** Broadcast action sent by {@link DeviceAdminTestReceiver} when LockTask stops. */
+    static final String ACTION_LOCK_TASK_STOPPED =
+            "com.android.cts.verifier.managedprovisioning.action.LOCK_TASK_STOPPED";
+
+    private static final ComponentName ADMIN_RECEIVER =
+            DeviceAdminTestReceiver.getReceiverComponentName();
+    private static final String TEST_PACKAGE_NAME = "com.android.cts.verifier";
+    private static final String ACTION_STOP_LOCK_TASK =
+            "com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK";
+
+    private static final String TEST_ID_DEFAULT = "lock-task-ui-default";
+    private static final String TEST_ID_SYSTEM_INFO = "lock-task-ui-system-info";
+    private static final String TEST_ID_NOTIFICATIONS = "lock-task-ui-notifications";
+    private static final String TEST_ID_HOME = "lock-task-ui-home";
+    private static final String TEST_ID_RECENTS = "lock-task-ui-recents";
+    private static final String TEST_ID_GLOBAL_ACTIONS = "lock-task-ui-global-actions";
+    private static final String TEST_ID_KEYGUARD = "lock-task-ui-keyguard";
+    private static final String TEST_ID_STOP_LOCK_TASK = "lock-task-ui-stop-lock-task";
+
+    private DevicePolicyManager mDpm;
+    private ActivityManager mAm;
+    private NotificationManager mNotifyMgr;
+
+    private LockTaskStateChangedReceiver mStateChangedReceiver;
+    private CountDownLatch mLockTaskStartedLatch;
+    private CountDownLatch mLockTaskStoppedLatch;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.device_owner_lock_task_ui);
+        setPassFailButtonClickListeners();
+
+        mDpm = getSystemService(DevicePolicyManager.class);
+        mAm = getSystemService(ActivityManager.class);
+        mNotifyMgr = getSystemService(NotificationManager.class);
+
+        final ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+        addTestsToAdapter(adapter);
+        adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                updatePassButton();
+            }
+        });
+        setTestListAdapter(adapter);
+
+        Button startLockTaskButton = findViewById(R.id.start_lock_task_button);
+        startLockTaskButton.setOnClickListener((view) -> startLockTaskMode());
+
+        if (ACTION_STOP_LOCK_TASK.equals(getIntent().getAction())) {
+            // This means we're started by the "stop LockTask mode" test activity (the last one in
+            // the list) in order to stop LockTask.
+            stopLockTaskMode();
+        }
+    }
+
+    private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
+        adapter.add(createInteractiveTestItem(this,
+                TEST_ID_DEFAULT,
+                R.string.device_owner_lock_task_ui_default_test,
+                R.string.device_owner_lock_task_ui_default_test_info,
+                new ButtonInfo[]{}));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_SYSTEM_INFO,
+                LOCK_TASK_FEATURE_SYSTEM_INFO,
+                R.string.device_owner_lock_task_ui_system_info_test,
+                R.string.device_owner_lock_task_ui_system_info_test_info));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_NOTIFICATIONS,
+                LOCK_TASK_FEATURE_NOTIFICATIONS,
+                R.string.device_owner_lock_task_ui_notifications_test,
+                R.string.device_owner_lock_task_ui_notifications_test_info));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_HOME,
+                LOCK_TASK_FEATURE_HOME,
+                R.string.device_owner_lock_task_ui_home_test,
+                R.string.device_owner_lock_task_ui_home_test_info));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_RECENTS,
+                LOCK_TASK_FEATURE_OVERVIEW,
+                R.string.device_owner_lock_task_ui_recents_test,
+                R.string.device_owner_lock_task_ui_recents_test_info));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_GLOBAL_ACTIONS,
+                LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                R.string.device_owner_lock_task_ui_global_actions_test,
+                R.string.device_owner_lock_task_ui_global_actions_test_info));
+
+        adapter.add(createSetLockTaskFeaturesTest(
+                TEST_ID_KEYGUARD,
+                LOCK_TASK_FEATURE_KEYGUARD,
+                R.string.device_owner_lock_task_ui_keyguard_test,
+                R.string.device_owner_lock_task_ui_keyguard_test_info));
+
+        final Intent stopLockTaskIntent = new Intent(this, LockTaskUiTestActivity.class);
+        stopLockTaskIntent.setAction(ACTION_STOP_LOCK_TASK);
+        adapter.add(createInteractiveTestItem(this,
+                TEST_ID_STOP_LOCK_TASK,
+                R.string.device_owner_lock_task_ui_stop_lock_task_test,
+                R.string.device_owner_lock_task_ui_stop_lock_task_test_info,
+                new ButtonInfo(
+                        R.string.device_owner_lock_task_ui_stop_lock_task_test,
+                        stopLockTaskIntent
+                )));
+    }
+
+    /** Receives LockTask start/stop callbacks forwarded by {@link DeviceAdminTestReceiver}. */
+    private final class LockTaskStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (action) {
+                case ACTION_LOCK_TASK_STARTED:
+                    if (mLockTaskStartedLatch != null) {
+                        mLockTaskStartedLatch.countDown();
+                    }
+                    break;
+                case ACTION_LOCK_TASK_STOPPED:
+                    if (mLockTaskStoppedLatch != null) {
+                        mLockTaskStoppedLatch.countDown();
+                    }
+                    break;
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mStateChangedReceiver = new LockTaskStateChangedReceiver();
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_LOCK_TASK_STARTED);
+        filter.addAction(ACTION_LOCK_TASK_STOPPED);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mStateChangedReceiver, filter);
+    }
+
+    @Override
+    protected void onPause() {
+        if (mStateChangedReceiver != null) {
+            LocalBroadcastManager.getInstance(this).unregisterReceiver(mStateChangedReceiver);
+            mStateChangedReceiver = null;
+        }
+        super.onPause();
+    }
+
+    /**
+     * Starts LockTask mode and waits for callback from {@link DeviceAdminTestReceiver} to confirm
+     * LockTask has started successfully. If the callback isn't received, the entire test will be
+     * marked as failed.
+     *
+     * @see LockTaskStateChangedReceiver
+     */
+    private void startLockTaskMode() {
+        if (mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED) {
+            return;
+        }
+
+        mLockTaskStartedLatch = new CountDownLatch(1);
+        try {
+            mDpm.setLockTaskPackages(ADMIN_RECEIVER, new String[] {TEST_PACKAGE_NAME});
+            mDpm.setLockTaskFeatures(ADMIN_RECEIVER, LOCK_TASK_FEATURE_NONE);
+            startLockTask();
+
+            new CheckLockTaskStateTask() {
+                @Override
+                protected void onPostExecute(Boolean success) {
+                    if (success) {
+                        issueTestNotification();
+                    } else {
+                        notifyFailure(getTestId(), "Failed to start LockTask mode");
+                    }
+                }
+            }.execute(mLockTaskStartedLatch);
+        } catch (SecurityException e) {
+            Log.e(TAG, e.getMessage(), e);
+            Toast.makeText(this, "Failed to run test. Did you set up device owner correctly?",
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * Stops LockTask mode and waits for callback from {@link DeviceAdminTestReceiver} to confirm
+     * LockTask has stopped successfully. If the callback isn't received, the "Stop LockTask mode"
+     * test case will be marked as failed.
+     *
+     * Note that we {@link #finish()} this activity here, since it's started by the "Stop LockTask
+     * mode" test activity, and shouldn't be exposed to the tester once its job is done.
+     *
+     * @see LockTaskStateChangedReceiver
+     */
+    private void stopLockTaskMode() {
+        if (mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_NONE) {
+            finish();
+            return;
+        }
+
+        mLockTaskStoppedLatch = new CountDownLatch(1);
+        try {
+            stopLockTask();
+
+            new CheckLockTaskStateTask() {
+                @Override
+                protected void onPostExecute(Boolean success) {
+                    if (!success) {
+                        notifyFailure(TEST_ID_STOP_LOCK_TASK, "Failed to stop LockTask mode");
+                    }
+                    cancelTestNotification();
+                    mDpm.setLockTaskFeatures(ADMIN_RECEIVER, LOCK_TASK_FEATURE_NONE);
+                    mDpm.setLockTaskPackages(ADMIN_RECEIVER, new String[] {});
+                    LockTaskUiTestActivity.this.finish();
+                }
+            }.execute(mLockTaskStoppedLatch);
+        } catch (SecurityException e) {
+            Log.e(TAG, e.getMessage(), e);
+            Toast.makeText(this, "Failed to finish test. Did you set up device owner correctly?",
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private abstract class CheckLockTaskStateTask extends AsyncTask<CountDownLatch, Void, Boolean> {
+        @Override
+        protected Boolean doInBackground(CountDownLatch... latches) {
+            if (latches.length > 0 && latches[0] != null) {
+                try {
+                    return latches[0].await(1, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    // Fall through
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected abstract void onPostExecute(Boolean success);
+    }
+
+    private void notifyFailure(String testId, String message) {
+        Log.e(TAG, message);
+        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+        TestResult.setFailedResult(this, testId, message);
+    }
+
+    private void issueTestNotification() {
+        String channelId = getTestId();
+        if (mNotifyMgr.getNotificationChannel(channelId) == null) {
+            NotificationChannel channel = new NotificationChannel(
+                    channelId, getTestId(), NotificationManager.IMPORTANCE_HIGH);
+            mNotifyMgr.createNotificationChannel(channel);
+        }
+
+        Notification note = new Notification.Builder(this, channelId)
+                .setContentTitle(getString(R.string.device_owner_lock_task_ui_test))
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setOngoing(true)
+                .build();
+
+        mNotifyMgr.notify(0, note);
+    }
+
+    private void cancelTestNotification() {
+        mNotifyMgr.cancelAll();
+    }
+
+    private TestListItem createSetLockTaskFeaturesTest(String testId, int featureFlags,
+            int titleResId, int detailResId) {
+        final Intent commandIntent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
+        commandIntent.putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                CommandReceiverActivity.COMMAND_SET_LOCK_TASK_FEATURES);
+        commandIntent.putExtra(CommandReceiverActivity.EXTRA_VALUE, featureFlags);
+
+        return createInteractiveTestItem(this, testId, titleResId, detailResId,
+                new ButtonInfo(titleResId, commandIntent));
+    }
+
+    @Override
+    public String getTestId() {
+        return getIntent().getStringExtra(EXTRA_TEST_ID);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
new file mode 100644
index 0000000..1ce0807
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
@@ -0,0 +1,169 @@
+/*
+ * 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.managedprovisioning;
+
+import static com.android.cts.verifier.managedprovisioning.Utils.createInteractiveTestItem;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+import com.android.cts.verifier.TestResult;
+
+/**
+ * Activity that lists all positive managed user tests.
+ */
+public class ManagedUserPositiveTestActivity extends PassFailButtons.TestListActivity {
+    private static final String TAG = "ManagedUserPositiveTestActivity";
+
+    private static final String ACTION_CHECK_AFFILIATED_PROFILE_OWNER =
+            "com.android.cts.verifier.managedprovisioning.action.CHECK_AFFILIATED_PROFILE_OWNER";
+    static final String EXTRA_TEST_ID = "extra-test-id";
+
+    private static final String CHECK_AFFILIATED_PROFILE_OWNER_TEST_ID =
+            "CHECK_AFFILIATED_PROFILE_OWNER";
+    private static final String DEVICE_ADMIN_SETTINGS_ID = "DEVICE_ADMIN_SETTINGS";
+    private static final String DISABLE_STATUS_BAR_TEST_ID = "DISABLE_STATUS_BAR";
+    private static final String DISABLE_KEYGUARD_TEST_ID = "DISABLE_KEYGUARD";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (ACTION_CHECK_AFFILIATED_PROFILE_OWNER.equals(getIntent().getAction())) {
+            DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+            if (dpm.isProfileOwnerApp(getPackageName()) && dpm.isAffiliatedUser()) {
+                TestResult.setPassedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+                        null, null);
+            } else {
+                TestResult.setFailedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+                        getString(R.string.managed_user_incorrect_managed_user), null);
+            }
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.positive_managed_user);
+        setInfoResources(R.string.managed_user_positive_tests,
+                R.string.managed_user_positive_tests_info, 0);
+        setPassFailButtonClickListeners();
+
+        final ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+        adapter.add(TestListItem.newCategory(this, R.string.managed_user_positive_category));
+
+        addTestsToAdapter(adapter);
+
+        adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                updatePassButton();
+            }
+        });
+
+        setTestListAdapter(adapter);
+    }
+
+    @Override
+    public void finish() {
+        // If this activity was started for checking profile owner status, then no need to do any
+        // tear down.
+        if (!ACTION_CHECK_AFFILIATED_PROFILE_OWNER.equals(getIntent().getAction())) {
+            // Pass and fail buttons are known to call finish() when clicked,
+            // and this is when we want to remove the managed owner.
+            DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+            dpm.logoutUser(DeviceAdminTestReceiver.getReceiverComponentName());
+        }
+        super.finish();
+    }
+
+    private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
+        adapter.add(createTestItem(this, CHECK_AFFILIATED_PROFILE_OWNER_TEST_ID,
+                R.string.managed_user_check_managed_user_test,
+                new Intent(ACTION_CHECK_AFFILIATED_PROFILE_OWNER)
+                        .putExtra(EXTRA_TEST_ID, getIntent().getStringExtra(EXTRA_TEST_ID))));
+
+        // device admin settings
+        adapter.add(createInteractiveTestItem(this, DEVICE_ADMIN_SETTINGS_ID,
+                R.string.device_owner_device_admin_visible,
+                R.string.device_owner_device_admin_visible_info,
+                new ButtonInfo(
+                        R.string.device_owner_settings_go,
+                        new Intent(Settings.ACTION_SECURITY_SETTINGS))));
+
+        // DISABLE_STATUS_BAR_TEST
+        if (isStatusBarEnabled()) {
+            adapter.add(createInteractiveTestItem(this, DISABLE_STATUS_BAR_TEST_ID,
+                    R.string.device_owner_disable_statusbar_test,
+                    R.string.device_owner_disable_statusbar_test_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_disable_statusbar_button,
+                                    createManagedUserIntentWithBooleanParameter(
+                                            CommandReceiverActivity.COMMAND_SET_STATUSBAR_DISABLED,
+                                            true)),
+                            new ButtonInfo(
+                                    R.string.device_owner_reenable_statusbar_button,
+                                    createManagedUserIntentWithBooleanParameter(
+                                            CommandReceiverActivity.COMMAND_SET_STATUSBAR_DISABLED,
+                                            false))}));
+        }
+
+        // setKeyguardDisabled
+        adapter.add(createInteractiveTestItem(this, DISABLE_KEYGUARD_TEST_ID,
+                R.string.device_owner_disable_keyguard_test,
+                R.string.device_owner_disable_keyguard_test_info,
+                new ButtonInfo[]{
+                        new ButtonInfo(
+                                R.string.device_owner_disable_keyguard_button,
+                                createManagedUserIntentWithBooleanParameter(
+                                        CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
+                                        true)),
+                        new ButtonInfo(
+                                R.string.device_owner_reenable_keyguard_button,
+                                createManagedUserIntentWithBooleanParameter(
+                                        CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
+                                        false))}));
+    }
+
+
+    static TestListItem createTestItem(Activity activity, String id, int titleRes,
+            Intent intent) {
+        intent.putExtra(EXTRA_TEST_ID, id);
+        return TestListItem.newTest(activity, titleRes, id, intent, null);
+    }
+
+    private Intent createManagedUserIntentWithBooleanParameter(String command, boolean value) {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND, command)
+                .putExtra(CommandReceiverActivity.EXTRA_ENFORCED, value);
+    }
+
+    private boolean isStatusBarEnabled() {
+        // Watches don't support the status bar so this is an ok proxy, but this is not the most
+        // general test for that. TODO: add a test API to do a real check for status bar support.
+        return !getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NfcTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NfcTestActivity.java
index e767f7f..ef3ae2a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NfcTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NfcTestActivity.java
@@ -22,17 +22,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.UserManager;
-import android.support.v4.content.FileProvider;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -40,9 +34,7 @@
 
 import com.android.cts.verifier.R;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+import java.nio.charset.Charset;
 
 public class NfcTestActivity extends Activity {
     private static final String TAG = "NfcTestActivity";
@@ -51,17 +43,14 @@
 
     private static final String NFC_BEAM_PACKAGE = "com.android.nfc";
     private static final String NFC_BEAM_ACTIVITY = "com.android.nfc.BeamShareActivity";
-    private static final String SAMPLE_IMAGE_FILENAME = "image_to_share.jpg";
-    private static final String SAMPLE_IMAGE_CONTENT = "sample image";
     private static final String SAMPLE_TEXT = "sample text";
-    private static final int MARGIN = 80;
-    private static final int TEXT_SIZE = 200;
 
     private ComponentName mAdminReceiverComponent;
     private DevicePolicyManager mDevicePolicyManager;
     private UserManager mUserMangaer;
     private NfcAdapter mNfcAdapter;
     private boolean mDisallowByPolicy;
+    private boolean mAddUserRestrictionOnFinish;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -72,34 +61,22 @@
         mDevicePolicyManager = (DevicePolicyManager) getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
         mUserMangaer = (UserManager) getSystemService(Context.USER_SERVICE);
+        mAddUserRestrictionOnFinish = mUserMangaer.hasUserRestriction(
+                UserManager.DISALLOW_OUTGOING_BEAM);
         mDisallowByPolicy = getIntent().getBooleanExtra(EXTRA_DISALLOW_BY_POLICY, false);
         if (mDisallowByPolicy) {
             mDevicePolicyManager.addUserRestriction(mAdminReceiverComponent,
                     UserManager.DISALLOW_OUTGOING_BEAM);
+        } else {
+            mDevicePolicyManager.clearUserRestriction(mAdminReceiverComponent,
+                    UserManager.DISALLOW_OUTGOING_BEAM);
         }
 
         mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
+        mNfcAdapter.setNdefPushMessage(getTestMessage(), this);
 
         final Intent shareIntent = new Intent(Intent.ACTION_SEND);
-
-        // Sending a large amount of data requires hand-over to bluetooth, so determine here
-        // if supported by the device. If bluetooth is not supported, a simple text message
-        // will be transferred instead.
-        final boolean hasBluetooth =
-                getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
-
-        if (hasBluetooth) {
-            final Uri uri = createUriForImage(SAMPLE_IMAGE_FILENAME, SAMPLE_IMAGE_CONTENT);
-            Uri[] uris = new Uri[]{uri};
-
-            mNfcAdapter.setBeamPushUris(uris, this);
-
-            shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
-            shareIntent.setType("image/jpg");
-            shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        } else {
-            shareIntent.putExtra(Intent.EXTRA_TEXT, SAMPLE_TEXT);
-        }
+        shareIntent.putExtra(Intent.EXTRA_TEXT, SAMPLE_TEXT);
 
         findViewById(R.id.manual_beam_button).setOnClickListener(new OnClickListener() {
             @Override
@@ -127,47 +104,23 @@
 
     @Override
     public void finish() {
-        if (mUserMangaer.hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM)) {
+        if (mAddUserRestrictionOnFinish) {
+            mDevicePolicyManager.addUserRestriction(mAdminReceiverComponent,
+                    UserManager.DISALLOW_OUTGOING_BEAM);
+        } else {
             mDevicePolicyManager.clearUserRestriction(mAdminReceiverComponent,
                     UserManager.DISALLOW_OUTGOING_BEAM);
         }
         super.finish();
     }
 
-    /**
-     * Creates a Bitmap image that contains red on white text with a specified margin.
-     * @param text Text to be displayed in the image.
-     * @return A Bitmap image with the above specification.
-     */
-    private Bitmap createSampleImage(String text) {
-        Paint paint = new Paint();
-        paint.setStyle(Paint.Style.FILL);
-        paint.setTextSize(TEXT_SIZE);
-        Rect rect = new Rect();
-        paint.getTextBounds(text, 0, text.length(), rect);
-        int w = 2 * MARGIN + rect.right - rect.left;
-        int h = 2 * MARGIN + rect.bottom - rect.top;
-        Bitmap dest = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas();
-        canvas.setBitmap(dest);
-        paint.setColor(Color.WHITE);
-        canvas.drawPaint(paint);
-        paint.setColor(Color.RED);
-        canvas.drawText(text, MARGIN - rect.left, MARGIN - rect.top, paint);
-        return dest;
-    }
-
-    private Uri createUriForImage(String name, String text) {
-        final File file = new File(getFilesDir() + File.separator + "images"
-                + File.separator + name);
-        file.getParentFile().mkdirs(); //if the folder doesn't exists it is created
-        try {
-            createSampleImage(text).compress(Bitmap.CompressFormat.JPEG, 100,
-                    new FileOutputStream(file));
-        } catch (FileNotFoundException e) {
-            return null;
-        }
-        return FileProvider.getUriForFile(this,
-                "com.android.cts.verifier.managedprovisioning.fileprovider", file);
+    private NdefMessage getTestMessage() {
+        byte[] mimeBytes = "application/com.android.cts.verifier.managedprovisioning"
+                .getBytes(Charset.forName("US-ASCII"));
+        byte[] id = new byte[] {1, 3, 3, 7};
+        byte[] payload = SAMPLE_TEXT.getBytes(Charset.forName("US-ASCII"));
+        return new NdefMessage(new NdefRecord[] {
+                new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, id, payload)
+        });
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
index 0f57b87..e3d6edb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
@@ -21,7 +21,6 @@
 import android.content.Intent;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
-import android.os.UserManager;
 import android.util.ArrayMap;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.View;
@@ -235,15 +234,14 @@
 
     @Override
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-        final Intent intent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
+        final Intent intent;
         if (TEST_CHECK_USER_RESTRICTION.equals(mTest)) {
             final String userRestriction = getIntent().getStringExtra(
                     CommandReceiverActivity.EXTRA_USER_RESTRICTION);
-            intent.putExtra(CommandReceiverActivity.EXTRA_COMMAND,
-                    CommandReceiverActivity.COMMAND_SET_USER_RESTRICTION);
-            intent.putExtra(CommandReceiverActivity.EXTRA_USER_RESTRICTION, userRestriction);
-            intent.putExtra(CommandReceiverActivity.EXTRA_ENFORCED, isChecked);
+            intent = CommandReceiverActivity.createSetUserRestrictionIntent(
+                    userRestriction, isChecked);
         } else {
+            intent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
             final PolicyTestItem testItem = POLICY_TEST_ITEMS.get(mTest);
             intent.putExtra(CommandReceiverActivity.EXTRA_COMMAND, testItem.command);
             intent.putExtra(CommandReceiverActivity.EXTRA_ENFORCED, isChecked);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/TurnOffWorkActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/TurnOffWorkActivity.java
index f855b56..c0a1626 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/TurnOffWorkActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/TurnOffWorkActivity.java
@@ -120,11 +120,6 @@
                 R.string.provisioning_byod_turn_off_work_notifications_instruction,
                 new Intent(ByodHelperActivity.ACTION_NOTIFICATION)));
 
-        adapter.add(new DialogTestListItem(this, R.string.provisioning_byod_turn_off_work_icon,
-                "BYOD_TurnOffWorkIcon",
-                R.string.provisioning_byod_turn_off_work_icon_instruction,
-                new Intent(Settings.ACTION_SETTINGS)));
-
         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             adapter.add(new DialogTestListItem(this, R.string.provisioning_byod_turn_off_work_launcher,
                     "BYOD_TurnOffWorkStartApps",
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 2c44030..466eab8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -49,7 +49,14 @@
         UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
         UserManager.DISALLOW_REMOVE_USER,
         UserManager.DISALLOW_SHARE_LOCATION,
-        UserManager.DISALLOW_UNINSTALL_APPS
+        UserManager.DISALLOW_UNINSTALL_APPS,
+        UserManager.DISALLOW_UNIFIED_PASSWORD,
+        UserManager.DISALLOW_CONFIG_DATE_TIME,
+        UserManager.DISALLOW_CONFIG_LOCATION,
+        UserManager.DISALLOW_AIRPLANE_MODE,
+        UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT,
+        UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+        UserManager.DISALLOW_AMBIENT_DISPLAY,
     };
 
     private static final ArrayMap<String, UserRestrictionItem> USER_RESTRICTION_ITEMS;
@@ -73,7 +80,14 @@
             R.string.disallow_remove_managed_profile,
             R.string.disallow_remove_user,
             R.string.disallow_share_location,
-            R.string.disallow_uninstall_apps
+            R.string.disallow_uninstall_apps,
+            R.string.disallow_unified_challenge,
+            R.string.disallow_config_date_time,
+            R.string.disallow_config_location,
+            R.string.disallow_airplane_mode,
+            R.string.disallow_config_screen_timeout,
+            R.string.disallow_config_brightness,
+            R.string.disallow_ambient_display
         };
 
         final int[] restrictionActions = new int[] {
@@ -95,7 +109,14 @@
             R.string.disallow_remove_managed_profile_action,
             R.string.disallow_remove_user_action,
             R.string.disallow_share_location_action,
-            R.string.disallow_uninstall_apps_action
+            R.string.disallow_uninstall_apps_action,
+            R.string.disallow_unified_challenge_action,
+            R.string.disallow_config_date_time_action,
+            R.string.disallow_config_location_action,
+            R.string.disallow_airplane_mode_action,
+            R.string.disallow_config_screen_timeout_action,
+            R.string.disallow_config_brightness_action,
+            R.string.disallow_ambient_display_action
         };
 
         final String[] settingsIntentActions = new String[] {
@@ -118,6 +139,13 @@
             Settings.ACTION_SETTINGS,
             Settings.ACTION_LOCATION_SOURCE_SETTINGS,
             Settings.ACTION_APPLICATION_SETTINGS,
+            Settings.ACTION_SECURITY_SETTINGS,
+            Settings.ACTION_DATE_SETTINGS,
+            Settings.ACTION_LOCATION_SOURCE_SETTINGS,
+            Settings.ACTION_AIRPLANE_MODE_SETTINGS,
+            Settings.ACTION_DISPLAY_SETTINGS,
+            Settings.ACTION_DISPLAY_SETTINGS,
+            Settings.ACTION_DISPLAY_SETTINGS,
         };
 
         if (RESTRICTION_IDS_FOR_POLICY_TRANSPARENCY.length != restrictionLabels.length
@@ -143,6 +171,8 @@
         ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_UNINSTALL_APPS);
         ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_MODIFY_ACCOUNTS);
         ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_SHARE_LOCATION);
+        ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_UNIFIED_PASSWORD);
+        ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_CONFIG_LOCATION);
     }
 
     public static String getRestrictionLabel(Context context, String restriction) {
@@ -168,7 +198,8 @@
             ArrayList<String> result = new ArrayList<String>();
             // They are all valid except for DISALLOW_REMOVE_MANAGED_PROFILE
             for (String st : RESTRICTION_IDS_FOR_POLICY_TRANSPARENCY) {
-                if (!st.equals(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)) {
+                if (!st.equals(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)
+                        && !st.equals(UserManager.DISALLOW_UNIFIED_PASSWORD)) {
                     result.add(st);
                 }
             }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
index 199a86f..39bca50 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -28,7 +28,9 @@
 import android.content.Context;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
+import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.Build;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -37,7 +39,9 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+
 import com.android.cts.verifier.R;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -52,6 +56,8 @@
 
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
     private static final String NOTIFICATION_CHANNEL_ID_NOISY = TAG + "/noisy";
+    private static final String NOTIFICATION_CHANNEL_ID_MEDIA = TAG + "/media";
+    private static final String NOTIFICATION_CHANNEL_ID_GAME = TAG + "/game";
     private static final String ALICE = "Alice";
     private static final String ALICE_PHONE = "+16175551212";
     private static final String ALICE_EMAIL = "alice@_foo._bar";
@@ -70,7 +76,6 @@
     private static final int SEND_C = 0x4;
     private static final int SEND_ALL = SEND_A | SEND_B | SEND_C;
 
-
     private Uri mAliceUri;
     private Uri mBobUri;
     private Uri mCharlieUri;
@@ -89,6 +94,7 @@
 
     @Override
     protected List<InteractiveTestCase> createTestItems() {
+
         List<InteractiveTestCase> tests = new ArrayList<>(17);
         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
         if (am.isLowRamDevice()) {
@@ -98,9 +104,18 @@
             tests.add(new IsEnabledTest());
             tests.add(new ServiceStartedTest());
             tests.add(new InsertContactsTest());
-            tests.add(new NoneInterceptsAllTest());
-            tests.add(new PriorityInterceptsSomeTest());
-            tests.add(new AllInterceptsNothingTest());
+            tests.add(new NoneInterceptsAllMessagesTest());
+            tests.add(new NoneInterceptsAlarmEventReminderCategoriesTest());
+            tests.add(new PriorityInterceptsSomeMessagesTest());
+
+            if (getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+                // Tests targeting P and above:
+                tests.add(new PriorityInterceptsAlarmsTest());
+                tests.add(new PriorityInterceptsMediaSystemOtherTest());
+            }
+
+            tests.add(new AllInterceptsNothingMessagesTest());
+            tests.add(new AllInterceptsNothingDiffCategoriesTest());
             tests.add(new DefaultOrderTest());
             tests.add(new PriorityOrderTest());
             tests.add(new InterruptionOrderTest());
@@ -121,11 +136,25 @@
                 NOTIFICATION_CHANNEL_ID_NOISY, NotificationManager.IMPORTANCE_HIGH);
         noisyChannel.enableVibration(true);
         mNm.createNotificationChannel(noisyChannel);
+        NotificationChannel mediaChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA,
+                NOTIFICATION_CHANNEL_ID_MEDIA, NotificationManager.IMPORTANCE_HIGH);
+        AudioAttributes.Builder aa = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA);
+        mediaChannel.setSound(null, aa.build());
+        mNm.createNotificationChannel(mediaChannel);
+        NotificationChannel gameChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_GAME,
+                NOTIFICATION_CHANNEL_ID_GAME, NotificationManager.IMPORTANCE_HIGH);
+        AudioAttributes.Builder aa2 = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_GAME);
+        gameChannel.setSound(null, aa2.build());
+        mNm.createNotificationChannel(gameChannel);
     }
 
     private void deleteChannels() {
         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY);
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA);
+        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_GAME);
     }
 
     // Tests
@@ -216,7 +245,7 @@
         }
     }
 
-    protected class NoneInterceptsAllTest extends InteractiveTestCase {
+    protected class NoneInterceptsAllMessagesTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
             return createAutoItem(parent, R.string.attention_all_are_filtered);
@@ -244,7 +273,7 @@
                 try {
                     String tag = payload.getString(JSON_TAG);
                     boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                    Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
                     if (found.contains(tag)) {
                         // multiple entries for same notification!
                         pass = false;
@@ -274,19 +303,19 @@
             deleteChannels();
             MockListener.getInstance().resetData();
         }
-
     }
 
-    protected class AllInterceptsNothingTest extends InteractiveTestCase {
+    protected class NoneInterceptsAlarmEventReminderCategoriesTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
-            return createAutoItem(parent, R.string.attention_none_are_filtered);
+            return createAutoItem(parent, R.string.attention_all_are_filtered);
         }
 
         @Override
         protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
             createChannels();
-            sendNotifications(MODE_URI, false, false);
+            sendEventAlarmReminderNotifications(SEND_ALL);
             status = READY;
         }
 
@@ -304,7 +333,66 @@
                 try {
                     String tag = payload.getString(JSON_TAG);
                     boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                    Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
+                    if (found.contains(tag)) {
+                        // multiple entries for same notification!
+                        pass = false;
+                    } else if (ALICE.equals(tag)) {
+                        found.add(ALICE);
+                        pass &= !zen;
+                    } else if (BOB.equals(tag)) {
+                        found.add(BOB);
+                        pass &= !zen;
+                    } else if (CHARLIE.equals(tag)) {
+                        found.add(CHARLIE);
+                        pass &= !zen;
+                    }
+                } catch (JSONException e) {
+                    pass = false;
+                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                }
+            }
+            pass &= found.size() == 3;
+            status = pass ? PASS : FAIL;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            MockListener.getInstance().resetData();
+        }
+    }
+
+    protected class AllInterceptsNothingMessagesTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_none_are_filtered_messages);
+        }
+
+        @Override
+        protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            createChannels();
+            sendNotifications(MODE_URI, false, false); // different messages
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+            Set<String> found = new HashSet<String>();
+            if (result.size() == 0) {
+                status = FAIL;
+                return;
+            }
+            boolean pass = true;
+            for (JSONObject payload : result) {
+                try {
+                    String tag = payload.getString(JSON_TAG);
+                    boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                    Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
                     if (found.contains(tag)) {
                         // multiple entries for same notification!
                         pass = false;
@@ -335,18 +423,77 @@
         }
     }
 
-    protected class PriorityInterceptsSomeTest extends InteractiveTestCase {
+    protected class AllInterceptsNothingDiffCategoriesTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
-            return createAutoItem(parent, R.string.attention_some_are_filtered);
+            return createAutoItem(parent, R.string.attention_none_are_filtered_diff_categories);
+        }
+
+        @Override
+        protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            createChannels();
+            sendEventAlarmReminderNotifications(SEND_ALL);
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+            Set<String> found = new HashSet<String>();
+            if (result.size() == 0) {
+                status = FAIL;
+                return;
+            }
+            boolean pass = true;
+            for (JSONObject payload : result) {
+                try {
+                    String tag = payload.getString(JSON_TAG);
+                    boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                    Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
+                    if (found.contains(tag)) {
+                        // multiple entries for same notification!
+                        pass = false;
+                    } else if (ALICE.equals(tag)) {
+                        found.add(ALICE);
+                        pass &= zen;
+                    } else if (BOB.equals(tag)) {
+                        found.add(BOB);
+                        pass &= zen;
+                    } else if (CHARLIE.equals(tag)) {
+                        found.add(CHARLIE);
+                        pass &= zen;
+                    }
+                } catch (JSONException e) {
+                    pass = false;
+                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                }
+            }
+            pass &= found.size() == 3;
+            status = pass ? PASS : FAIL;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            MockListener.getInstance().resetData();
+        }
+    }
+
+    protected class PriorityInterceptsSomeMessagesTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_some_are_filtered_messages);
         }
 
         @Override
         protected void setUp() {
             mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
             NotificationManager.Policy policy = mNm.getNotificationPolicy();
-            policy = new NotificationManager.Policy(policy.priorityCategories
-                    | NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES,
+            policy = new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES,
                     policy.priorityCallSenders,
                     NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
             mNm.setNotificationPolicy(policy);
@@ -369,7 +516,7 @@
                 try {
                     String tag = payload.getString(JSON_TAG);
                     boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                    Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
                     if (found.contains(tag)) {
                         // multiple entries for same notification!
                         pass = false;
@@ -388,7 +535,139 @@
                     Log.e(TAG, "failed to unpack data from mocklistener", e);
                 }
             }
-            pass &= found.size() == 3;
+            pass &= found.size() >= 3;
+            status = pass ? PASS : FAIL;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            MockListener.getInstance().resetData();
+        }
+    }
+
+    protected class PriorityInterceptsAlarmsTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_some_are_filtered_alarms);
+        }
+
+        @Override
+        protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            NotificationManager.Policy policy = mNm.getNotificationPolicy();
+            policy = new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS,
+                    policy.priorityCallSenders,
+                    policy.priorityMessageSenders);
+            mNm.setNotificationPolicy(policy);
+            createChannels();
+            // Event to Alice, Alarm to Bob, Reminder to Charlie:
+            sendEventAlarmReminderNotifications(SEND_ALL);
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+            Set<String> found = new HashSet<String>();
+            if (result.size() == 0) {
+                status = FAIL;
+                return;
+            }
+            boolean pass = true;
+            for (JSONObject payload : result) {
+                try {
+                    String tag = payload.getString(JSON_TAG);
+                    boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                    Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted");
+                    if (found.contains(tag)) {
+                        // multiple entries for same notification!
+                        pass = false;
+                    } else if (ALICE.equals(tag)) {
+                        found.add(ALICE);
+                        pass &= zenIntercepted; // Alice's event notif should be intercepted
+                    } else if (BOB.equals(tag)) {
+                        found.add(BOB);
+                        pass &= !zenIntercepted;   // Bob's alarm notif should not be intercepted
+                    } else if (CHARLIE.equals(tag)) {
+                        found.add(CHARLIE);
+                        pass &= zenIntercepted; // Charlie's reminder notif should be intercepted
+                    }
+                } catch (JSONException e) {
+                    pass = false;
+                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                }
+            }
+            pass &= found.size() >= 3;
+            status = pass ? PASS : FAIL;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            MockListener.getInstance().resetData();
+        }
+    }
+
+    protected class PriorityInterceptsMediaSystemOtherTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_some_are_filtered_media_system_other);
+        }
+
+        @Override
+        protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            NotificationManager.Policy policy = mNm.getNotificationPolicy();
+            policy = new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
+                    policy.priorityCallSenders,
+                    policy.priorityMessageSenders);
+            mNm.setNotificationPolicy(policy);
+            createChannels();
+            // Alarm to Alice, Other (Game) to Bob, Media to Charlie:
+            sendAlarmOtherMediaNotifications(SEND_ALL);
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+            Set<String> found = new HashSet<String>();
+            if (result.size() == 0) {
+                status = FAIL;
+                return;
+            }
+            boolean pass = true;
+            for (JSONObject payload : result) {
+                try {
+                    String tag = payload.getString(JSON_TAG);
+                    boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                    Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted");
+                    if (found.contains(tag)) {
+                        // multiple entries for same notification!
+                        pass = false;
+                    } else if (ALICE.equals(tag)) {
+                        found.add(ALICE);
+                        pass &= zenIntercepted;
+                    } else if (BOB.equals(tag)) {
+                        found.add(BOB);
+                        pass &= !zenIntercepted;
+                    } else if (CHARLIE.equals(tag)) {
+                        found.add(CHARLIE);
+                        pass &= !zenIntercepted;
+                    }
+                } catch (JSONException e) {
+                    pass = false;
+                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                }
+            }
+            pass &= found.size() >= 3;
             status = pass ? PASS : FAIL;
         }
 
@@ -410,6 +689,7 @@
 
         @Override
         protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             createChannels();
             sendNotifications(MODE_NONE, false, false);
             status = READY;
@@ -446,6 +726,7 @@
 
         @Override
         protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             createChannels();
             sendNotifications(MODE_NONE, true, false);
             status = READY;
@@ -484,6 +765,7 @@
 
         @Override
         protected void setUp() {
+            mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             delayTime = 15000;
             createChannels();
             // send B & C noisy with contact affinity
@@ -758,6 +1040,83 @@
         }
     }
 
+    private void sendEventAlarmReminderNotifications(int which) {
+        long when = System.currentTimeMillis() - 4000000L;
+        final String channelId = NOTIFICATION_CHANNEL_ID;
+
+        // Event notification to Alice
+        if ((which & SEND_A) != 0) {
+            Notification.Builder alice = new Notification.Builder(mContext, channelId)
+                    .setContentTitle(ALICE)
+                    .setContentText(ALICE)
+                    .setSmallIcon(R.drawable.ic_stat_alice)
+                    .setCategory(Notification.CATEGORY_EVENT)
+                    .setWhen(when);
+            mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
+        }
+
+        // Alarm notification to Bob
+        if ((which & SEND_B) != 0) {
+            Notification.Builder bob = new Notification.Builder(mContext, channelId)
+                    .setContentTitle(BOB)
+                    .setContentText(BOB)
+                    .setSmallIcon(R.drawable.ic_stat_bob)
+                    .setCategory(Notification.CATEGORY_ALARM)
+                    .setWhen(when);
+            mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
+        }
+
+        // Reminder notification to Charlie
+        if ((which & SEND_C) != 0) {
+            Notification.Builder charlie =
+                    new Notification.Builder(mContext, channelId)
+                            .setContentTitle(CHARLIE)
+                            .setContentText(CHARLIE)
+                            .setSmallIcon(R.drawable.ic_stat_charlie)
+                            .setCategory(Notification.CATEGORY_REMINDER)
+                            .setWhen(when);
+            mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
+        }
+    }
+
+    private void sendAlarmOtherMediaNotifications(int which) {
+        long when = System.currentTimeMillis() - 4000000L;
+        final String channelId = NOTIFICATION_CHANNEL_ID;
+
+        // Alarm notification to Alice
+        if ((which & SEND_A) != 0) {
+            Notification.Builder alice = new Notification.Builder(mContext, channelId)
+                    .setContentTitle(ALICE)
+                    .setContentText(ALICE)
+                    .setSmallIcon(R.drawable.ic_stat_alice)
+                    .setCategory(Notification.CATEGORY_ALARM)
+                    .setWhen(when);
+            mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
+        }
+
+        // "Other" notification to Bob
+        if ((which & SEND_B) != 0) {
+            Notification.Builder bob = new Notification.Builder(mContext,
+                    NOTIFICATION_CHANNEL_ID_GAME)
+                    .setContentTitle(BOB)
+                    .setContentText(BOB)
+                    .setSmallIcon(R.drawable.ic_stat_bob)
+                    .setWhen(when);
+            mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
+        }
+
+        // Media notification to Charlie
+        if ((which & SEND_C) != 0) {
+            Notification.Builder charlie =
+                    new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID_MEDIA)
+                            .setContentTitle(CHARLIE)
+                            .setContentText(CHARLIE)
+                            .setSmallIcon(R.drawable.ic_stat_charlie)
+                            .setWhen(when);
+            mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
+        }
+    }
+
     private void addPerson(int mode, Notification.Builder note,
             Uri uri, String phone, String email) {
         if (mode == MODE_URI && uri != null) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
new file mode 100644
index 0000000..084e907
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
@@ -0,0 +1,36 @@
+package com.android.cts.verifier.notifications;
+
+import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+public class BlockChangeReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        SharedPreferences prefs = context.getSharedPreferences(
+                NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = prefs.edit();
+        if (ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED.equals(intent.getAction())) {
+            String id = intent.getStringExtra(
+                    NotificationManager.EXTRA_NOTIFICATION_CHANNEL_GROUP_ID);
+            editor.putBoolean(id,
+                    intent.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false));
+        } else if (ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED.equals(intent.getAction())) {
+            String id = intent.getStringExtra(NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID);
+            editor.putBoolean(id,
+                    intent.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false));
+        } else if (ACTION_APP_BLOCK_STATE_CHANGED.equals(intent.getAction())) {
+            String id = context.getPackageName();
+            editor.putBoolean(id,
+                    intent.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false));
+        }
+        editor.commit();
+    }
+}
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 46b808b..d711dbf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
@@ -71,6 +71,8 @@
             tests.add(new SubscribeAutomaticZenRuleTest());
             tests.add(new DeleteAutomaticZenRuleTest());
             tests.add(new UnsubscribeAutomaticZenRuleTest());
+            tests.add(new RequestUnbindTest());
+            tests.add(new RequestBindTest());
             tests.add(new IsDisabledTest());
             tests.add(new ServiceStoppedTest());
         }
@@ -124,7 +126,7 @@
         @Override
         protected void test() {
             mNm.cancelAll();
-            Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
+            Intent settings = new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
             if (settings.resolveActivity(mPackageManager) == null) {
                 logFail("no settings activity");
                 status = FAIL;
@@ -142,6 +144,11 @@
             // wait for the service to start
             delay();
         }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+        }
     }
 
     protected class ServiceStartedTest extends InteractiveTestCase {
@@ -176,6 +183,75 @@
         }
     }
 
+    private class RequestUnbindTest extends InteractiveTestCase {
+        int mRetries = 5;
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_snooze);
+
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            if (status == READY) {
+                MockConditionProvider.getInstance().requestUnbind();
+                status = RETEST;
+            } else {
+                if (MockConditionProvider.getInstance() == null ||
+                        !MockConditionProvider.getInstance().isConnected()) {
+                    status = PASS;
+                } else {
+                    if (--mRetries > 0) {
+                        status = RETEST;
+                    } else {
+                        logFail();
+                        status = FAIL;
+                    }
+                }
+                next();
+            }
+        }
+    }
+
+    private class RequestBindTest extends InteractiveTestCase {
+        int mRetries = 5;
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_unsnooze);
+
+        }
+
+        @Override
+        protected void test() {
+            if (status == READY) {
+                MockConditionProvider.requestRebind(MockConditionProvider.COMPONENT_NAME);
+                status = RETEST;
+            } else {
+                if (MockConditionProvider.getInstance().isConnected()
+                        && MockConditionProvider.getInstance().isBound()) {
+                    status = PASS;
+                    next();
+                } else {
+                    if (--mRetries > 0) {
+                        status = RETEST;
+                        next();
+                    } else {
+                        logFail();
+                        status = FAIL;
+                    }
+                }
+            }
+        }
+    }
+
+
     private class CreateAutomaticZenRuleTest extends InteractiveTestCase {
         private String id = null;
 
@@ -557,6 +633,11 @@
             MockConditionProvider.resetData(mContext);
             delay();
         }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+        }
     }
 
     private class ServiceStoppedTest extends InteractiveTestCase {
@@ -611,21 +692,4 @@
     protected View createSettingsItem(ViewGroup parent, int messageId) {
         return createUserItem(parent, R.string.cp_start_settings, messageId);
     }
-
-    public void launchSettings() {
-        startActivity(new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
-    }
-
-    public void actionPressed(View v) {
-        Object tag = v.getTag();
-        if (tag instanceof Integer) {
-            int id = ((Integer) tag).intValue();
-            if (id == R.string.cp_start_settings) {
-                launchSettings();
-            } else if (id == R.string.attention_ready) {
-                mCurrentTest.status = READY;
-                next();
-            }
-        }
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 1433914..f1f08ff 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.notifications;
 
+import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -65,8 +67,6 @@
 
     // TODO remove these once b/10023397 is fixed
     public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
-    public static final String NOTIFICATION_LISTENER_SETTINGS =
-            "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
 
     protected InteractiveTestCase mCurrentTest;
     protected PackageManager mPackageManager;
@@ -139,6 +139,12 @@
             Log.e(TAG, "failed " + this.getClass().getSimpleName() +
                     ((message == null) ? "" : ": " + message), e);
         }
+
+        // If this test contains a button that launches another activity, override this
+        // method to provide the intent to launch.
+        protected Intent getIntent() {
+            return null;
+        }
     }
 
     protected abstract int getTitleResource();
@@ -361,16 +367,12 @@
 
     // UI callbacks
 
-    public void launchSettings() {
-        startActivity(new Intent(NOTIFICATION_LISTENER_SETTINGS));
-    }
-
     public void actionPressed(View v) {
         Object tag = v.getTag();
         if (tag instanceof Integer) {
             int id = ((Integer) tag).intValue();
-            if (id == R.string.nls_start_settings) {
-                launchSettings();
+            if (mCurrentTest != null && mCurrentTest.getIntent() != null) {
+                startActivity(mCurrentTest.getIntent());
             } else if (id == R.string.attention_ready) {
                 if (mCurrentTest != null) {
                     mCurrentTest.status = READY;
@@ -471,7 +473,7 @@
         @Override
         protected void test() {
             mNm.cancelAll();
-            Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
+            Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
             if (settings.resolveActivity(mPackageManager) == null) {
                 logFail("no settings activity");
                 status = FAIL;
@@ -487,10 +489,57 @@
             }
         }
 
+        @Override
         protected void tearDown() {
             // wait for the service to start
             delay();
         }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+        }
+    }
+
+    protected class CannotBeEnabledTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createNlsSettingsItem(parent, R.string.nls_cannot_enable_service);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            mNm.cancelAll();
+            Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+            if (settings.resolveActivity(mPackageManager) == null) {
+                logFail("no settings activity");
+                status = FAIL;
+            } else {
+                String listeners = Secure.getString(getContentResolver(),
+                        ENABLED_NOTIFICATION_LISTENERS);
+                if (listeners != null && listeners.contains(LISTENER_PATH)) {
+                    status = FAIL;
+                } else {
+                    status = PASS;
+                }
+                next();
+            }
+        }
+
+        protected void tearDown() {
+            // wait for the service to start
+            delay();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+        }
     }
 
     protected class ServiceStartedTest extends InteractiveTestCase {
@@ -511,41 +560,4 @@
             }
         }
     }
-
-    protected class CannotBeEnabledTest extends InteractiveTestCase {
-        @Override
-        protected View inflate(ViewGroup parent) {
-            return createNlsSettingsItem(parent, R.string.nls_cannot_enable_service);
-        }
-
-        @Override
-        boolean autoStart() {
-            return true;
-        }
-
-        @Override
-        protected void test() {
-            mNm.cancelAll();
-            Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
-            if (settings.resolveActivity(mPackageManager) == null) {
-                logFail("no settings activity");
-                status = FAIL;
-            } else {
-                String listeners = Secure.getString(getContentResolver(),
-                        ENABLED_NOTIFICATION_LISTENERS);
-                if (listeners != null && listeners.contains(LISTENER_PATH)) {
-                    status = FAIL;
-                } else {
-                    status = PASS;
-                }
-                next();
-            }
-        }
-
-        protected void tearDown() {
-            // wait for the service to start
-            delay();
-        }
-    }
-
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
index 489797b..f32460f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.AutomaticZenRule;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -35,7 +36,8 @@
 public class MockConditionProvider extends ConditionProviderService {
     static final String TAG = "MockConditionProvider";
 
-    static final String PACKAGE_NAME = "com.android.cts.verifier.notifications";
+    public static final ComponentName COMPONENT_NAME =
+            new ComponentName("com.android.cts.verifier", MockConditionProvider.class.getName());
     static final String PATH = "mock_cp";
     static final String QUERY = "query_item";
 
@@ -56,6 +58,7 @@
     private ArrayList<Uri> mSubscriptions = new ArrayList<>();
     private boolean mConnected = false;
     private BroadcastReceiver mReceiver;
+    private static MockConditionProvider sConditionProviderInstance = null;
 
     @Override
     public void onCreate() {
@@ -103,6 +106,20 @@
         unregisterReceiver(mReceiver);
         mReceiver = null;
         Log.d(TAG, "destroyed");
+        sConditionProviderInstance = null;
+    }
+
+    public void requestUnbindService() {
+        sConditionProviderInstance = null;
+        super.requestUnbind();
+    }
+
+    public boolean isConnected() {
+        return mConnected;
+    }
+
+    public static MockConditionProvider getInstance() {
+        return sConditionProviderInstance;
     }
 
     public void resetData() {
@@ -141,6 +158,7 @@
     public void onConnected() {
         Log.d(TAG, "connected");
         mConnected = true;
+        sConditionProviderInstance = this;
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index 8b5acf8..5dfee12 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -18,6 +18,7 @@
 import android.app.Notification;
 import android.content.ComponentName;
 import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -47,6 +48,7 @@
     public static final String JSON_AMBIENT = "ambient";
     public static final String JSON_MATCHES_ZEN_FILTER = "matches_zen_filter";
     public static final String JSON_REASON = "reason";
+    public static final String JSON_STATS = "stats";
 
     ArrayList<String> mPosted = new ArrayList<String>();
     ArrayMap<String, JSONObject> mNotifications = new ArrayMap<>();
@@ -174,13 +176,14 @@
 
     @Override
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
-            int reason) {
+            NotificationStats stats, int reason) {
         Log.d(TAG, "removed: " + sbn.getTag() + " for reason " + reason);
         mRemoved.add(sbn.getTag());
         JSONObject removed = new JSONObject();
         try {
             removed.put(JSON_TAG, sbn.getTag());
             removed.put(JSON_REASON, reason);
+            removed.put(JSON_STATS, stats != null);
         } catch (JSONException e) {
             Log.e(TAG, "failed to pack up notification payload", e);
         }
@@ -189,5 +192,4 @@
         mRemovedReason.put(sbn.getTag(), removed);
         onNotificationRankingUpdate(rankingMap);
     }
-
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 0983580..643cceb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,19 +16,39 @@
 
 package com.android.cts.verifier.notifications;
 
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+import static android.provider.Settings.EXTRA_APP_PACKAGE;
+import static android.provider.Settings.EXTRA_CHANNEL_ID;
+
+import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
+import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
+import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
+import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
+import static com.android.cts.verifier.notifications.MockListener.JSON_REASON;
+import static com.android.cts.verifier.notifications.MockListener.JSON_STATS;
+import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
+import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
+import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
+
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationManager;
+import android.app.NotificationChannelGroup;
 import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.notification.StatusBarNotification;
 import android.support.v4.app.NotificationCompat;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 
 import com.android.cts.verifier.R;
 
@@ -41,14 +61,11 @@
 import java.util.Set;
 import java.util.UUID;
 
-import static com.android.cts.verifier.notifications.MockListener.*;
-
-import static junit.framework.Assert.assertNotNull;
-
 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
         implements Runnable {
     private static final String TAG = "NoListenerVerifier";
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
+    protected static final String PREFS = "listener_prefs";
 
     private String mTag1;
     private String mTag2;
@@ -93,11 +110,16 @@
             tests.add(new DataIntactTest());
             tests.add(new DismissOneTest());
             tests.add(new DismissOneWithReasonTest());
+            tests.add(new DismissOneWithStatsTest());
             tests.add(new DismissAllTest());
             tests.add(new SnoozeNotificationForTimeTest());
             tests.add(new SnoozeNotificationForTimeCancelTest());
             tests.add(new GetSnoozedNotificationTest());
             tests.add(new EnableHintsTest());
+            tests.add(new ReceiveAppBlockNoticeTest());
+            tests.add(new ReceiveAppUnblockNoticeTest());
+            tests.add(new ReceiveChannelBlockNoticeTest());
+            tests.add(new ReceiveGroupBlockNoticeTest());
             tests.add(new RequestUnbindTest());
             tests.add(new RequestBindTest());
             tests.add(new MessageBundleTest());
@@ -111,7 +133,7 @@
 
     private void createChannel() {
         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
-                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+                NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
         mNm.createNotificationChannel(channel);
     }
 
@@ -212,6 +234,300 @@
         }
     }
 
+    /**
+     * Creates a notification channel. Sends the user to settings to block the channel. Waits
+     * to receive the broadcast that the channel was blocked, and confirms that the broadcast
+     * contains the correct extras.
+     */
+    protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase {
+        private String mChannelId;
+        private int mRetries = 2;
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.nls_block_channel);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            mChannelId = UUID.randomUUID().toString();
+            NotificationChannel channel = new NotificationChannel(
+                    mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
+            mNm.createNotificationChannel(channel);
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            NotificationChannel channel = mNm.getNotificationChannel(mChannelId);
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+            if (channel.getImportance() == IMPORTANCE_NONE) {
+                if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                // user hasn't jumped to settings to block the channel yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            MockListener.getInstance().resetData();
+            mNm.deleteNotificationChannel(mChannelId);
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(mChannelId);
+        }
+
+        @Override
+        protected Intent getIntent() {
+         return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+                 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
+                 .putExtra(EXTRA_CHANNEL_ID, mChannelId);
+        }
+    }
+
+    /**
+     * Creates a notification channel group. Sends the user to settings to block the group. Waits
+     * to receive the broadcast that the group was blocked, and confirms that the broadcast contains
+     * the correct extras.
+     */
+    protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase {
+        private String mGroupId;
+        private int mRetries = 2;
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.nls_block_group);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            mGroupId = UUID.randomUUID().toString();
+            NotificationChannelGroup group
+                    = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest");
+            mNm.createNotificationChannelGroup(group);
+            NotificationChannel channel = new NotificationChannel(
+                    mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
+            channel.setGroup(mGroupId);
+            mNm.createNotificationChannel(channel);
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId);
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+            if (group.isBlocked()) {
+                if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                // user hasn't jumped to settings to block the group yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            MockListener.getInstance().resetData();
+            mNm.deleteNotificationChannelGroup(mGroupId);
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(mGroupId);
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                    .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
+        }
+    }
+
+    /**
+     * Sends the user to settings to block the app. Waits to receive the broadcast that the app was
+     * blocked, and confirms that the broadcast contains the correct extras.
+     */
+    protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase {
+        private int mRetries = 2;
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.nls_block_app);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+            if (!mNm.areNotificationsEnabled()) {
+                Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName()));
+                Log.d(TAG, "Broadcast contains correct data? " +
+                        prefs.getBoolean(mContext.getPackageName(), false));
+                if (prefs.contains(mContext.getPackageName())
+                        && prefs.getBoolean(mContext.getPackageName(), false)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                Log.d(TAG, "Notifications still enabled");
+                // user hasn't jumped to settings to block the app yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            MockListener.getInstance().resetData();
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(mContext.getPackageName());
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                    .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
+        }
+    }
+
+    /**
+     * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app
+     * was unblocked, and confirms that the broadcast contains the correct extras.
+     */
+    protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase {
+        private int mRetries = 2;
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.nls_unblock_app);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+            if (mNm.areNotificationsEnabled()) {
+                if (prefs.contains(mContext.getPackageName())
+                        && !prefs.getBoolean(mContext.getPackageName(), true)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                // user hasn't jumped to settings to block the app yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            MockListener.getInstance().resetData();
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(mContext.getPackageName());
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                    .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
+        }
+    }
+
     private class DataIntactTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
@@ -271,9 +587,6 @@
                                 "data integrity test: notification ID (%d, %d)");
                         pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
                                 "data integrity test: notification when (%d, %d)");
-                    } else {
-                        pass = false;
-                        logFail("unexpected notification tag: " + tag);
                     }
                 } catch (JSONException e) {
                     pass = false;
@@ -281,7 +594,7 @@
                 }
             }
 
-            pass &= found.size() == 3;
+            pass &= found.size() >= 3;
             status = pass ? PASS : FAIL;
         }
 
@@ -395,6 +708,61 @@
         }
     }
 
+    private class DismissOneWithStatsTest extends InteractiveTestCase {
+        int mRetries = 3;
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_clear_one_stats);
+        }
+
+        @Override
+        protected void setUp() {
+            createChannel();
+            sendNotifications();
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            if (status == READY) {
+                MockListener.getInstance().cancelNotification(
+                        MockListener.getInstance().getKeyForTag(mTag1));
+                status = RETEST;
+            } else {
+                List<JSONObject> result =
+                        new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
+                boolean pass = true;
+                for (JSONObject payload : result) {
+                    try {
+                        pass &= (payload.getBoolean(JSON_STATS) == false);
+                    } catch (JSONException e) {
+                        e.printStackTrace();
+                        pass = false;
+                    }
+                }
+                if (pass) {
+                    status = PASS;
+                } else {
+                    if (--mRetries > 0) {
+                        sleep(100);
+                        status = RETEST;
+                    } else {
+                        logFail("Notification listener got populated stats object.");
+                        status = FAIL;
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannel();
+            MockListener.getInstance().resetData();
+        }
+    }
+
     private class DismissAllTest extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
@@ -461,6 +829,11 @@
         protected void tearDown() {
             MockListener.getInstance().resetData();
         }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+        }
     }
 
     private class ServiceStoppedTest extends InteractiveTestCase {
@@ -484,6 +857,11 @@
                 }
             }
         }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+        }
     }
 
     private class NotificationNotReceivedTest extends InteractiveTestCase {
@@ -935,7 +1313,7 @@
                 next();
                 return;
             }
-            // Can only read in MessaginStyle using the compat class.
+            // Can only read in MessagingStyle using the compat class.
             NotificationCompat.MessagingStyle readStyle =
                     NotificationCompat.MessagingStyle
                             .extractMessagingStyleFromNotification(
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 764ecb3..651b6bf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -167,10 +167,14 @@
      * only works if the user has just authenticated via device credentials.
      * has to be run after successful auth, in order to succeed
      */
-    private boolean tryEncrypt() {
+    protected boolean tryEncrypt() {
         return encryptInternal(true);
     }
 
+    protected Cipher getCipher() {
+        return mCipher;
+    }
+
     private boolean encryptInternal(boolean doEncrypt) {
         try {
             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
@@ -217,15 +221,14 @@
         }
     }
 
-    private void showAuthenticationScreen() {
+    protected void showAuthenticationScreen() {
         mFingerprintDialog = new FingerprintAuthDialogFragment();
         mFingerprintDialog.setActivity(this);
         mFingerprintDialog.show(getFragmentManager(), "fingerprint_dialog");
     }
 
-    private void showToast(String message) {
-        Toast.makeText(this, message, Toast.LENGTH_LONG)
-            .show();
+    protected void showToast(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
     }
 
     public static class FingerprintAuthDialogFragment extends DialogFragment {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintDialogBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintDialogBoundKeysTest.java
new file mode 100644
index 0000000..89277dd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintDialogBoundKeysTest.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.verifier.security;
+
+import android.content.DialogInterface;
+import android.hardware.fingerprint.FingerprintDialog;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.Executor;
+
+public class FingerprintDialogBoundKeysTest extends FingerprintBoundKeysTest {
+
+    private DialogCallback mDialogCallback;
+    private FingerprintDialog mFingerprintDialog;
+    private CancellationSignal mCancellationSignal;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private final Executor mExecutor = (runnable) -> {
+        mHandler.post(runnable);
+    };
+
+    private final Runnable mNegativeButtonRunnable = () -> {
+        showToast("Authentication canceled by user");
+    };
+
+    private class DialogCallback extends
+            FingerprintDialog.AuthenticationCallback {
+        @Override
+        public void onAuthenticationError(int errMsgId, CharSequence errString) {
+            showToast(errString.toString());
+        }
+
+        @Override
+        public void onAuthenticationSucceeded(FingerprintDialog.AuthenticationResult result) {
+            if (tryEncrypt()) {
+                showToast("Test passed.");
+                getPassButton().setEnabled(true);
+            } else {
+                showToast("Test failed. Key not accessible after auth");
+            }
+        }
+    }
+
+    @Override
+    protected void showAuthenticationScreen() {
+        mCancellationSignal = new CancellationSignal();
+        mDialogCallback = new DialogCallback();
+        mFingerprintDialog = new FingerprintDialog.Builder()
+                .setTitle("Authenticate with fingerprint")
+                .setNegativeButton("Cancel", mExecutor,
+                        (DialogInterface dialogInterface, int which) -> {
+                            if (which == DialogInterface.BUTTON_NEGATIVE) {
+                                mHandler.post(mNegativeButtonRunnable);
+                            }
+                        })
+                .build(getApplicationContext());
+        mFingerprintDialog.authenticate(
+                new FingerprintDialog.CryptoObject(getCipher()),
+                mCancellationSignal, mExecutor, mDialogCallback);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
index b7d9617..428d237 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
@@ -155,7 +155,7 @@
         return executeTest(operation);
     }
 
-    private String executeTest(TestSensorOperation operation) throws InterruptedException {
+    private String executeTest(TestSensorOperation operation) throws Exception {
         operation.addDefaultVerifications();
         operation.execute(getCurrentTestNode());
         return null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/EventSanitizationTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/EventSanitizationTestActivity.java
new file mode 100644
index 0000000..4ebb5c6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/EventSanitizationTestActivity.java
@@ -0,0 +1,149 @@
+/*
+ * 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.sensors;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.hardware.cts.helpers.SensorNotSupportedException;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
+import junit.framework.Assert;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests about event policy when the observer's UID is idle.
+ */
+public class EventSanitizationTestActivity extends SensorCtsVerifierTestActivity {
+    public EventSanitizationTestActivity() {
+        super(EventSanitizationTestActivity.class);
+    }
+
+    // time for the test to wait for an event
+    private static final int EVENT_TIMEOUT = 30;
+
+    @Override
+    protected void activitySetUp() throws Exception {
+        getTestLogger().logInstructions(R.string.snsr_event_sanitization_test_setup);
+        waitForUserToBegin();
+    }
+
+    @Override
+    protected void activityCleanUp() throws Exception {
+        getTestLogger().logInstructions(R.string.snsr_event_sanitization_test_cleanup);
+        waitForUserToContinue();
+    }
+
+    /**
+     * Test that no trigger events are triggered while the UID is idle.
+     */
+    public String testNoTriggerEventsWhileUidIdle() throws Exception {
+        // Not significant motion sensor, nothing to do.
+        final SensorManager sensorManager = getApplicationContext()
+                .getSystemService(SensorManager.class);
+        final Sensor sensor = sensorManager.getDefaultSensor(
+                Sensor.TYPE_SIGNIFICANT_MOTION);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_SIGNIFICANT_MOTION);
+        }
+
+        // Let us begin.
+        final SensorTestLogger logger = getTestLogger();
+        logger.logInstructions(R.string.snsr_significant_motion_test_uid_idle);
+        waitForUserToBegin();
+
+        // Watch for the trigger event.
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TriggerEventListener listener = new TriggerEventListener() {
+            @Override
+            public void onTrigger(TriggerEvent event) {
+                latch.countDown();
+            }
+        };
+        sensorManager.requestTriggerSensor(listener, sensor);
+
+        // Tell the user now when the test completes.
+        logger.logWaitForSound();
+
+        // We shouldn't be getting an event.
+        try {
+            Assert.assertFalse(getString(R.string
+                    .snsr_significant_motion_test_uid_idle_expectation),
+                    latch.await(EVENT_TIMEOUT, TimeUnit.SECONDS));
+        } finally {
+            sensorManager.cancelTriggerSensor(listener, sensor);
+            playSound();
+        }
+
+        return null;
+    }
+
+    /**
+     * Test that no on-change events are triggered while the UID is idle.
+     */
+    public String testNoOnChangeEventsWhileUidIdle() throws Exception {
+        // Not significant motion sensor, nothing to do.
+        final SensorManager sensorManager = getApplicationContext()
+                .getSystemService(SensorManager.class);
+        final Sensor sensor = sensorManager.getDefaultSensor(
+                Sensor.TYPE_PROXIMITY);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_PROXIMITY);
+        }
+
+        // Let us begin.
+        final SensorTestLogger logger = getTestLogger();
+        logger.logInstructions(R.string.snsr_proximity_test_uid_idle);
+        waitForUserToBegin();
+
+        // Watch for the change event.
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SensorEventListener listener = new SensorEventListener() {
+            @Override
+            public void onSensorChanged(SensorEvent event) {
+                latch.countDown();
+            }
+
+            @Override
+            public void onAccuracyChanged(Sensor sensor, int accuracy) {
+                /* do nothing */
+            }
+        };
+        sensorManager.registerListener(listener, sensor,
+                sensor.getMinDelay(), sensor.getMaxDelay());
+
+        // Tell the user now when the test completes.
+        logger.logWaitForSound();
+
+        // We shouldn't be getting an event.
+        try {
+            Assert.assertFalse(getString(R.string
+                    .snsr_proximity_test_uid_idle_expectation),
+                    latch.await(EVENT_TIMEOUT, TimeUnit.SECONDS));
+        } finally {
+            sensorManager.unregisterListener(listener, sensor);
+            playSound();
+        }
+
+        return null;
+    }
+}
diff --git a/apps/PermissionApp/Android.mk b/apps/PermissionApp/Android.mk
index f22ecc3..cf4186d 100644
--- a/apps/PermissionApp/Android.mk
+++ b/apps/PermissionApp/Android.mk
@@ -29,6 +29,6 @@
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index 82e3617..f880933 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-sdk android:minSdkVersion="23"/>
 
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application android:label="CtsPermissionApp"
             android:icon="@drawable/ic_permissionapp">
diff --git a/apps/VpnApp/latest/AndroidManifest.xml b/apps/VpnApp/latest/AndroidManifest.xml
index 6cc706f..f63e46f 100644
--- a/apps/VpnApp/latest/AndroidManifest.xml
+++ b/apps/VpnApp/latest/AndroidManifest.xml
@@ -19,6 +19,8 @@
 
     <uses-sdk android:minSdkVersion="22"/>
 
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+
     <application android:label="@string/app">
         <activity android:name=".VpnClient">
             <intent-filter>
diff --git a/build/compatibility_test_suite.mk b/build/compatibility_test_suite.mk
index d133c3e..9bad76e 100644
--- a/build/compatibility_test_suite.mk
+++ b/build/compatibility_test_suite.mk
@@ -46,6 +46,14 @@
 
 LOCAL_MODULE_TAGS := optional
 
+# If DynamicConfig.xml exists copy it inside the jar
+ifneq (,$(wildcard $(LOCAL_PATH)/DynamicConfig.xml))
+  dynamic_config_local := $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),true,COMMON)/$(LOCAL_MODULE).dynamic
+  $(eval $(call copy-one-file,$(LOCAL_PATH)/DynamicConfig.xml,$(dynamic_config_local)))
+  LOCAL_JAVA_RESOURCE_FILES += $(dynamic_config_local)
+endif
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 
+dynamic_config_local :=
 suite_info_prop :=
diff --git a/build/device_info_package.mk b/build/device_info_package.mk
index 5c290b0..7922f28 100644
--- a/build/device_info_package.mk
+++ b/build/device_info_package.mk
@@ -19,7 +19,9 @@
 DEVICE_INFO_PACKAGE := com.android.compatibility.common.deviceinfo
 DEVICE_INFO_INSTRUMENT := android.support.test.runner.AndroidJUnitRunner
 DEVICE_INFO_USES_LIBRARY := android.test.runner
-DEVICE_INFO_PERMISSIONS += android.permission.WRITE_EXTERNAL_STORAGE
+DEVICE_INFO_PERMISSIONS += \
+  android.permission.READ_PHONE_STATE \
+  android.permission.WRITE_EXTERNAL_STORAGE
 DEVICE_INFO_ACTIVITIES += \
   $(DEVICE_INFO_PACKAGE).ConfigurationDeviceInfo \
   $(DEVICE_INFO_PACKAGE).CpuDeviceInfo \
@@ -42,7 +44,7 @@
 endif
 
 ifeq ($(DEVICE_INFO_TARGET_SDK),)
-DEVICE_INFO_TARGET_SDK := 8
+DEVICE_INFO_TARGET_SDK := 17
 endif
 
 # Add the base device info
diff --git a/common/device-side/device-info/Android.mk b/common/device-side/device-info/Android.mk
index bf6e122..df95d9a 100644
--- a/common/device-side/device-info/Android.mk
+++ b/common/device-side/device-info/Android.mk
@@ -23,14 +23,26 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
     android-support-test \
-    junit \
-    legacy-android-test
+    junit
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.base.stubs \
+    framework-stub-for-compatibility-device-info
 
 LOCAL_MODULE := compatibility-device-info
 
-# uncomment when b/13282254 is fixed
-#LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+include $(CLEAR_VARS)
+# This stub library provides some internal APIs (SystemProperties, VintfObject, etc.)
+# to compatibility-device-info library.
+LOCAL_MODULE := framework-stub-for-compatibility-device-info
+LOCAL_SRC_FILES := $(call all-java-files-under, src_stub)
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+LOCAL_UNINSTALLABLE_MODULE := true
+include $(BUILD_JAVA_LIBRARY)
+
 include $(call all-makefiles-under,$(LOCAL_PATH))
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 cb4324c..c81f8fe 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
@@ -73,7 +73,7 @@
         store.addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
         store.addResult(BUILD_ABI, Build.CPU_ABI);
         store.addResult(BUILD_ABI2, Build.CPU_ABI2);
-        store.addResult(BUILD_SERIAL, Build.SERIAL);
+        store.addResult(BUILD_SERIAL, Build.getSerial());
         store.addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
         store.addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
         store.addResult(BUILD_REFERENCE_FINGERPRINT,
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/VintfDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/VintfDeviceInfo.java
index 468bcc1..6234f0e 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/VintfDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/VintfDeviceInfo.java
@@ -63,5 +63,13 @@
             store.endGroup();
         }
         store.endArray();
+
+        // getTargetFrameworkCompatibilityMatrixVersion is available Android P onward.
+        // (Use O_MR1 until P is released.)
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
+           return;
+        }
+        store.addResult("target_fcm_version",
+                        VintfObject.getTargetFrameworkCompatibilityMatrixVersion());
     }
 }
diff --git a/common/device-side/device-info/src_stub/android/os/SystemProperties.java b/common/device-side/device-info/src_stub/android/os/SystemProperties.java
new file mode 100644
index 0000000..2f8a051
--- /dev/null
+++ b/common/device-side/device-info/src_stub/android/os/SystemProperties.java
@@ -0,0 +1,9 @@
+/**
+ * This is a stub library for the runtime class at
+ * frameworks/base/core/java/android/os/SystemProperties.java
+ */
+package android.os;
+
+public class SystemProperties {
+    public static String get(String key, String def) { return null; }
+}
diff --git a/common/device-side/device-info/src_stub/android/os/VintfObject.java b/common/device-side/device-info/src_stub/android/os/VintfObject.java
new file mode 100644
index 0000000..b8da52e
--- /dev/null
+++ b/common/device-side/device-info/src_stub/android/os/VintfObject.java
@@ -0,0 +1,16 @@
+/**
+ * This is a stub library for the runtime class at
+ * frameworks/base/core/java/android/os/VintfObject.java
+ */
+
+package android.os;
+
+import java.util.Map;
+
+public class VintfObject {
+    public static Map<String, String[]> getVndkSnapshots() { return null; }
+    public static String getSepolicyVersion() { return null; }
+    public static String[] getHalNamesAndVersions() { return null; }
+    public static String[] report() { return null; }
+    public static Long getTargetFrameworkCompatibilityMatrixVersion() { return null; }
+}
diff --git a/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java b/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java
new file mode 100644
index 0000000..20fa94c
--- /dev/null
+++ b/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java
@@ -0,0 +1,17 @@
+/**
+ * This is a stub library for the runtime class at
+ * frameworks/base/core/java/android/os/VintfRuntimeInfo.java
+ */
+package android.os;
+
+public class VintfRuntimeInfo {
+    public static String getBootAvbVersion() { return null; }
+    public static String getBootVbmetaAvbVersion() { return null; }
+    public static String getCpuInfo() { return null; }
+    public static String getHardwareId() { return null; }
+    public static String getKernelVersion() { return null; }
+    public static String getNodeName() { return null; }
+    public static String getOsName() { return null; }
+    public static String getOsRelease() { return null; }
+    public static String getOsVersion() { return null; }
+}
diff --git a/common/device-side/device-info/tests/Android.mk b/common/device-side/device-info/tests/Android.mk
index d40614c..cd05796 100644
--- a/common/device-side/device-info/tests/Android.mk
+++ b/common/device-side/device-info/tests/Android.mk
@@ -18,12 +18,14 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info junit
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE := compatibility-device-info-tests
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/nativetesthelper/Android.mk b/common/device-side/nativetesthelper/Android.mk
new file mode 100644
index 0000000..63b81e1
--- /dev/null
+++ b/common/device-side/nativetesthelper/Android.mk
@@ -0,0 +1,26 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-devicesidelib
+LOCAL_MODULE := nativetesthelper
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/device-side/nativetesthelper/jni/Android.mk b/common/device-side/nativetesthelper/jni/Android.mk
new file mode 100644
index 0000000..f970e8c
--- /dev/null
+++ b/common/device-side/nativetesthelper/jni/Android.mk
@@ -0,0 +1,36 @@
+# 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.
+
+#
+# This is the shared library included by the JNI test app.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativetesthelper_jni
+
+LOCAL_SRC_FILES := \
+        gtest_wrapper.cpp
+
+LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++
+LOCAL_WHOLE_STATIC_LIBRARIES := libgtest_ndk_c++
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := libgtest_ndk_c++
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_MULTILIB := both
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp b/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp
new file mode 100644
index 0000000..1f91b3a
--- /dev/null
+++ b/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <gtest/gtest.h>
+
+static struct {
+    jclass clazz;
+
+    /** static methods **/
+    jmethodID createTestDescription;
+
+    /** methods **/
+    jmethodID addChild;
+} gDescription;
+
+static struct {
+    jclass clazz;
+
+    jmethodID fireTestStarted;
+    jmethodID fireTestIgnored;
+    jmethodID fireTestFailure;
+    jmethodID fireTestFinished;
+
+} gRunNotifier;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gAssertionFailure;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gFailure;
+
+jobject gEmptyAnnotationsArray;
+
+static jobject createTestDescription(JNIEnv* env, const char* className, const char* testName) {
+    ScopedLocalRef<jstring> jClassName(env, env->NewStringUTF(className));
+    ScopedLocalRef<jstring> jTestName(env, env->NewStringUTF(testName));
+    return env->CallStaticObjectMethod(gDescription.clazz, gDescription.createTestDescription,
+            jClassName.get(), jTestName.get(), gEmptyAnnotationsArray);
+}
+
+static void addChild(JNIEnv* env, jobject description, jobject childDescription) {
+    env->CallVoidMethod(description, gDescription.addChild, childDescription);
+}
+
+
+class JUnitNotifyingListener : public ::testing::EmptyTestEventListener {
+public:
+
+    JUnitNotifyingListener(JNIEnv* env, jobject runNotifier)
+            : mEnv(env)
+            , mRunNotifier(runNotifier)
+            , mCurrentTestDescription{env, nullptr}
+    {}
+    virtual ~JUnitNotifyingListener() {}
+
+    virtual void OnTestStart(const testing::TestInfo &testInfo) override {
+        mCurrentTestDescription.reset(
+                createTestDescription(mEnv, testInfo.test_case_name(), testInfo.name()));
+        notify(gRunNotifier.fireTestStarted);
+    }
+
+    virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override {
+        if (!testPartResult.passed()) {
+            char message[1024];
+            snprintf(message, 1024, "%s:%d\n%s", testPartResult.file_name(), testPartResult.line_number(),
+                    testPartResult.message());
+            ScopedLocalRef<jstring> jmessage(mEnv, mEnv->NewStringUTF(message));
+            ScopedLocalRef<jobject> jthrowable(mEnv, mEnv->NewObject(gAssertionFailure.clazz,
+                    gAssertionFailure.ctor, jmessage.get()));
+            ScopedLocalRef<jobject> jfailure(mEnv, mEnv->NewObject(gFailure.clazz,
+                    gFailure.ctor, mCurrentTestDescription.get(), jthrowable.get()));
+            mEnv->CallVoidMethod(mRunNotifier, gRunNotifier.fireTestFailure, jfailure.get());
+        }
+    }
+
+    virtual void OnTestEnd(const testing::TestInfo&) override {
+        notify(gRunNotifier.fireTestFinished);
+        mCurrentTestDescription.reset();
+    }
+
+    virtual void OnTestProgramEnd(const testing::UnitTest& unitTest) override {
+        // Invoke the notifiers for all the disabled tests
+        for (int testCaseIndex = 0; testCaseIndex < unitTest.total_test_case_count(); testCaseIndex++) {
+            auto testCase = unitTest.GetTestCase(testCaseIndex);
+            for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
+                auto testInfo = testCase->GetTestInfo(testIndex);
+                if (!testInfo->should_run()) {
+                    mCurrentTestDescription.reset(
+                            createTestDescription(mEnv, testCase->name(), testInfo->name()));
+                    notify(gRunNotifier.fireTestIgnored);
+                    mCurrentTestDescription.reset();
+                }
+            }
+        }
+    }
+
+private:
+    void notify(jmethodID method) {
+        mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get());
+    }
+
+    JNIEnv* mEnv;
+    jobject mRunNotifier;
+    ScopedLocalRef<jobject> mCurrentTestDescription;
+};
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jobject suite) {
+    // Initialize gtest, removing the default result printer
+    int argc = 1;
+    const char* argv[] = { "gtest_wrapper" };
+    ::testing::InitGoogleTest(&argc, (char**) argv);
+
+    auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+    delete listeners.Release(listeners.default_result_printer());
+
+    gDescription.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/Description"));
+    gDescription.createTestDescription = env->GetStaticMethodID(gDescription.clazz, "createTestDescription",
+            "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/annotation/Annotation;)Lorg/junit/runner/Description;");
+    gDescription.addChild = env->GetMethodID(gDescription.clazz, "addChild",
+            "(Lorg/junit/runner/Description;)V");
+
+    jclass annotations = env->FindClass("java/lang/annotation/Annotation");
+    gEmptyAnnotationsArray = env->NewGlobalRef(env->NewObjectArray(0, annotations, nullptr));
+
+    gAssertionFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("java/lang/AssertionError"));
+    gAssertionFailure.ctor = env->GetMethodID(gAssertionFailure.clazz, "<init>", "(Ljava/lang/Object;)V");
+
+    gFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/notification/Failure"));
+    gFailure.ctor = env->GetMethodID(gFailure.clazz, "<init>",
+            "(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V");
+
+    gRunNotifier.clazz = (jclass) env->NewGlobalRef(
+            env->FindClass("org/junit/runner/notification/RunNotifier"));
+    gRunNotifier.fireTestStarted = env->GetMethodID(gRunNotifier.clazz, "fireTestStarted",
+            "(Lorg/junit/runner/Description;)V");
+    gRunNotifier.fireTestIgnored = env->GetMethodID(gRunNotifier.clazz, "fireTestIgnored",
+            "(Lorg/junit/runner/Description;)V");
+    gRunNotifier.fireTestFinished = env->GetMethodID(gRunNotifier.clazz, "fireTestFinished",
+            "(Lorg/junit/runner/Description;)V");
+    gRunNotifier.fireTestFailure = env->GetMethodID(gRunNotifier.clazz, "fireTestFailure",
+            "(Lorg/junit/runner/notification/Failure;)V");
+
+    auto unitTest = ::testing::UnitTest::GetInstance();
+    for (int testCaseIndex = 0; testCaseIndex < unitTest->total_test_case_count(); testCaseIndex++) {
+        auto testCase = unitTest->GetTestCase(testCaseIndex);
+        for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
+            auto testInfo = testCase->GetTestInfo(testIndex);
+            ScopedLocalRef<jobject> testDescription(env,
+                    createTestDescription(env, testCase->name(), testInfo->name()));
+            addChild(env, suite, testDescription.get());
+        }
+    }
+}
+
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv *env, jclass, jobject notifier) {
+    auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+    JUnitNotifyingListener junitListener{env, notifier};
+    listeners.Append(&junitListener);
+    int success = RUN_ALL_TESTS();
+    listeners.Release(&junitListener);
+    return success == 0;
+}
diff --git a/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java b/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java
new file mode 100644
index 0000000..222a1a0
--- /dev/null
+++ b/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java
@@ -0,0 +1,59 @@
+/*
+ * 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.gtestrunner;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+
+public class GtestRunner extends Runner {
+    private static boolean sOnceFlag = false;
+
+    private Class mTargetClass;
+    private Description mDescription;
+
+    public GtestRunner(Class testClass) {
+        synchronized (GtestRunner.class) {
+            if (sOnceFlag) {
+                throw new IllegalStateException("Error multiple GtestRunners defined");
+            }
+            sOnceFlag = true;
+        }
+
+        mTargetClass = testClass;
+        TargetLibrary library = (TargetLibrary) testClass.getAnnotation(TargetLibrary.class);
+        if (library == null) {
+            throw new IllegalStateException("Missing required @TargetLibrary annotation");
+        }
+        System.loadLibrary(library.value());
+        mDescription = Description.createSuiteDescription(testClass);
+        nInitialize(mDescription);
+    }
+
+    @Override
+    public Description getDescription() {
+        return mDescription;
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        nRun(notifier);
+    }
+
+    private static native void nInitialize(Description description);
+    private static native void nRun(RunNotifier notifier);
+}
diff --git a/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java b/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java
new file mode 100644
index 0000000..23bc53d
--- /dev/null
+++ b/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java
@@ -0,0 +1,30 @@
+/*
+ * 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.gtestrunner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface TargetLibrary {
+    String value();
+}
diff --git a/common/device-side/test-app/AndroidManifest.xml b/common/device-side/test-app/AndroidManifest.xml
index 9c857f0..883b439 100755
--- a/common/device-side/test-app/AndroidManifest.xml
+++ b/common/device-side/test-app/AndroidManifest.xml
@@ -17,7 +17,10 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.compatibility.common">
+
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.compatibility.common.deviceinfo.TestDeviceInfo" />
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index 26a37ec..1092fc4 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -24,15 +24,17 @@
     android-support-test \
     ub-uiautomator \
     mockito-target-minus-junit4 \
-    legacy-android-test
+    platformprotosnano
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    android.test.base.stubs
 
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE := compatibility-device-util
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java
new file mode 100644
index 0000000..646a9fc
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.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.compatibility.common.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.am.proto.nano.ActivityManagerServiceDumpProcessesProto;
+import com.android.server.am.proto.nano.ProcessRecordProto;
+
+public class AmUtils {
+    private static final String TAG = "CtsAmUtils";
+
+    private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity --proto processes";
+
+    private AmUtils() {
+    }
+
+    /** Run "adb shell am make-uid-idle PACKAGE" */
+    public static void runMakeUidIdle(String packageName) {
+        SystemUtil.runShellCommandForNoOutput("am make-uid-idle " + packageName);
+    }
+
+    /** Run "adb shell am kill PACKAGE" */
+    public static void runKill(String packageName) throws Exception {
+        runKill(packageName, false /* wait */);
+    }
+
+    public static void runKill(String packageName, boolean wait) throws Exception {
+        SystemUtil.runShellCommandForNoOutput("am kill --user cur " + packageName);
+
+        if (!wait) {
+            return;
+        }
+
+        TestUtils.waitUntil("package process was not killed:" + packageName,
+                () -> !isProcessRunning(packageName));
+    }
+
+    private static boolean isProcessRunning(String packageName) throws Exception {
+        byte[] dump = executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES);
+        ProcessRecordProto[] processes = ActivityManagerServiceDumpProcessesProto.parseFrom(dump)
+                .procs;
+
+        for (int i = processes.length - 1; i >=0; --i) {
+            if (processes[i].processName.equals(packageName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static 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);
+        }
+    }
+
+    /** Run "adb shell am set-standby-bucket" */
+    public static void setStandbyBucket(String packageName, int value) {
+        SystemUtil.runShellCommandForNoOutput("am set-standby-bucket " + packageName
+                + " " + value);
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
new file mode 100644
index 0000000..20c5b9e
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import android.app.AppOpsManager;
+import android.support.test.InstrumentationRegistry;
+
+import java.io.IOException;
+
+/**
+ * Utilities for controlling App Ops settings, and testing whether ops are logged.
+ */
+public class AppOpsUtils {
+
+    /**
+     * Resets a package's app ops configuration to the device default. See AppOpsManager for the
+     * default op settings.
+     *
+     * <p>
+     * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
+     * ends with a reproducible default state, and so doesn't affect other tests.
+     *
+     * <p>
+     * Some app ops are configured to be non-resettable, which means that the state of these will
+     * not be reset even when calling this method.
+     */
+    public static String reset(String packageName) throws IOException {
+        return runCommand("appops reset " + packageName);
+    }
+
+    /**
+     * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
+     */
+    public static String setOpMode(String packageName, String opStr, int mode)
+            throws IOException {
+        String modeStr;
+        switch (mode) {
+            case MODE_ALLOWED:
+                modeStr = "allow";
+                break;
+            case MODE_ERRORED:
+                modeStr = "deny";
+                break;
+            case MODE_IGNORED:
+                modeStr = "ignore";
+                break;
+            case MODE_DEFAULT:
+                modeStr = "default";
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected app op type");
+        }
+        String command = "appops set " + packageName + " " + opStr + " " + modeStr;
+        return runCommand(command);
+    }
+
+    /**
+     * Returns whether an allowed operation has been logged by the AppOpsManager for a
+     * package. Operations are noted when the app attempts to perform them and calls e.g.
+     * {@link AppOpsManager#noteOperation}.
+     *
+     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+     */
+    public static boolean allowedOperationLogged(String packageName, String opStr)
+            throws IOException {
+        return getOpState(packageName, opStr).contains(" time=");
+    }
+
+    /**
+     * Returns whether an allowed operation has been logged by the AppOpsManager for a
+     * package. Operations are noted when the app attempts to perform them and calls e.g.
+     * {@link AppOpsManager#noteOperation}.
+     *
+     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+     */
+    public static boolean rejectedOperationLogged(String packageName, String opStr)
+            throws IOException {
+        return getOpState(packageName, opStr).contains(" rejectTime=");
+    }
+
+    /**
+     * Returns the app op state for a package. Includes information on when the operation was last
+     * attempted to be performed by the package.
+     *
+     * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
+     */
+    private static String getOpState(String packageName, String opStr) throws IOException {
+        return runCommand("appops get " + packageName + " " + opStr);
+    }
+
+    private static String runCommand(String command) throws IOException {
+        return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..c6fbac0
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.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.compatibility.common.util;
+
+import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import android.os.BatteryManager;
+import android.os.PowerManager;
+import android.provider.Settings.Global;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+public class BatteryUtils {
+    private static final String TAG = "CtsBatteryUtils";
+
+    private BatteryUtils() {
+    }
+
+    public static BatteryManager getBatteryManager() {
+        return InstrumentationRegistry.getContext().getSystemService(BatteryManager.class);
+    }
+
+    public static PowerManager getPowerManager() {
+        return InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
+    }
+
+    /** Make the target device think it's off charger. */
+    public static void runDumpsysBatteryUnplug() throws Exception {
+        SystemUtil.runShellCommandForNoOutput("dumpsys battery unplug");
+
+        Log.d(TAG, "Battery UNPLUGGED");
+    }
+
+    /** Reset {@link #runDumpsysBatteryUnplug}.  */
+    public static void runDumpsysBatteryReset() throws Exception {
+        SystemUtil.runShellCommandForNoOutput(("dumpsys battery reset"));
+
+        Log.d(TAG, "Battery RESET");
+    }
+
+    /**
+     * Enable / disable battery saver. Note {@link #runDumpsysBatteryUnplug} must have been
+     * executed before enabling BS.
+     */
+    public static void enableBatterySaver(boolean enabled) throws Exception {
+        if (enabled) {
+            putGlobalSetting(Global.LOW_POWER_MODE, "1");
+            waitUntil("Battery saver still off", () -> getPowerManager().isPowerSaveMode());
+            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
+                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
+                            != getPowerManager().getLocationPowerSaveMode()));
+        } else {
+            putGlobalSetting(Global.LOW_POWER_MODE, "0");
+            waitUntil("Battery saver still on", () -> !getPowerManager().isPowerSaveMode());
+            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
+                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
+                            == getPowerManager().getLocationPowerSaveMode()));
+        }
+
+        Log.d(TAG, "Battery saver turned " + (enabled ? "ON" : "OFF"));
+    }
+
+    /**
+     * Turn on/off screen.
+     */
+    public static void turnOnScreen(boolean on) throws Exception {
+        if (on) {
+            SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_WAKEUP");
+            waitUntil("Device still not interactive", () -> getPowerManager().isInteractive());
+
+        } else {
+            SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_SLEEP");
+            waitUntil("Device still interactive", () -> !getPowerManager().isInteractive());
+        }
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
index 4ac8403..3fbc2c5 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
@@ -79,6 +79,19 @@
         return null;
     }
 
+    /**
+     * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
+     * if no broadcast with expected action is received within the given timeout.
+     */
+    public @Nullable Intent awaitForBroadcast(long timeoutMillis) {
+        try {
+            return mBlockingQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForBroadcast get interrupted: ", e);
+        }
+        return null;
+    }
+
     public void unregisterQuietly() {
         try {
             mContext.unregisterReceiver(this);
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
new file mode 100644
index 0000000..772e7d3
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class to help broadcast-based RPC.
+ */
+public abstract class BroadcastRpcBase<TRequest, TResponse> {
+    private static final String TAG = "BroadcastRpc";
+
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    static final String ACTION_REQUEST = "ACTION_REQUEST";
+    static final String EXTRA_PAYLOAD = "EXTRA_PAYLOAD";
+    static final String EXTRA_EXCEPTION = "EXTRA_EXCEPTION";
+
+    static Handler sMainHandler = new Handler(Looper.getMainLooper());
+
+    /** Implement in a subclass */
+    protected abstract byte[] requestToBytes(TRequest request);
+
+    /** Implement in a subclass */
+    protected abstract TResponse bytesToResponse(byte[] bytes);
+
+    public TResponse invoke(ComponentName targetReceiver, TRequest request) throws Exception {
+        // Create a request intent.
+        Log.i(TAG, "Sending to: " + targetReceiver + (VERBOSE ? "\nRequest: " + request : ""));
+
+        final Intent requestIntent = new Intent(ACTION_REQUEST)
+                .setComponent(targetReceiver)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                .putExtra(EXTRA_PAYLOAD, requestToBytes(request));
+
+        // Send it.
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Bundle> responseBundle = new AtomicReference<>();
+
+        InstrumentationRegistry.getContext().sendOrderedBroadcast(
+                requestIntent, null, new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        responseBundle.set(getResultExtras(false));
+                        latch.countDown();
+                    }
+                }, sMainHandler, 0, null, null);
+
+        // Wait for a reply and check it.
+        final boolean responseArrived = latch.await(60, TimeUnit.SECONDS);
+        assertTrue("Didn't receive broadcast result.", responseArrived);
+
+        // TODO If responseArrived is false, print if the package / component is installed?
+
+        assertNotNull("Didn't receive result extras", responseBundle.get());
+
+        final String exception = responseBundle.get().getString(EXTRA_EXCEPTION);
+        if (exception != null) {
+            fail("Target throw exception: receiver=" + targetReceiver
+                    + "\nException: " + exception);
+        }
+
+        final byte[] resultPayload = responseBundle.get().getByteArray(EXTRA_PAYLOAD);
+        assertNotNull("Didn't receive result payload", resultPayload);
+
+        Log.i(TAG, "Response received: " + (VERBOSE ? resultPayload.toString() : ""));
+
+        return bytesToResponse(resultPayload);
+    }
+
+    /**
+     * Base class for a receiver for a broadcast-based RPC.
+     */
+    public abstract static class ReceiverBase<TRequest, TResponse> extends BroadcastReceiver {
+        @Override
+        public final void onReceive(Context context, Intent intent) {
+            assertEquals(ACTION_REQUEST, intent.getAction());
+
+            // Parse the request.
+            final TRequest request = bytesToRequest(intent.getByteArrayExtra(EXTRA_PAYLOAD));
+
+            Log.i(TAG, "Request received: " + (VERBOSE ? request.toString() : ""));
+
+            Throwable exception = null;
+
+            // Handle it and generate a response.
+            TResponse response = null;
+            try {
+                response = handleRequest(context, request);
+                Log.i(TAG, "Response generated: " + (VERBOSE ? response.toString() : ""));
+            } catch (Throwable e) {
+                exception = e;
+                Log.e(TAG, "Exception thrown: " + e.getMessage(), e);
+            }
+
+            // Send back.
+            final Bundle extras = new Bundle();
+            if (response != null) {
+                extras.putByteArray(EXTRA_PAYLOAD, responseToBytes(response));
+            }
+            if (exception != null) {
+                extras.putString(EXTRA_EXCEPTION,
+                        exception.toString() + "\n" + Log.getStackTraceString(exception));
+            }
+            setResultExtras(extras);
+        }
+
+        /** Implement in a subclass */
+        protected abstract TResponse handleRequest(Context context, TRequest request)
+                throws Exception;
+
+        /** Implement in a subclass */
+        protected abstract byte[] responseToBytes(TResponse response);
+
+        /** Implement in a subclass */
+        protected abstract TRequest bytesToRequest(byte[] bytes);
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
new file mode 100644
index 0000000..eda641d
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import android.os.Bundle;
+
+public class BundleUtils {
+    private BundleUtils() {
+    }
+
+    public static Bundle makeBundle(Object... keysAndValues) {
+        if ((keysAndValues.length % 2) != 0) {
+            throw new IllegalArgumentException("Argument count not even.");
+        }
+
+        if (keysAndValues.length == 0) {
+            return null;
+        }
+        final Bundle ret = new Bundle();
+
+        for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+            final String key = keysAndValues[i].toString();
+            final Object value = keysAndValues[i + 1];
+
+            if (value == null) {
+                ret.putString(key, null);
+
+            } else if (value instanceof Boolean) {
+                ret.putBoolean(key, (Boolean) value);
+
+            } else if (value instanceof Integer) {
+                ret.putInt(key, (Integer) value);
+
+            } else if (value instanceof String) {
+                ret.putString(key, (String) value);
+
+            } else if (value instanceof Bundle) {
+                ret.putBundle(key, (Bundle) value);
+            } else {
+                throw new IllegalArgumentException(
+                        "Type not supported yet: " + value.getClass().getName());
+            }
+        }
+        return ret;
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicConditionalTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicConditionalTestCase.java
new file mode 100644
index 0000000..fd05398
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicConditionalTestCase.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import org.junit.Before;
+
+/**
+ *  Device-side base class for tests leveraging the Business Logic service for rules that are
+ *  conditionally added based on the device characteristics.
+ */
+public class BusinessLogicConditionalTestCase extends BusinessLogicTestCase {
+
+    @Override
+    @Before
+    public void handleBusinessLogic() {
+        super.loadBuisnessLogic();
+        ensureAuthenticated();
+        super.executeBusinessLogic();
+    }
+
+    protected void ensureAuthenticated() {
+        if (!mCanReadBusinessLogic) {
+            // super class handles the condition that the service is unavailable.
+            return;
+        }
+
+        if (!mBusinessLogic.mConditionalTestsEnabled) {
+            skipTest("Execution of device specific tests is not enabled. "
+                    + "Enable with '--conditional-business-logic-tests-enabled'");
+        }
+
+        if (mBusinessLogic.isAuthorized()) {
+            // Run test as normal.
+            return;
+        }
+        String message = mBusinessLogic.getAuthenticationStatusMessage();
+
+        // Fail test since request was not authorized.
+        failTest(String.format("Unable to execute because %s.", message));
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
index 2316637..45f979a 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
@@ -45,21 +45,16 @@
     /* Test name rule that tracks the current test method under execution */
     @Rule public TestName mTestCase = new TestName();
 
-    private static BusinessLogic mBusinessLogic;
-    private static boolean mCanReadBusinessLogic = true;
-
-    @BeforeClass
-    public static void prepareBusinessLogic() {
-        File businessLogicFile = new File(BusinessLogic.DEVICE_FILE);
-        if (businessLogicFile.canRead()) {
-            mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
-        } else {
-            mCanReadBusinessLogic = false;
-        }
-    }
+    protected BusinessLogic mBusinessLogic;
+    protected boolean mCanReadBusinessLogic = true;
 
     @Before
-    public void executeBusinessLogic() {
+    public void handleBusinessLogic() {
+        loadBuisnessLogic();
+        executeBusinessLogic();
+    }
+
+    protected void executeBusinessLogic() {
         String methodName = mTestCase.getMethodName();
         assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
                 + "remote configuration.", methodName), mCanReadBusinessLogic);
@@ -75,6 +70,15 @@
         }
     }
 
+    protected void loadBuisnessLogic() {
+        File businessLogicFile = new File(BusinessLogic.DEVICE_FILE);
+        if (businessLogicFile.canRead()) {
+            mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
+        } else {
+            mCanReadBusinessLogic = false;
+        }
+    }
+
     protected static Instrumentation getInstrumentation() {
         return InstrumentationRegistry.getInstrumentation();
     }
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java b/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
new file mode 100644
index 0000000..436161a
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import static junit.framework.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * CallbackAsserter helps wait until a callback is called.
+ */
+public class CallbackAsserter {
+    private static final String TAG = "CallbackAsserter";
+
+    final CountDownLatch mLatch = new CountDownLatch(1);
+
+    CallbackAsserter() {
+    }
+
+    /**
+     * Call this to assert a callback be called within the given timeout.
+     */
+    public final void assertCalled(String message, int timeoutSeconds) throws Exception {
+        try {
+            if (mLatch.await(timeoutSeconds, TimeUnit.SECONDS)) {
+                return;
+            }
+            fail("Didn't receive callback: " + message);
+        } finally {
+            cleanUp();
+        }
+    }
+
+    void cleanUp() {
+    }
+
+    /**
+     * Create an instance for a broadcast.
+     */
+    public static CallbackAsserter forBroadcast(IntentFilter filter) {
+        return forBroadcast(filter, null);
+    }
+
+    /**
+     * Create an instance for a broadcast.
+     */
+    public static CallbackAsserter forBroadcast(IntentFilter filter, Predicate<Intent> checker) {
+        return new BroadcastAsserter(filter, checker);
+    }
+
+    /**
+     * Create an instance for a content changed notification.
+     */
+    public static CallbackAsserter forContentUri(Uri watchUri) {
+        return forContentUri(watchUri, null);
+    }
+
+    /**
+     * Create an instance for a content changed notification.
+     */
+    public static CallbackAsserter forContentUri(Uri watchUri, Predicate<Uri> checker) {
+        return new ContentObserverAsserter(watchUri, checker);
+    }
+
+    private static class BroadcastAsserter extends CallbackAsserter {
+        private final BroadcastReceiver mReceiver;
+
+        BroadcastAsserter(IntentFilter filter, Predicate<Intent> checker) {
+            mReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (checker != null && !checker.test(intent)) {
+                        Log.v(TAG, "Ignoring intent: " + intent);
+                        return;
+                    }
+                    mLatch.countDown();
+                }
+            };
+            InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
+        }
+
+        @Override
+        void cleanUp() {
+            InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
+        }
+    }
+
+    private static class ContentObserverAsserter extends CallbackAsserter {
+        private final ContentObserver mObserver;
+
+        ContentObserverAsserter(Uri watchUri, Predicate<Uri> checker) {
+            mObserver = new ContentObserver(null) {
+                @Override
+                public void onChange(boolean selfChange, Uri uri) {
+                    if (checker != null && !checker.test(uri)) {
+                        Log.v(TAG, "Ignoring notification on URI: " + uri);
+                        return;
+                    }
+                    mLatch.countDown();
+                }
+            };
+            InstrumentationRegistry.getContext().getContentResolver().registerContentObserver(
+                    watchUri, true, mObserver);
+        }
+
+        @Override
+        void cleanUp() {
+            InstrumentationRegistry.getContext().getContentResolver().unregisterContentObserver(
+                    mObserver);
+        }
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java
new file mode 100644
index 0000000..09a0a85
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.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 com.android.compatibility.common.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+public class ConnectivityUtils {
+    private ConnectivityUtils() {
+    }
+
+    /** @return true when the device has a network connection. */
+    public static boolean isNetworkConnected(Context context) {
+        final NetworkInfo networkInfo = context.getSystemService(ConnectivityManager.class)
+                .getActiveNetworkInfo();
+        return (networkInfo != null) && networkInfo.isConnected();
+    }
+
+    /** Assert that the device has a network connection. */
+    public static void assertNetworkConnected(Context context) {
+        assertTrue("Network must be connected", isNetworkConnected(context));
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java b/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java
new file mode 100644
index 0000000..cee610e
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.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 com.android.compatibility.common.util;
+
+import org.mockito.ArgumentMatchers;
+
+public class MoreMatchers {
+    private MoreMatchers() {
+    }
+
+    public static <T> T anyOrNull(Class<T> clazz) {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+
+    public static String anyStringOrNull() {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java
new file mode 100644
index 0000000..c34c477
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.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.compatibility.common.util;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that provides a callback upon test failures.
+ */
+public abstract class OnFailureRule implements TestRule {
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    onTestFailure(base, description, t);
+                    throw t;
+                }
+            }
+        };
+    }
+
+    protected abstract void onTestFailure(Statement base, Description description, Throwable t);
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
new file mode 100644
index 0000000..ecaa722
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
@@ -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.
+ */
+package com.android.compatibility.common.util;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ParcelUtils {
+    private ParcelUtils() {
+    }
+
+    /** Convert a Parcelable into a byte[]. */
+    public static byte[] toBytes(Parcelable p) {
+        assertNotNull(p);
+
+        final Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(p, 0);
+        byte[] data = parcel.marshall();
+        parcel.recycle();
+
+        return data;
+    }
+
+    /** Decode a byte[] into a Parcelable. */
+    public static <T extends Parcelable> T fromBytes(byte[] data) {
+        assertNotNull(data);
+
+        final Parcel parcel = Parcel.obtain();
+        parcel.unmarshall(data, 0, data.length);
+        parcel.setDataPosition(0);
+        T ret = parcel.readParcelable(ParcelUtils.class.getClassLoader());
+        parcel.recycle();
+
+        return ret;
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
new file mode 100644
index 0000000..0968ddc
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that does not run a test case if the device does not have a given feature.
+ */
+public class RequiredFeatureRule implements TestRule {
+    private static final String TAG = "RequiredFeatureRule";
+
+    private final String mFeature;
+    private final boolean mHasFeature;
+
+    public RequiredFeatureRule(String feature) {
+        mFeature = feature;
+        mHasFeature = hasFeature(feature);
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                if (!mHasFeature) {
+                    Log.d(TAG, "skipping "
+                            + description.getClassName() + "#" + description.getMethodName()
+                            + " because device does not have feature '" + mFeature + "'");
+                    return;
+                }
+                base.evaluate();
+            }
+        };
+    }
+
+    public static boolean hasFeature(String feature) {
+        return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature);
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
new file mode 100644
index 0000000..c34cb60
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+public class SettingsUtils {
+    private SettingsUtils() {
+    }
+
+    /**
+     * Put a global setting.
+     */
+    public static void putGlobalSetting(String key, String value) {
+        // Hmm, technically we should escape a value, but if I do like '1', it won't work. ??
+        SystemUtil.runShellCommandForNoOutput("settings put global " + key + " " + value);
+    }
+
+    /**
+     * Put a global setting for the current (foreground) user.
+     */
+    public static void putSecureSetting(String key, String value) {
+        SystemUtil.runShellCommandForNoOutput(
+                "settings --user current put secure " + key + " " + value);
+    }
+}
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 20b4625..64cf944 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
@@ -16,17 +16,25 @@
 
 package com.android.compatibility.common.util;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.MemoryInfo;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
 import android.os.StatFs;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.function.Predicate;
 
 public class SystemUtil {
+    private static final String TAG = "CtsSystemUtil";
+
     public static long getFreeDiskSize(Context context) {
         final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
         return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
@@ -55,6 +63,7 @@
      */
     public static String runShellCommand(Instrumentation instrumentation, String cmd)
             throws IOException {
+        Log.v(TAG, "Running command: " + cmd);
         ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
         byte[] buf = new byte[512];
         int bytesRead;
@@ -66,4 +75,64 @@
         fis.close();
         return stdout.toString();
     }
+
+    /**
+     * Simpler version of {@link #runShellCommand(Instrumentation, String)}.
+     */
+    public static String runShellCommand(String cmd) {
+        try {
+            return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
+        } catch (IOException e) {
+            fail("Failed reading command output: " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Same as {@link #runShellCommand(String)}, with optionally
+     * check the result using {@code resultChecker}.
+     */
+    public static String runShellCommand(String cmd, Predicate<String> resultChecker) {
+        final String result = runShellCommand(cmd);
+        if (resultChecker != null) {
+            assertTrue("Assertion failed. Command was: " + cmd + "\n"
+                    + "Output was:\n" + result,
+                    resultChecker.test(result));
+        }
+        return result;
+    }
+
+    /**
+     * Same as {@link #runShellCommand(String)}, but fails if the output is not empty.
+     */
+    public static String runShellCommandForNoOutput(String cmd) {
+        final String result = runShellCommand(cmd);
+        assertTrue("Command failed. Command was: " + cmd + "\n"
+                + "Didn't expect any output, but the output was:\n" + result,
+                result.length() == 0);
+        return result;
+    }
+
+    /**
+     * Run a command and print the result on logcat.
+     */
+    public static void runCommandAndPrintOnLogcat(String logtag, String cmd) {
+        Log.i(logtag, "Executing: " + cmd);
+        final String output = runShellCommand(cmd);
+        for (String line : output.split("\\n", -1)) {
+            Log.i(logtag, line);
+        }
+    }
+
+    /**
+     * Run a command and return the section matching the patterns.
+     *
+     * @see TextUtils#extractSection
+     */
+    public static String runCommandAndExtractSection(String cmd,
+            String extractionStartRegex, boolean startInclusive,
+            String extractionEndRegex, boolean endInclusive) {
+        return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive,
+                extractionEndRegex, endInclusive);
+    }
 }
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
new file mode 100644
index 0000000..1702875
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import static junit.framework.Assert.fail;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+public class TestUtils {
+    private static final String TAG = "CtsTestUtils";
+
+    private TestUtils() {
+    }
+
+    public static final int DEFAULT_TIMEOUT_SECONDS = 30;
+
+    /** Print an error log and fail. */
+    public static void failWithLog(String message) {
+        Log.e(TAG, message);
+        fail(message);
+    }
+
+    @FunctionalInterface
+    public interface BooleanSupplierWithThrow {
+        boolean getAsBoolean() throws Exception;
+    }
+
+    @FunctionalInterface
+    public interface RunnableWithThrow {
+        void run() throws Exception;
+    }
+
+    /**
+     * Wait until {@code predicate} is satisfied, or fail, with {@link #DEFAULT_TIMEOUT_SECONDS}.
+     */
+    public static void waitUntil(String message, BooleanSupplierWithThrow predicate)
+            throws Exception {
+        waitUntil(message, 0, predicate);
+    }
+
+    /**
+     * Wait until {@code predicate} is satisfied, or fail, with a given timeout.
+     */
+    public static void waitUntil(
+            String message, int timeoutSecond, BooleanSupplierWithThrow predicate)
+            throws Exception {
+        if (timeoutSecond <= 0) {
+            timeoutSecond = DEFAULT_TIMEOUT_SECONDS;
+        }
+        int sleep = 125;
+        final long timeout = SystemClock.uptimeMillis() + timeoutSecond * 1000;
+        while (SystemClock.uptimeMillis() < timeout) {
+            if (predicate.getAsBoolean()) {
+                return; // okay
+            }
+            Thread.sleep(sleep);
+            sleep *= 5;
+            sleep = Math.min(2000, sleep);
+        }
+        failWithLog("Timeout: " + message);
+    }
+
+    /**
+     * Run a Runnable {@code r}, and if it throws, also run {@code onFailure}.
+     */
+    public static void runWithFailureHook(RunnableWithThrow r, RunnableWithThrow onFailure)
+            throws Exception {
+        if (r == null) {
+            throw new NullPointerException("r");
+        }
+        if (onFailure == null) {
+            throw new NullPointerException("onFailure");
+        }
+        try {
+            r.run();
+        } catch (Throwable th) {
+            Log.e(TAG, "Caught exception: " + th, th);
+            onFailure.run();
+            throw th;
+        }
+    }
+}
+
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
new file mode 100644
index 0000000..639dc9c
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.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.compatibility.common.util;
+
+import java.util.regex.Pattern;
+
+public class TextUtils {
+    private TextUtils() {
+    }
+
+    /**
+     * Return the first section in {@code source} between the line matches
+     * {@code extractionStartRegex} and the line matches {@code extractionEndRegex}.
+     */
+    public static String extractSection(String source,
+            String extractionStartRegex, boolean startInclusive,
+            String extractionEndRegex, boolean endInclusive) {
+
+        final Pattern start = Pattern.compile(extractionStartRegex);
+        final Pattern end = Pattern.compile(extractionEndRegex);
+
+        final StringBuilder sb = new StringBuilder();
+        final String[] lines = source.split("\\n", -1);
+
+        int i = 0;
+        for (; i < lines.length; i++) {
+            final String line = lines[i];
+            if (start.matcher(line).matches()) {
+                if (startInclusive) {
+                    sb.append(line);
+                    sb.append('\n');
+                }
+                i++;
+                break;
+            }
+        }
+
+        for (; i < lines.length; i++) {
+            final String line = lines[i];
+            if (end.matcher(line).matches()) {
+                if (endInclusive) {
+                    sb.append(line);
+                    sb.append('\n');
+                }
+                break;
+            }
+            sb.append(line);
+            sb.append('\n');
+        }
+        return sb.toString();
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index 19278d0..05edf1a 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -130,7 +130,9 @@
         bundle.putParcelable(Intent.EXTRA_INTENT, intent);
         bundle.putBinder(StartProvisioningActivity.EXTRA_BOOLEAN_CALLBACK,
                 mProvisioningResultCallback.asBinder());
-        return new Intent(mContext, StartProvisioningActivity.class).putExtras(bundle);
+        return new Intent(mContext, StartProvisioningActivity.class)
+                .putExtras(bundle)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     }
 
     private static void wakeUpAndDismissInsecureKeyguard() {
diff --git a/common/device-side/util/tests/Android.mk b/common/device-side/util/tests/Android.mk
index 12baeb7..a073b9a 100644
--- a/common/device-side/util/tests/Android.mk
+++ b/common/device-side/util/tests/Android.mk
@@ -24,4 +24,6 @@
 
 LOCAL_MODULE := compatibility-device-util-tests
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
index aed5926..b68b34c 100644
--- a/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
+++ b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
@@ -30,11 +30,11 @@
     private static final String PACKAGE = "test.package";
     private static final String INSTRUMENT = "test.package.TestInstrument";
     private static final String MIN_SDK = "8";
-    private static final String TARGET_SDK = "9";
+    private static final String TARGET_SDK = "17";
     private static final String MANIFEST = "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\r\n"
         + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
         + "package=\"test.package\">\r\n"
-        + "  <uses-sdk android:minSdkVersion=\"8\" android:targetSdkVersion=\"9\" />\r\n"
+        + "  <uses-sdk android:minSdkVersion=\"8\" android:targetSdkVersion=\"17\" />\r\n"
         + "%s"
         + "  <application>\r\n"
         + "%s"
diff --git a/common/host-side/tradefed/res/config/common-compatibility-config.xml b/common/host-side/tradefed/res/config/common-compatibility-config.xml
index 2f90e22..f72efb8 100644
--- a/common/host-side/tradefed/res/config/common-compatibility-config.xml
+++ b/common/host-side/tradefed/res/config/common-compatibility-config.xml
@@ -14,10 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Common config for Compatibility suites">
-    <option name="dynamic-sharding" value="false" />
+    <option name="dynamic-sharding" value="true" />
+    <option name="disable-strict-sharding" value="true" />
     <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
-    <test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTest" />
+    <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite" />
+
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
     <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:rerun-from-file:true" />
@@ -28,4 +30,5 @@
     </logger>
     <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
     <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+    <result_reporter class="com.android.tradefed.result.suite.SuiteResultReporter" />
 </configuration>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index 50d1c3a..48ed864 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -16,6 +16,7 @@
 package com.android.compatibility.common.tradefed.build;
 
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.build.VersionedFile;
 import com.android.tradefed.util.FileUtil;
@@ -232,18 +233,6 @@
     }
 
     /**
-     * @return a {@link File} representing the directory to store screenshots taken while testing.
-     * @throws FileNotFoundException if the directory structure is not valid.
-     */
-    public File getScreenshotsDir() throws FileNotFoundException {
-        File screenshotsDir = new File(getResultDir(), "screenshots");
-        if (!screenshotsDir.exists()) {
-            screenshotsDir.mkdirs();
-        }
-        return screenshotsDir;
-    }
-
-    /**
      * @return a {@link File} representing the test modules directory.
      * @throws FileNotFoundException if the directory structure is not valid.
      */
@@ -264,6 +253,12 @@
         }
 
         if (testsDir == null) {
+            if (mBuildInfo instanceof IDeviceBuildInfo) {
+                testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir();
+            }
+        }
+
+        if (testsDir == null) {
             String altTestsDir = System.getenv().get(ALT_HOST_TESTCASE_DIR);
             if (altTestsDir != null) {
                 testsDir = new File(altTestsDir);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
index 3654d78..60400be 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
@@ -16,7 +16,6 @@
 package com.android.compatibility.common.tradefed.build;
 
 import com.android.annotations.VisibleForTesting;
-import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.DeviceBuildInfo;
 import com.android.tradefed.build.IBuildInfo;
@@ -29,10 +28,13 @@
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.FileUtil;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
@@ -42,7 +44,7 @@
  * A simple {@link IBuildProvider} that uses a pre-existing Compatibility install.
  */
 @OptionClass(alias="compatibility-build-provider")
-public class CompatibilityBuildProvider implements IDeviceBuildProvider {
+public class CompatibilityBuildProvider implements IDeviceBuildProvider, IInvocationContextReceiver {
 
     private static final Pattern RELEASE_BUILD = Pattern.compile("^[A-Z]{3}\\d{2}[A-Z]{0,1}$");
     private static final String ROOT_DIR = "ROOT_DIR";
@@ -53,7 +55,7 @@
     private static final String SUITE_PLAN = "SUITE_PLAN";
     private static final String RESULT_DIR = "RESULT_DIR";
     private static final String START_TIME_MS = "START_TIME_MS";
-    private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
+    public static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
 
     /* API Key for compatibility test project, used for dynamic configuration */
     private static final String API_KEY = "AIzaSyAbwX5JRlmsLeygY2WWihpIJPXFLueOQ3U";
@@ -69,25 +71,33 @@
     @Option(name="build-flavor", description="build flavor name to supply.")
     private String mBuildFlavor = null;
 
+    @Option(name="build-target", description="build target name to supply.")
+    private String mBuildTarget = null;
+
     @Option(name="build-attribute", description="build attributes to supply.")
     private Map<String, String> mBuildAttributes = new HashMap<String,String>();
 
     @Option(name="use-device-build-info", description="Bootstrap build info from device")
     private boolean mUseDeviceBuildInfo = false;
 
-    @Option(name="test-tag", description="test tag name to supply.")
-    private String mTestTag = "cts";
-
     @Option(name = "dynamic-config-url",
             description = "Specify the url for override config")
     private String mURL = "https://androidpartner.googleapis.com/v1/dynamicconfig/"
             + "suites/{suite-name}/modules/{module}/version/{version}?key=" + API_KEY;
 
+    @Option(name = "url-suite-name-override",
+            description = "Override the name that should used to replace the {suite-name} "
+                    + "pattern in the dynamic-config-url.")
+    private String mUrlSuiteNameOverride = null;
+
     @Option(name = "plan",
             description = "the test suite plan to run, such as \"everything\" or \"cts\"",
             importance = Importance.ALWAYS)
     private String mSuitePlan;
 
+    private String mTestTag;
+    private File mArtificialRootDir;
+
     /**
      * Util method to inject build attributes into supplied {@link IBuildInfo}
      * @param buildInfo
@@ -96,13 +106,24 @@
         for (Map.Entry<String, String> entry : mBuildAttributes.entrySet()) {
             buildInfo.addBuildAttribute(entry.getKey(), entry.getValue());
         }
+        if (mTestTag != null) {
+            buildInfo.setTestTag(mTestTag);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public IBuildInfo getBuild() {
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mTestTag = invocationContext.getTestTag();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IBuildInfo getBuild() throws BuildRetrievalError {
         // Create a blank BuildInfo which will get populated later.
         String version = null;
         if (mBuildId != null) {
@@ -113,7 +134,7 @@
                 version = IBuildInfo.UNKNOWN_BUILD_ID;
             }
         }
-        IBuildInfo ctsBuild = new BuildInfo(version, mTestTag);
+        IBuildInfo ctsBuild = new DeviceBuildInfo(version, mBuildTarget);
         if (mBranch  != null) {
             ctsBuild.setBuildBranch(mBranch);
         }
@@ -136,9 +157,18 @@
             // build info fields
             return getBuild();
         } else {
-            String buildId = device.getBuildId();
-            String buildFlavor = device.getBuildFlavor();
-            IBuildInfo info = new DeviceBuildInfo(buildId, mTestTag);
+            if (mBuildId == null) {
+                mBuildId = device.getBuildId();
+            }
+            if (mBuildFlavor == null) {
+                mBuildFlavor = device.getBuildFlavor();
+            }
+            if (mBuildTarget == null) {
+                String name = device.getProperty("ro.product.name");
+                String variant = device.getProperty("ro.build.type");
+                mBuildTarget = name + "-" + variant;
+            }
+            IBuildInfo info = new DeviceBuildInfo(mBuildId, mBuildTarget);
             if (mBranch == null) {
                 // if branch is not specified via param, make a pseudo branch name based on platform
                 // version and product info from device
@@ -149,7 +179,7 @@
                         device.getProperty("ro.build.version.release"));
             }
             info.setBuildBranch(mBranch);
-            info.setBuildFlavor(buildFlavor);
+            info.setBuildFlavor(mBuildFlavor);
             String buildAlias = device.getBuildAlias();
             if (RELEASE_BUILD.matcher(buildAlias).matches()) {
                 info.addBuildAttribute("build_alias", buildAlias);
@@ -184,6 +214,7 @@
         } else {
             info.cleanUp();
         }
+        FileUtil.recursiveDelete(mArtificialRootDir);
     }
 
     private void addCompatibilitySuiteInfo(IBuildInfo info) {
@@ -208,13 +239,17 @@
         info.addBuildAttribute(ROOT_DIR, rootDir.getAbsolutePath());
         // For DeviceBuildInfo we populate the testsDir folder of the build info.
         if (info instanceof IDeviceBuildInfo) {
-            File testDir =  new File(rootDir, String.format("android-%s/testcases/",
+            File testDir = new File(rootDir, String.format("android-%s/testcases/",
                     getSuiteInfoName().toLowerCase()));
             ((IDeviceBuildInfo) info).setTestsDir(testDir, "0");
         }
         if (mURL != null && !mURL.isEmpty()) {
+            String suiteName = mUrlSuiteNameOverride;
+            if (suiteName == null) {
+                suiteName = getSuiteInfoName();
+            }
             info.addBuildAttribute(DYNAMIC_CONFIG_OVERRIDE_URL,
-                    mURL.replace("{suite-name}", getSuiteInfoName()));
+                    mURL.replace("{suite-name}", suiteName));
         }
     }
 
@@ -223,7 +258,23 @@
      */
     @VisibleForTesting
     String getRootDirPath() {
-        return System.getProperty(String.format("%s_ROOT", getSuiteInfoName()));
+        String varName = String.format("%s_ROOT", getSuiteInfoName());
+        String rootDirVariable = System.getProperty(varName);
+        if (rootDirVariable != null) {
+            return rootDirVariable;
+        }
+        // Create an artificial root dir, we are most likely running from Tradefed directly.
+        try {
+            mArtificialRootDir = FileUtil.createTempDir(
+                    String.format("%s-root-dir", getSuiteInfoName()));
+            new File(mArtificialRootDir, String.format("android-%s/testcases",
+                    getSuiteInfoName().toLowerCase())).mkdirs();
+            return mArtificialRootDir.getAbsolutePath();
+        } catch (IOException e) {
+            throw new RuntimeException(
+                    String.format("%s was not set, and couldn't create an artificial one.",
+                            varName));
+        }
     }
 
     /**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 32e6197..5dcf2dd 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -22,6 +22,7 @@
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.ResultHandler;
 import com.android.compatibility.common.util.TestStatus;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.command.Console;
 import com.android.tradefed.config.ArgsOptionParser;
 import com.android.tradefed.config.ConfigurationException;
@@ -195,7 +196,12 @@
                 "\t\t\t--screenshot-on-failure: Capture a screenshot when a test fails"
                 + LINE_SEPARATOR +
                 "\t\t\t--shard-count <shards>: Shards a run into the given number of independent " +
-                "chunks, to run on multiple devices in parallel." + LINE_SEPARATOR;
+                "chunks, to run on multiple devices in parallel." + LINE_SEPARATOR +
+                "\t ----- In order to retry a previous run -----" + LINE_SEPARATOR +
+                "\tretry --retry <session id to retry> [--retry-type <FAILED | NOT_EXECUTED>]"
+                + LINE_SEPARATOR +
+                "\t\tWithout --retry-type, retry will run both FAIL and NOT_EXECUTED tests"
+                + LINE_SEPARATOR;
         commandHelp.put(RUN_PATTERN, combinedRunHelp);
 
         commandHelp.put(ADD_PATTERN, String.format(
@@ -421,8 +427,12 @@
 
     private CompatibilityBuildHelper getBuildHelper() {
         if (mBuildHelper == null) {
-            CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
-            mBuildHelper = new CompatibilityBuildHelper(buildProvider.getBuild());
+            try {
+                CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
+                mBuildHelper = new CompatibilityBuildHelper(buildProvider.getBuild());
+            } catch (BuildRetrievalError e) {
+                e.printStackTrace();
+            }
         }
         return mBuildHelper;
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
index 4944da2..9c79abd 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
@@ -17,13 +17,13 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.TimeUtil;
 
 import java.util.Map;
@@ -84,7 +84,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testStarted(TestIdentifier test) {
+    public void testStarted(TestDescription test) {
         mTestFailed = false;
         mCurrentTestNum++;
     }
@@ -93,7 +93,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testFailed(TestIdentifier test, String trace) {
+    public void testFailed(TestDescription test, String trace) {
         logProgress("%s fail: %s", test, trace);
         mTestFailed = true;
         mFailedTests++;
@@ -103,7 +103,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testIgnored(TestIdentifier test) {
+    public void testIgnored(TestDescription test) {
         mCurrentTestNum--;
         logProgress("%s ignore", test);
     }
@@ -112,7 +112,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testAssumptionFailure(TestIdentifier test, String trace) {
+    public void testAssumptionFailure(TestDescription test, String trace) {
         logProgress("%s skip", test);
     }
 
@@ -120,7 +120,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+    public void testEnded(TestDescription test, Map<String, String> testMetrics) {
         if (!mTestFailed) {
             logProgress("%s pass", test);
             mPassedTests++;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
index 6894c2d..3f51cca 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
@@ -17,7 +17,6 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.json.stream.JsonWriter;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
@@ -25,6 +24,7 @@
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.TestDescription;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -96,7 +96,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testStarted(TestIdentifier test) {
+    public void testStarted(TestDescription test) {
         mStartTime = System.currentTimeMillis();
         mTestFailed = false;
     }
@@ -105,7 +105,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testFailed(TestIdentifier test, String trace) {
+    public void testFailed(TestDescription test, String trace) {
         mTestFailed = true;
     }
 
@@ -113,7 +113,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testIgnored(TestIdentifier test) {
+    public void testIgnored(TestDescription test) {
         mTestFailed = true;
     }
 
@@ -121,7 +121,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testAssumptionFailure(TestIdentifier test, String trace) {
+    public void testAssumptionFailure(TestDescription test, String trace) {
         mTestFailed = true;
     }
 
@@ -129,7 +129,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+    public void testEnded(TestDescription test, Map<String, String> testMetrics) {
         long duration = System.currentTimeMillis() - mStartTime;
         if (mTestFailed && !mIncludeFailures) {
             return;
@@ -163,7 +163,7 @@
         long seconds;
     }
 
-    private static String buildTestId(TestIdentifier test) {
+    private static String buildTestId(TestDescription test) {
         return String.format("%s.%s", test.getClassName(), test.getTestName());
     }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
index 8140887..878d0fd 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
@@ -16,12 +16,12 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.testtype.IModuleDef;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestSummary;
 
 import java.util.Map;
@@ -67,7 +67,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testStarted(TestIdentifier test) {
+    public void testStarted(TestDescription test) {
         CLog.d("ModuleListener.testStarted(%s)", test.toString());
         mListener.testStarted(test);
     }
@@ -76,7 +76,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+    public void testEnded(TestDescription test, Map<String, String> metrics) {
         CLog.d("ModuleListener.testEnded(%s, %s)", test.toString(), metrics.toString());
         mListener.testEnded(test, metrics);
     }
@@ -85,7 +85,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testIgnored(TestIdentifier test) {
+    public void testIgnored(TestDescription test) {
         CLog.d("ModuleListener.testIgnored(%s)", test.toString());
         mListener.testIgnored(test);
     }
@@ -94,7 +94,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testFailed(TestIdentifier test, String trace) {
+    public void testFailed(TestDescription test, String trace) {
         CLog.d("ModuleListener.testFailed(%s, %s)", test.toString(), trace);
         mListener.testFailed(test, trace);
     }
@@ -103,7 +103,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testAssumptionFailure(TestIdentifier test, String trace) {
+    public void testAssumptionFailure(TestDescription test, String trace) {
         CLog.d("ModuleListener.testAssumptionFailure(%s, %s)", test.toString(), trace);
         mListener.testAssumptionFailure(test, trace);
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 5427c26..1e0782d 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -16,7 +16,8 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.tradefed.util.RetryType;
 import com.android.compatibility.common.util.ChecksumReporter;
 import com.android.compatibility.common.util.DeviceInfo;
@@ -31,7 +32,6 @@
 import com.android.compatibility.common.util.ResultUploader;
 import com.android.compatibility.common.util.TestStatus;
 import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
@@ -39,6 +39,7 @@
 import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.ILogSaver;
 import com.android.tradefed.result.ILogSaverListener;
 import com.android.tradefed.result.IShardableListener;
@@ -48,7 +49,9 @@
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.LogFile;
 import com.android.tradefed.result.LogFileSaver;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.result.suite.SuiteResultReporter;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 import com.android.tradefed.util.TimeUtil;
@@ -63,6 +66,8 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -85,6 +90,7 @@
     private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
     private static final String CTS_PREFIX = "cts:";
     private static final String BUILD_INFO = CTS_PREFIX + "build_";
+    private static final String LATEST_LINK_NAME = "latest";
 
     public static final String BUILD_BRAND = "build_brand";
     public static final String BUILD_DEVICE = "build_device";
@@ -101,14 +107,14 @@
             ResultHandler.FAILURE_REPORT_NAME,
             "diffs");
 
-    @Option(name = CompatibilityTest.RETRY_OPTION,
+    @Option(name = RetryFactoryTest.RETRY_OPTION,
             shortName = 'r',
             description = "retry a previous session.",
             importance = Importance.IF_UNSET)
     private Integer mRetrySessionId = null;
 
-    @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
-            description = "used with " + CompatibilityTest.RETRY_OPTION
+    @Option(name = RetryFactoryTest.RETRY_TYPE_OPTION,
+            description = "used with " + RetryFactoryTest.RETRY_OPTION
             + ", retry tests of a certain status. Possible values include \"failed\", "
             + "\"not_executed\", and \"custom\".",
             importance = Importance.IF_UNSET)
@@ -321,7 +327,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testStarted(TestIdentifier test) {
+    public void testStarted(TestDescription test) {
         mCurrentCaseResult = mCurrentModuleResult.getOrCreateResult(test.getClassName());
         mCurrentResult = mCurrentCaseResult.getOrCreateResult(test.getTestName().trim());
         if (mCurrentResult.isRetry()) {
@@ -334,7 +340,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+    public void testEnded(TestDescription test, Map<String, String> metrics) {
         if (mCurrentResult.getResultStatus() == TestStatus.FAIL) {
             // Test has previously failed.
             return;
@@ -364,7 +370,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testIgnored(TestIdentifier test) {
+    public void testIgnored(TestDescription test) {
         mCurrentResult.skipped();
     }
 
@@ -372,7 +378,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testFailed(TestIdentifier test, String trace) {
+    public void testFailed(TestDescription test, String trace) {
         mCurrentResult.failed(trace);
     }
 
@@ -380,7 +386,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testAssumptionFailure(TestIdentifier test, String trace) {
+    public void testAssumptionFailure(TestDescription test, String trace) {
         mCurrentResult.skipped();
     }
 
@@ -454,10 +460,24 @@
      */
     @Override
     public void putSummary(List<TestSummary> summaries) {
-        // This is safe to be invoked on either the master or a shard ResultReporter,
-        // but the value added to the report will be that of the master ResultReporter.
-        if (summaries.size() > 0) {
-            mReferenceUrl = summaries.get(0).getSummary().getString();
+        for (TestSummary summary : summaries) {
+            // If one summary is from SuiteResultReporter, log it as an extra file.
+            if (SuiteResultReporter.SUITE_REPORTER_SOURCE.equals(summary.getSource())) {
+                File summaryFile = null;
+                try {
+                    summaryFile = FileUtil.createTempFile("summary", ".txt");
+                    FileUtil.writeToFile(summary.getSummary().getString(), summaryFile);
+                    try (InputStreamSource stream = new FileInputStreamSource(summaryFile)) {
+                        testLog("summary", LogDataType.TEXT, stream);
+                    }
+                } catch (IOException e) {
+                    CLog.e(e);
+                } finally {
+                    FileUtil.deleteFile(summaryFile);
+                }
+            } else if (mReferenceUrl == null && summary.getSummary().getString() != null) {
+                mReferenceUrl = summary.getSummary().getString();
+            }
         }
     }
 
@@ -544,6 +564,16 @@
             info("Test Logs: %s", mLogDir.getCanonicalPath());
             debug("Full Result: %s", zippedResults.getCanonicalPath());
 
+            Path latestLink = createLatestLinkDirectory(mResultDir.toPath());
+            if (latestLink != null) {
+                info("Latest results link: " + latestLink.toAbsolutePath());
+            }
+
+            latestLink = createLatestLinkDirectory(mLogDir.toPath());
+            if (latestLink != null) {
+                info("Latest logs link: " + latestLink.toAbsolutePath());
+            }
+
             saveLog(resultFile, zippedResults);
 
             uploadResult(resultFile);
@@ -560,6 +590,30 @@
                 moduleProgress);
     }
 
+    private Path createLatestLinkDirectory(Path directory) {
+        Path link = null;
+
+        Path parent = directory.getParent();
+
+        if (parent != null) {
+            link = parent.resolve(LATEST_LINK_NAME);
+            try {
+                // if latest already exists, we have to remove it before creating
+                Files.deleteIfExists(link);
+                Files.createSymbolicLink(link, directory);
+            } catch (IOException ioe) {
+                CLog.e("Exception while attempting to create 'latest' link to: [%s]",
+                    directory);
+                CLog.e(ioe);
+                return null;
+            } catch (UnsupportedOperationException uoe) {
+                CLog.e("Failed to create 'latest' symbolic link - unsupported operation");
+                return null;
+            }
+        }
+        return link;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -584,6 +638,7 @@
             // Handle device info file case
             testLogDeviceInfo(name, stream);
         } else {
+            // Handle default case
             try {
                 File logFile = null;
                 if (mCompressLogs) {
@@ -815,11 +870,11 @@
         }
         return !(RetryType.FAILED.equals(mRetryType)
                 || RetryType.CUSTOM.equals(mRetryType)
-                || args.contains(CompatibilityTest.INCLUDE_FILTER_OPTION)
-                || args.contains(CompatibilityTest.EXCLUDE_FILTER_OPTION)
-                || args.contains(CompatibilityTest.SUBPLAN_OPTION)
+                || args.contains(CompatibilityTestSuite.INCLUDE_FILTER_OPTION)
+                || args.contains(CompatibilityTestSuite.EXCLUDE_FILTER_OPTION)
+                || args.contains(CompatibilityTestSuite.SUBPLAN_OPTION)
                 || args.matches(String.format(".* (-%s|--%s) .*",
-                CompatibilityTest.TEST_OPTION_SHORT_NAME, CompatibilityTest.TEST_OPTION)));
+                CompatibilityTestSuite.TEST_OPTION_SHORT_NAME, CompatibilityTestSuite.TEST_OPTION)));
     }
 
     /**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
index 9c87a75..87f2436 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
@@ -16,9 +16,9 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.tradefed.testtype.ISubPlan;
 import com.android.compatibility.common.tradefed.testtype.SubPlan;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.tradefed.util.OptionHelper;
 import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
@@ -33,8 +33,8 @@
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -43,13 +43,13 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -90,32 +90,32 @@
             importance=Importance.IF_UNSET)
     private Set<String> mResultTypes = new HashSet<String>();
 
-    @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+    @Option(name = CompatibilityTestSuite.INCLUDE_FILTER_OPTION,
             description = "the include module filters to apply.",
             importance = Importance.NEVER)
     private Set<String> mIncludeFilters = new HashSet<String>();
 
-    @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+    @Option(name = CompatibilityTestSuite.EXCLUDE_FILTER_OPTION,
             description = "the exclude module filters to apply.",
             importance = Importance.NEVER)
     private Set<String> mExcludeFilters = new HashSet<String>();
 
-    @Option(name = CompatibilityTest.MODULE_OPTION, shortName = 'm',
+    @Option(name = CompatibilityTestSuite.MODULE_OPTION, shortName = 'm',
             description = "the test module to run.",
             importance = Importance.NEVER)
     private String mModuleName = null;
 
-    @Option(name = CompatibilityTest.TEST_OPTION, shortName = 't',
+    @Option(name = CompatibilityTestSuite.TEST_OPTION, shortName = 't',
             description = "the test to run.",
             importance = Importance.NEVER)
     private String mTestName = null;
 
-    @Option(name = CompatibilityTest.ABI_OPTION, shortName = 'a',
+    @Option(name = CompatibilityTestSuite.ABI_OPTION, shortName = 'a',
             description = "the abi to test.",
             importance = Importance.NEVER)
     private String mAbiName = null;
 
-    @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+    @Option(name = CompatibilityTestSuite.SUBPLAN_OPTION,
             description = "the subplan used in the previous session",
             importance = Importance.NEVER)
     private String mLastSubPlan;
@@ -159,7 +159,7 @@
             throw new RuntimeException(
                     String.format("Unable to find or parse subplan %s", name), e);
         } finally {
-            StreamUtil.closeStream(subPlanInputStream);
+            StreamUtil.close(subPlanInputStream);
         }
     }
 
@@ -207,7 +207,7 @@
      * Create a subplan derived from a result.
      * <p/>
      * {@link Option} values must be set before this is called.
-     * @param build
+     * @param buildHelper
      * @return subplan
      * @throws ConfigurationException
      */
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java
new file mode 100644
index 0000000..13eed58
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationResultXml.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.result.suite;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.result.suite.SuiteResultHolder;
+import com.android.tradefed.result.suite.XmlSuiteResultFormatter;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Utility class to save a Compatibility run as an XML.
+ */
+public class CertificationResultXml extends XmlSuiteResultFormatter {
+
+    private static final String LOG_URL_ATTR = "log_url";
+    private static final String REPORT_VERSION_ATTR = "report_version";
+    private static final String REFERENCE_URL_ATTR = "reference_url";
+    private static final String RESULT_FILE_VERSION = "5.0";
+    private static final String SUITE_NAME_ATTR = "suite_name";
+    private static final String SUITE_PLAN_ATTR = "suite_plan";
+    private static final String SUITE_VERSION_ATTR = "suite_version";
+    private static final String SUITE_BUILD_ATTR = "suite_build_number";
+
+    private String mSuiteName;
+    private String mSuiteVersion;
+    private String mSuitePlan;
+    private String mSuiteBuild;
+    private String mReferenceUrl;
+    private String mLogUrl;
+
+    /**
+     * Create an XML report specialized for the Compatibility Test cases.
+     */
+    public CertificationResultXml(String suiteName,
+            String suiteVersion,
+            String suitePlan,
+            String suiteBuild,
+            String referenceUrl,
+            String logUrl) {
+        mSuiteName = suiteName;
+        mSuiteVersion = suiteVersion;
+        mSuitePlan = suitePlan;
+        mSuiteBuild = suiteBuild;
+        mReferenceUrl = referenceUrl;
+        mLogUrl = logUrl;
+    }
+
+    /**
+     * Add Compatibility specific attributes.
+     */
+    @Override
+    public void addSuiteAttributes(XmlSerializer serializer)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        serializer.attribute(NS, SUITE_NAME_ATTR, mSuiteName);
+        serializer.attribute(NS, SUITE_VERSION_ATTR, mSuiteVersion);
+        serializer.attribute(NS, SUITE_PLAN_ATTR, mSuitePlan);
+        serializer.attribute(NS, SUITE_BUILD_ATTR, mSuiteBuild);
+        serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
+
+        if (mReferenceUrl != null) {
+            serializer.attribute(NS, REFERENCE_URL_ATTR, mReferenceUrl);
+        }
+
+        if (mLogUrl != null) {
+            serializer.attribute(NS, LOG_URL_ATTR, mLogUrl);
+        }
+    }
+
+    /**
+     * Add compatibility specific build info attributes.
+     */
+    @Override
+    public void addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        for (IBuildInfo build : holder.context.getBuildInfos()) {
+            for (String key : build.getBuildAttributes().keySet()) {
+                if (key.startsWith(getAttributesPrefix())) {
+                    String newKey = key.split(getAttributesPrefix())[1];
+                    serializer.attribute(NS, newKey, build.getBuildAttributes().get(key));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the compatibility prefix for attributes.
+     */
+    public String getAttributesPrefix() {
+        return "cts:";
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
new file mode 100644
index 0000000..7934abd
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.result.suite;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.DeviceInfo;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.compatibility.common.util.ResultUploader;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ILogSaver;
+import com.android.tradefed.result.ILogSaverListener;
+import com.android.tradefed.result.ITestSummaryListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFile;
+import com.android.tradefed.result.LogFileSaver;
+import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.result.suite.IFormatterGenerator;
+import com.android.tradefed.result.suite.SuiteResultReporter;
+import com.android.tradefed.result.suite.XmlFormattedGeneratorReporter;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.ZipUtil;
+
+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 java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+/**
+ * Extension of {@link XmlFormattedGeneratorReporter} and {@link SuiteResultReporter} to handle
+ * Compatibility specific format and operations.
+ */
+public class CertificationSuiteResultReporter extends XmlFormattedGeneratorReporter
+        implements ILogSaverListener, ITestSummaryListener {
+
+    public static final String LATEST_LINK_NAME = "latest";
+    public static final String SUMMARY_FILE = "invocation_summary.txt";
+    public static final String FAILURE_REPORT_NAME = "test_result_failures_suite.html";
+    public static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
+
+    @Option(name = "result-server", description = "Server to publish test results.")
+    private String mResultServer;
+
+    @Option(name = "disable-result-posting", description = "Disable result posting into report server.")
+    private boolean mDisableResultPosting = false;
+
+    @Option(name = "include-test-log-tags", description = "Include test log tags in report.")
+    private boolean mIncludeTestLogTags = false;
+
+    @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
+    private boolean mUseLogSaver = false;
+
+    @Option(name = "compress-logs", description = "Whether logs will be saved with compression")
+    private boolean mCompressLogs = true;
+
+    public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
+    @Option(name = INCLUDE_HTML_IN_ZIP,
+            description = "Whether failure summary report is included in the zip fie.")
+    private boolean mIncludeHtml = false;
+
+    private CompatibilityBuildHelper mBuildHelper;
+
+    /** The directory containing the results */
+    private File mResultDir = null;
+    /** The directory containing the logs */
+    private File mLogDir = null;
+
+    private ResultUploader mUploader;
+
+    private LogFileSaver mTestLogSaver;
+    /** Log saver to receive when files are logged */
+    private ILogSaver mLogSaver;
+
+    private String mReferenceUrl;
+
+    private Map<String, String> mLoggedFiles;
+
+    private static final String[] RESULT_RESOURCES = {
+        "compatibility_result.css",
+        "compatibility_result.xsd",
+        "compatibility_result.xsl",
+        "logo.png"
+    };
+
+    public CertificationSuiteResultReporter() {
+        super();
+        mLoggedFiles = new LinkedHashMap<>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final void invocationStarted(IInvocationContext context) {
+        super.invocationStarted(context);
+
+        if (mBuildHelper == null) {
+            mBuildHelper = new CompatibilityBuildHelper(getPrimaryBuildInfo());
+        }
+        if (mResultDir == null) {
+            initializeResultDirectories();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String name, LogDataType type, InputStreamSource stream) {
+        if (name.endsWith(DeviceInfo.FILE_SUFFIX)) {
+            // Handle device info file case
+            testLogDeviceInfo(name, stream);
+            return;
+        }
+        try {
+            File logFile = null;
+            if (mCompressLogs) {
+                try (InputStream inputStream = stream.createInputStream()) {
+                    logFile = mTestLogSaver.saveAndGZipLogData(name, type, inputStream);
+                }
+            } else {
+                try (InputStream inputStream = stream.createInputStream()) {
+                    logFile = mTestLogSaver.saveLogData(name, type, inputStream);
+                }
+            }
+            CLog.d("Saved logs for %s in %s", name, logFile.getAbsolutePath());
+        } catch (IOException e) {
+            CLog.e("Failed to write log for %s", name);
+            CLog.e(e);
+        }
+    }
+
+    /** Write device-info files to the result, invoked only by the master result reporter */
+    private void testLogDeviceInfo(String name, InputStreamSource stream) {
+        try {
+            File ediDir = new File(mResultDir, DeviceInfo.RESULT_DIR_NAME);
+            ediDir.mkdirs();
+            File ediFile = new File(ediDir, name);
+            if (!ediFile.exists()) {
+                // only write this file to the results if not already present
+                FileUtil.writeToFile(stream.createInputStream(), ediFile);
+            }
+        } catch (IOException e) {
+            CLog.w("Failed to write device info %s to result", name);
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
+            LogFile logFile) {
+        if (mIncludeTestLogTags) {
+            switch (dataType) {
+                case BUGREPORT:
+                case LOGCAT:
+                case PNG:
+                    mLoggedFiles.put(dataName, logFile.getUrl());
+                    break;
+                default:
+                    // Do nothing
+                    break;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void putSummary(List<TestSummary> summaries) {
+        for (TestSummary summary : summaries) {
+            if (mReferenceUrl == null && summary.getSummary().getString() != null) {
+                mReferenceUrl = summary.getSummary().getString();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setLogSaver(ILogSaver saver) {
+        mLogSaver = saver;
+    }
+
+    /**
+     * Create directory structure where results and logs will be written.
+     */
+    private void initializeResultDirectories() {
+        CLog.d("Initializing result directory");
+
+        try {
+            mResultDir = mBuildHelper.getResultDir();
+            if (mResultDir != null) {
+                mResultDir.mkdirs();
+            }
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        if (mResultDir == null) {
+            throw new RuntimeException("Result Directory was not created");
+        }
+        if (!mResultDir.exists()) {
+            throw new RuntimeException("Result Directory was not created: " +
+                    mResultDir.getAbsolutePath());
+        }
+
+        CLog.e("Results Directory: " + mResultDir.getAbsolutePath());
+
+        mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
+        try {
+            mLogDir = new File(mBuildHelper.getLogsDir(),
+                    CompatibilityBuildHelper.getDirSuffix(mBuildHelper.getStartTime()));
+        } catch (FileNotFoundException e) {
+            CLog.e(e);
+        }
+        if (mLogDir != null && mLogDir.mkdirs()) {
+            CLog.d("Created log dir %s", mLogDir.getAbsolutePath());
+        }
+        if (mLogDir == null || !mLogDir.exists()) {
+            throw new IllegalArgumentException(String.format("Could not create log dir %s",
+                    mLogDir.getAbsolutePath()));
+        }
+        if (mTestLogSaver == null) {
+            mTestLogSaver = new LogFileSaver(mLogDir);
+        }
+    }
+
+    @Override
+    public IFormatterGenerator createFormatter() {
+        return new CertificationResultXml(mBuildHelper.getSuiteName(),
+                mBuildHelper.getSuiteVersion(),
+                mBuildHelper.getSuitePlan(),
+                mBuildHelper.getSuiteBuild(),
+                mReferenceUrl,
+                getLogUrl());
+    }
+
+    @Override
+    public void preFormattingSetup(IFormatterGenerator formater) {
+        super.preFormattingSetup(formater);
+        // Log the summary
+        TestSummary summary = getSummary();
+        try {
+            File summaryFile = new File(mResultDir, SUMMARY_FILE);
+            FileUtil.writeToFile(summary.getSummary().toString(), summaryFile);
+        } catch (IOException e) {
+            CLog.e("Failed to save the summary.");
+            CLog.e(e);
+        }
+
+        copyDynamicConfigFiles();
+        copyFormattingFiles(mResultDir, mBuildHelper.getSuiteName());
+    }
+
+    @Override
+    public File createResultDir() throws IOException {
+        return mResultDir;
+    }
+
+    @Override
+    public void postFormattingStep(File resultDir, File reportFile) {
+        super.postFormattingStep(resultDir,reportFile);
+
+        File failureReport = null;
+        if (mIncludeHtml) {
+            // Create the html report before the zip file.
+            failureReport = createFailureReport(reportFile);
+        }
+        File zippedResults = zipResults(mResultDir);
+        // TODO: calculate results checksum file
+        if (!mIncludeHtml) {
+            // Create failure report after zip file so extra data is not uploaded
+            failureReport = createFailureReport(reportFile);
+        }
+        try {
+            if (failureReport.exists()) {
+                CLog.i("Test Result: %s", failureReport.getCanonicalPath());
+            } else {
+                CLog.i("Test Result: %s", reportFile.getCanonicalPath());
+            }
+            Path latestLink = createLatestLinkDirectory(mResultDir.toPath());
+            if (latestLink != null) {
+                CLog.i("Latest results link: " + latestLink.toAbsolutePath());
+            }
+
+            latestLink = createLatestLinkDirectory(mLogDir.toPath());
+            if (latestLink != null) {
+                CLog.i("Latest logs link: " + latestLink.toAbsolutePath());
+            }
+
+            saveLog(reportFile, zippedResults);
+        } catch (IOException e) {
+            CLog.e("Error when handling the post processing of results file:");
+            CLog.e(e);
+        }
+
+        uploadResult(reportFile);
+    }
+
+    /**
+     * Return the path in which log saver persists log files or null if
+     * logSaver is not enabled.
+     */
+    private String getLogUrl() {
+        if (!mUseLogSaver || mLogSaver == null) {
+            return null;
+        }
+
+        return mLogSaver.getLogReportDir().getUrl();
+    }
+
+    /**
+     * Update the "latest" symlink to the newest result directory. CTS specific.
+     */
+    private Path createLatestLinkDirectory(Path directory) {
+        Path link = null;
+
+        Path parent = directory.getParent();
+
+        if (parent != null) {
+            link = parent.resolve(LATEST_LINK_NAME);
+            try {
+                // if latest already exists, we have to remove it before creating
+                Files.deleteIfExists(link);
+                Files.createSymbolicLink(link, directory);
+            } catch (IOException ioe) {
+                CLog.e("Exception while attempting to create 'latest' link to: [%s]",
+                    directory);
+                CLog.e(ioe);
+                return null;
+            } catch (UnsupportedOperationException uoe) {
+                CLog.e("Failed to create 'latest' symbolic link - unsupported operation");
+                return null;
+            }
+        }
+        return link;
+    }
+
+    /**
+     * move the dynamic config files to the results directory
+     */
+    private void copyDynamicConfigFiles() {
+        File configDir = new File(mResultDir, "config");
+        if (!configDir.mkdir()) {
+            CLog.w("Failed to make dynamic config directory \"%s\" in the result",
+                    configDir.getAbsolutePath());
+        }
+
+        Set<String> uniqueModules = new HashSet<>();
+        // Check each build of the invocation, in case of multi-device invocation.
+        for (IBuildInfo buildInfo : getInvocationContext().getBuildInfos()) {
+            CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
+            Map<String, File> dcFiles = helper.getDynamicConfigFiles();
+            for (String moduleName : dcFiles.keySet()) {
+                File srcFile = dcFiles.get(moduleName);
+                if (!uniqueModules.contains(moduleName)) {
+                    // have not seen config for this module yet, copy into result
+                    File destFile = new File(configDir, moduleName + ".dynamic");
+                    try {
+                        FileUtil.copyFile(srcFile, destFile);
+                        uniqueModules.add(moduleName); // Add to uniqueModules if copy succeeds
+                    } catch (IOException e) {
+                        CLog.w("Failure when copying config file \"%s\" to \"%s\" for module %s",
+                                srcFile.getAbsolutePath(), destFile.getAbsolutePath(), moduleName);
+                        CLog.e(e);
+                    }
+                }
+                FileUtil.deleteFile(srcFile);
+            }
+        }
+    }
+
+    /**
+     * Copy the xml formatting files stored in this jar to the results directory. CTS specific.
+     *
+     * @param resultsDir
+     */
+    private void copyFormattingFiles(File resultsDir, String suiteName) {
+        for (String resultFileName : RESULT_RESOURCES) {
+            InputStream configStream = CertificationResultXml.class.getResourceAsStream(
+                    String.format("/report/%s-%s", suiteName, resultFileName));
+            if (configStream == null) {
+                // If suite specific files are not available, fallback to common.
+                configStream = CertificationResultXml.class.getResourceAsStream(
+                    String.format("/report/%s", resultFileName));
+            }
+            if (configStream != null) {
+                File resultFile = new File(resultsDir, resultFileName);
+                try {
+                    FileUtil.writeToFile(configStream, resultFile);
+                } catch (IOException e) {
+                    CLog.w("Failed to write %s to file", resultFileName);
+                }
+            } else {
+                CLog.w("Failed to load %s from jar", resultFileName);
+            }
+        }
+    }
+
+    /**
+     * When enabled, save log data using log saver
+     */
+    private void saveLog(File resultFile, File zippedResults) throws IOException {
+        if (!mUseLogSaver) {
+            return;
+        }
+
+        FileInputStream fis = null;
+        LogFile logFile = null;
+        try {
+            fis = new FileInputStream(resultFile);
+            logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
+            CLog.d("Result XML URL: %s", logFile.getUrl());
+        } catch (IOException ioe) {
+            CLog.e("error saving XML with log saver");
+            CLog.e(ioe);
+        } finally {
+            StreamUtil.close(fis);
+        }
+        // Save the full results folder.
+        if (zippedResults != null) {
+            FileInputStream zipResultStream = null;
+            try {
+                zipResultStream = new FileInputStream(zippedResults);
+                logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
+                CLog.d("Result zip URL: %s", logFile.getUrl());
+            } finally {
+                StreamUtil.close(zipResultStream);
+            }
+        }
+    }
+
+    /**
+     * Zip the contents of the given results directory. CTS specific.
+     *
+     * @param resultsDir
+     */
+    private static File zipResults(File resultsDir) {
+        File zipResultFile = null;
+        try {
+            // create a file in parent directory, with same name as resultsDir
+            zipResultFile = new File(resultsDir.getParent(), String.format("%s.zip",
+                    resultsDir.getName()));
+            ZipUtil.createZip(resultsDir, zipResultFile);
+        } catch (IOException e) {
+            CLog.w("Failed to create zip for %s", resultsDir.getName());
+        }
+        return zipResultFile;
+    }
+
+    /**
+     * When enabled, upload the result to a server. CTS specific.
+     */
+    private void uploadResult(File resultFile) {
+        if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
+            try {
+                CLog.d("Result Server: %d", mUploader.uploadResult(resultFile, mReferenceUrl));
+            } catch (IOException ioe) {
+                CLog.e("IOException while uploading result.");
+                CLog.e(ioe);
+            }
+        }
+    }
+
+    /**
+     * Generate html report listing an failed tests. CTS specific.
+     */
+    private File createFailureReport(File inputXml) {
+        File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME);
+        try (InputStream xslStream = ResultHandler.class.getResourceAsStream(
+                String.format("/report/%s", FAILURE_XSL_FILE_NAME));
+             OutputStream outputStream = new FileOutputStream(failureReport)) {
+
+            Transformer transformer = TransformerFactory.newInstance().newTransformer(
+                    new StreamSource(xslStream));
+            transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
+        } catch (IOException | TransformerException ignored) { }
+        return failureReport;
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
index 1673866..dbd202b 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
@@ -17,7 +17,6 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
@@ -25,6 +24,7 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
@@ -57,9 +57,9 @@
     @Option(name = "throw-error", description = "Whether to throw error for device test failure")
     protected boolean mThrowError = true;
 
-    protected ConcurrentHashMap<TestIdentifier, Map<String, String>> testMetrics =
+    protected ConcurrentHashMap<TestDescription, Map<String, String>> testMetrics =
             new ConcurrentHashMap<>();
-    private ConcurrentHashMap<TestIdentifier, String> testFailures = new ConcurrentHashMap<>();
+    private ConcurrentHashMap<TestDescription, String> testFailures = new ConcurrentHashMap<>();
 
     /**
      * {@inheritDoc}
@@ -126,7 +126,7 @@
         instrTest.run(listener);
         boolean success = true;
         if (!testFailures.isEmpty()) {
-            for (TestIdentifier test : testFailures.keySet()) {
+            for (TestDescription test : testFailures.keySet()) {
                 success = false;
                 String trace = testFailures.get(test);
                 if (mThrowError) {
@@ -143,12 +143,12 @@
     private class TargetPreparerListener implements ITestInvocationListener {
 
         @Override
-        public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        public void testEnded(TestDescription test, Map<String, String> metrics) {
             testMetrics.put(test, metrics);
         }
 
         @Override
-        public void testFailed(TestIdentifier test, String trace) {
+        public void testFailed(TestDescription test, String trace) {
             testFailures.put(test, trace);
         }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
index a5fff834..d54d1e7 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/BusinessLogicPreparer.java
@@ -18,6 +18,7 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
 import com.android.compatibility.common.util.BusinessLogic;
+import com.android.compatibility.common.util.BusinessLogicFactory;
 import com.android.compatibility.common.util.FeatureUtil;
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.tradefed.build.IBuildInfo;
@@ -36,12 +37,19 @@
 import com.android.tradefed.util.net.HttpHelper;
 import com.android.tradefed.util.net.IHttpHelper;
 
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.common.base.Strings;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -59,13 +67,17 @@
     private static final String FILE_LOCATION = "business-logic";
     /* Extension of business logic files */
     private static final String FILE_EXT = ".bl";
-
     /* Default amount of time to attempt connection to the business logic service, in seconds */
     private static final int DEFAULT_CONNECTION_TIME = 10;
-
+    /* URI of api scope to use when retrieving business logic rules */
+    private static final String APE_API_SCOPE = "https://www.googleapis.com/auth/androidPartner";
     /* Dynamic config constants */
     private static final String DYNAMIC_CONFIG_FEATURES_KEY = "business_logic_device_features";
     private static final String DYNAMIC_CONFIG_PROPERTIES_KEY = "business_logic_device_properties";
+    private static final String DYNAMIC_CONFIG_CONDITIONAL_TESTS_ENABLED_KEY =
+            "conditional_business_logic_tests_enabled";
+    /* Format used to append the enabled attribute to the serialized business logic string. */
+    private static final String ENABLED_ATTRIBUTE_SNIPPET = ", \"%s\":%s }";
 
     @Option(name = "business-logic-url", description = "The URL to use when accessing the " +
             "business logic service, parameters not included", mandatory = true)
@@ -83,6 +95,10 @@
             "suite invocation if retrieval of business logic fails.")
     private boolean mIgnoreFailure = false;
 
+    @Option(name="conditional-business-logic-tests-enabled",
+            description="Setting to true will ensure the device specific tests are executed.")
+    private boolean mConditionalTestsEnabled = false;
+
     @Option(name = "business-logic-connection-time", description = "Amount of time to attempt " +
             "connection to the business logic service, in seconds.")
     private int mMaxConnectionTime = DEFAULT_CONNECTION_TIME;
@@ -106,6 +122,7 @@
             try {
                 URL request = new URL(requestString);
                 businessLogicString = StreamUtil.getStringFromStream(request.openStream());
+                businessLogicString = addRuntimeConfig(businessLogicString, buildInfo);
             } catch (IOException e) {} // ignore, re-attempt connection with remaining time
         }
         if (businessLogicString == null) {
@@ -151,9 +168,15 @@
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
         String baseUrl = mUrl.replace(SUITE_PLACEHOLDER, getSuiteName());
         MultiMap<String, String> paramMap = new MultiMap<>();
-        paramMap.put("key", mApiKey);
         paramMap.put("suite_version", buildHelper.getSuiteVersion());
         paramMap.put("oem", String.valueOf(PropertyUtil.getManufacturer(device)));
+        String accessToken = getToken();
+        // Add api key (not authenticated) or Oath token, but not both.
+        if (Strings.isNullOrEmpty(accessToken)) {
+            paramMap.put("key", mApiKey);
+        } else {
+            paramMap.put("access_token", accessToken);
+        }
         for (String feature : getBusinessLogicFeatures(device, buildInfo)) {
             paramMap.put("features", feature);
         }
@@ -208,6 +231,37 @@
     }
 
     /**
+     * Append runtime configuration attributes to the end of the Json string.
+     * Determine if conditional tests should execute and add the value to the serialized business
+     * logic settings.
+     */
+    private String addRuntimeConfig(String businessLogicString, IBuildInfo buildInfo) {
+        int indexOfClosingParen = businessLogicString.lastIndexOf("}");
+        // Replace the closing paren with th enabled flag and closing paren. ex
+        // { "a":4 } -> {"a":4, "enabled":true }
+        return businessLogicString.substring(0, indexOfClosingParen) +
+                String.format(ENABLED_ATTRIBUTE_SNIPPET,
+                        BusinessLogicFactory.CONDITIONAL_TESTS_ENABLED,
+                        shouldExecuteConditionalTests(buildInfo));
+    }
+
+    /**
+     * Execute device specific test if enabled in config or through the command line.
+     * Otherwise skip all conditional tests.
+     */
+    private boolean shouldExecuteConditionalTests(IBuildInfo buildInfo) {
+        boolean enabledInConfig = false;
+        try {
+            String enabledInConfigValue = DynamicConfigFileReader.getValueFromConfig(
+                    buildInfo, getSuiteName(), DYNAMIC_CONFIG_CONDITIONAL_TESTS_ENABLED_KEY);
+            enabledInConfig = Boolean.parseBoolean(enabledInConfigValue);
+        } catch (XmlPullParserException | IOException e) {
+            CLog.e("Failed to pull business logic features from dynamic config");
+        }
+        return enabledInConfig || mConditionalTestsEnabled;
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -228,4 +282,29 @@
     private static void removeDeviceFile(ITestDevice device) throws DeviceNotAvailableException {
         device.executeShellCommand(String.format("rm -rf %s", BusinessLogic.DEVICE_FILE));
     }
+
+    /*
+    * Returns an OAuth2 token string obtained using a service account json key file.
+    *
+    * Uses the service account key file location stored in environment variable 'APE_API_KEY'
+    * to request an OAuth2 token.
+    */
+    private String getToken() {
+        String keyFilePath = System.getenv("APE_API_KEY");
+        if (Strings.isNullOrEmpty(keyFilePath)) {
+            CLog.d("Environment variable APE_API_KEY not set.");
+            return null;
+        }
+        try {
+            Credential credential = GoogleCredential.fromStream(new FileInputStream(keyFilePath))
+                    .createScoped(Collections.singleton(APE_API_SCOPE));
+            credential.refreshToken();
+            return credential.getAccessToken();
+        } catch (FileNotFoundException e) {
+            CLog.e(String.format("Service key file %s doesn't exist.", keyFilePath));
+        } catch (IOException e) {
+            CLog.e(String.format("Can't read the service key file, %s", keyFilePath));
+        }
+        return null;
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
index 918c27c..6db5e78 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -15,6 +15,7 @@
  */
 package com.android.compatibility.common.tradefed.targetprep;
 
+import com.android.annotations.VisibleForTesting;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.DynamicConfig;
 import com.android.compatibility.common.util.DynamicConfigHandler;
@@ -25,9 +26,11 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 
 import org.json.JSONException;
@@ -36,13 +39,14 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 
 /**
  * Pushes dynamic config files from config repository
  */
 @OptionClass(alias="dynamic-config-pusher")
-public class DynamicConfigPusher implements ITargetCleaner {
+public class DynamicConfigPusher extends BaseTargetPreparer implements ITargetCleaner {
     public enum TestTarget {
         DEVICE,
         HOST
@@ -70,6 +74,18 @@
             "from the server, e.g. \"1.0\". Defaults to suite version string.")
     private String mVersion;
 
+    // Options for getting the dynamic file from resources.
+    @Option(name = "extract-from-resource",
+            description = "Whether to look for the local dynamic config inside the jar resources "
+                + "or on the local disk.")
+    private boolean mExtractFromResource = false;
+
+    @Option(name = "dynamic-resource-name",
+            description = "When using --extract-from-resource, this option allow to specify the "
+                + "resource name, instead of the module name for the lookup. File will still be "
+                + "logged under the module name.")
+    private String mResourceFileName = null;
+
     private String mDeviceFilePushed;
 
     void setModuleName(String moduleName) {
@@ -85,13 +101,7 @@
 
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
 
-        File localConfigFile = null;
-        try {
-            localConfigFile = buildHelper.getTestFile(mModuleName + ".dynamic");
-        } catch (FileNotFoundException e) {
-            throw new TargetSetupError("Cannot get local dynamic config file from test directory",
-                    e, device.getDeviceDescriptor());
-        }
+        File localConfigFile = getLocalConfigFile(buildHelper, device);
 
         if (mVersion == null) {
             mVersion = buildHelper.getSuiteVersion();
@@ -152,4 +162,34 @@
             device.executeShellCommand("rm -r " + mDeviceFilePushed);
         }
     }
+
+    @VisibleForTesting
+    final File getLocalConfigFile(CompatibilityBuildHelper buildHelper, ITestDevice device)
+            throws TargetSetupError {
+        File localConfigFile = null;
+        if (mExtractFromResource) {
+            String lookupName = (mResourceFileName != null) ? mResourceFileName : mModuleName;
+            InputStream dynamicFileRes = getClass().getResourceAsStream(
+                    String.format("/%s.dynamic", lookupName));
+            try {
+                localConfigFile = FileUtil.createTempFile(lookupName, ".dynamic");
+                FileUtil.writeToFile(dynamicFileRes, localConfigFile);
+            } catch (IOException e) {
+                FileUtil.deleteFile(localConfigFile);
+                throw new TargetSetupError(
+                        String.format("Fail to unpack '%s.dynamic' from resources", lookupName),
+                        e, device.getDeviceDescriptor());
+            }
+            return localConfigFile;
+        }
+
+        // If not from resources look at local path.
+        try {
+            localConfigFile = buildHelper.getTestFile(String.format("%s.dynamic", mModuleName));
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError("Cannot get local dynamic config file from test directory",
+                    e, device.getDeviceDescriptor());
+        }
+        return localConfigFile;
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
index 1bcc6f8..59216df 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/MediaPreparer.java
@@ -16,16 +16,15 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.targetprep.PreconditionPreparer;
 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
 import com.android.ddmlib.IDevice;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.testtype.AndroidJUnitTest;
@@ -380,7 +379,7 @@
     private class MediaPreparerListener implements ITestInvocationListener {
 
         @Override
-        public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        public void testEnded(TestDescription test, Map<String, String> metrics) {
             String resString = metrics.get(RESOLUTION_STRING_KEY);
             if (resString != null) {
                 mMaxRes = new Resolution(resString);
@@ -388,7 +387,7 @@
         }
 
         @Override
-        public void testFailed(TestIdentifier test, String trace) {
+        public void testFailed(TestDescription test, String trace) {
             mFailureStackTrace = trace;
         }
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicConditionalHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicConditionalHostTestBase.java
new file mode 100644
index 0000000..1bee3a3
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicConditionalHostTestBase.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.testtype;
+
+import org.junit.Before;
+
+/**
+ * Host-side base class for tests leveraging the Business Logic service.
+ */
+public class BusinessLogicConditionalHostTestBase extends BusinessLogicHostTestBase {
+
+    @Override
+    @Before
+    public void handleBusinessLogic() {
+        super.loadBusinessLogic();
+        ensureAuthenticated();
+        super.executeBusinessLogic();
+    }
+
+    protected void ensureAuthenticated() {
+        if (!mCanReadBusinessLogic) {
+            // super class handles the condition that the service is unavailable.
+            return;
+        }
+
+        if (!mBusinessLogic.mConditionalTestsEnabled) {
+            skipTest("Execution of device specific tests is not enabled. "
+                    + "Enable with '--conditional-business-logic-tests-enabled'");
+        }
+
+        if (mBusinessLogic.isAuthorized()) {
+            // Run test as normal.
+            return;
+        }
+        String message = mBusinessLogic.getAuthenticationStatusMessage();
+
+        // Fail test since request was not authorized.
+        failTest(String.format("Unable to execute because %s.", message));
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
index 7ebd717..ed6e7bc 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
@@ -29,13 +29,15 @@
 import com.android.compatibility.common.util.BusinessLogicFactory;
 import com.android.compatibility.common.util.BusinessLogicHostExecutor;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.suite.TestSuiteInfo;
 
 import java.io.File;
 
 /**
  * Host-side base class for tests leveraging the Business Logic service.
  */
-public class BusinessLogicHostTestBase extends CompatibilityHostTestBase {
+public class BusinessLogicHostTestBase extends BaseHostJUnit4Test {
 
     /* String marking the beginning of the parameter in a test name */
     private static final String PARAM_START = "[";
@@ -43,23 +45,16 @@
     /* Test name rule that tracks the current test method under execution */
     @Rule public TestName mTestCase = new TestName();
 
-    private static BusinessLogic mBusinessLogic;
-    private static boolean mCanReadBusinessLogic = true;
+    protected BusinessLogic mBusinessLogic;
+    protected boolean mCanReadBusinessLogic = true;
 
     @Before
-    public void executeBusinessLogic() {
-        // Business logic must be retrieved in this @Before method, since the build info contains
-        // the location of the business logic file and cannot be referenced from a static context
-        if (mBusinessLogic == null) {
-            CompatibilityBuildHelper helper = new CompatibilityBuildHelper(mBuild);
-            File businessLogicFile = helper.getBusinessLogicHostFile();
-            if (businessLogicFile != null && businessLogicFile.canRead()) {
-                mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
-            } else {
-                mCanReadBusinessLogic = false; // failed to retrieve business logic
-            }
-        }
+    public void handleBusinessLogic() {
+        loadBusinessLogic();
+        executeBusinessLogic();
+    }
 
+    protected void executeBusinessLogic() {
         String methodName = mTestCase.getMethodName();
         assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
                 + "remote configuration.", methodName), mCanReadBusinessLogic);
@@ -71,11 +66,21 @@
         if (mBusinessLogic.hasLogicFor(testName)) {
             CLog.i("Applying business logic for test case: ", testName);
             BusinessLogicExecutor executor = new BusinessLogicHostExecutor(getDevice(),
-                    mBuild, this);
+                    getBuild(), this);
             mBusinessLogic.applyLogicFor(testName, executor);
         }
     }
 
+    protected void loadBusinessLogic() {
+        CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
+        File businessLogicFile = helper.getBusinessLogicHostFile();
+        if (businessLogicFile != null && businessLogicFile.canRead()) {
+            mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
+        } else {
+            mCanReadBusinessLogic = false; // failed to retrieve business logic
+        }
+    }
+
     public static void skipTest(String message) {
         assumeTrue(message, false);
     }
@@ -84,3 +89,4 @@
         fail(message);
     }
 }
+
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
deleted file mode 100644
index 1222a1f..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.tradefed.testtype;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.IDeviceTest;
-import com.android.tradefed.util.AbiUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.internal.AssumptionViolatedException;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Compatibility host test base class for JUnit4 tests. Enables host-side tests written in JUnit4
- * to access build and ABI information, as well as a reference to the testing device. The class
- * includes methods to install and uninstall test packages, as well as methods to run device-side
- * tests and retrieve their results.
- */
-public class CompatibilityHostTestBase implements IAbiReceiver, IBuildReceiver, IDeviceTest {
-
-    protected static final String AJUR = "android.support.test.runner.AndroidJUnitRunner";
-
-    /** The build will be used. */
-    protected IBuildInfo mBuild;
-
-    /** The ABI to use. */
-    protected IAbi mAbi;
-
-    /** A reference to the device under test. */
-    protected ITestDevice mDevice;
-
-    /** The test runner used for test apps */
-    private String mRunner;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public IAbi getAbi() {
-        return mAbi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        // Get the build, this is used to access the APK.
-        mBuild = buildInfo;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public ITestDevice getDevice() {
-        return mDevice;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setDevice(ITestDevice device) {
-        mDevice = device;
-    }
-
-    @Before
-    public void baseSetUp() throws Exception {
-        mRunner = AJUR;
-    }
-
-    /**
-     * Set the runner name
-     * @param runner of the device test runner
-     */
-    protected void setRunner(String runner) {
-        mRunner = runner;
-    }
-
-    /**
-     * Get the runner name
-     * @return name of the device test runner
-     */
-    protected String getRunner() {
-        return mRunner;
-    }
-
-    /**
-     * Installs a package on the device
-     * @param fileName the name of the file to install
-     * @param options optional extra arguments to pass. See 'adb shell pm install --help' for
-     * available options
-     * @throws FileNotFoundException if file with filename cannot be found
-     * @throws DeviceNotAvailableException
-     */
-    protected void installPackage(String fileName, String... options)
-            throws FileNotFoundException, DeviceNotAvailableException {
-
-        final List<String> optList = new ArrayList<>(Arrays.asList(options));
-        optList.add(AbiUtils.createAbiFlag(mAbi.getName()));
-        options = optList.toArray(new String[optList.size()]);
-
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
-        File testFile = buildHelper.getTestFile(fileName);
-        // Install the APK on the device.
-        String installResult = mDevice.installPackage(testFile, true, options);
-
-        assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
-                installResult);
-    }
-
-    /**
-     * Uninstalls a package on the device
-     * @param pkgName the Android package to uninstall
-     * @return a {@link String} with an error code, or <code>null</code> if success
-     * @throws DeviceNotAvailableException
-     */
-    protected String uninstallPackage(String pkgName) throws DeviceNotAvailableException {
-        return mDevice.uninstallPackage(pkgName);
-    }
-
-    /**
-     * Checks if a package of a given name is installed on the device
-     * @param pkg the name of the package
-     * @return true if the package is found on the device
-     * @throws DeviceNotAvailableException
-     */
-    protected boolean isPackageInstalled(String pkg) throws DeviceNotAvailableException {
-        for (String installedPackage : mDevice.getInstalledPackageNames()) {
-            if (pkg.equals(installedPackage)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void printTestResult(TestRunResult runResult) {
-        for (Map.Entry<TestIdentifier, TestResult> testEntry :
-                     runResult.getTestResults().entrySet()) {
-            TestResult testResult = testEntry.getValue();
-            TestStatus testStatus = testResult.getStatus();
-            CLog.logAndDisplay(LogLevel.INFO,
-                    "Test " + testEntry.getKey() + ": " + testStatus);
-            if (testStatus != TestStatus.PASSED && testStatus != TestStatus.ASSUMPTION_FAILURE) {
-                CLog.logAndDisplay(LogLevel.WARN, testResult.getStackTrace());
-            }
-        }
-    }
-
-    /**
-     * Runs tests of a given package on the device and reports success.
-     * @param pkgName the name of the package containing tests
-     * @param testClassName the class from which tests should be collected. Tests are collected
-     * from all test classes in the package if null
-     * @return true if at least once test runs and there are no failures
-     * @throws AssertionError if device fails to run instrumentation tests
-     * @throws AssumptionViolatedException if each device test fails an assumption
-     * @throws DeviceNotAvailableException
-     */
-    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName)
-            throws DeviceNotAvailableException {
-        return runDeviceTests(pkgName, testClassName, null /*testMethodName*/);
-    }
-
-    /**
-     * Runs tests of a given package on the device and reports success.
-     * @param pkgName the name of the package containing tests
-     * @param testClassName the class from which tests should be collected. Tests are collected
-     * from all test classes in the package if null
-     * @param testMethodName the test method to run. All tests from the class or package are run
-     * if null
-     * @return true if at least once test runs and there are no failures
-     * @throws AssertionError if device fails to run instrumentation tests
-     * @throws AssumptionViolatedException if each device test fails an assumption
-     * @throws DeviceNotAvailableException
-     */
-    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
-            @Nullable String testMethodName)
-            throws DeviceNotAvailableException {
-        TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName);
-        printTestResult(runResult);
-        // assume not all tests have skipped (and rethrow AssumptionViolatedException if so)
-        Assume.assumeTrue(runResult.getNumTests() != runResult.getNumTestsInState(
-                TestStatus.ASSUMPTION_FAILURE));
-        return !runResult.hasFailedTests() && runResult.getNumTests() > 0;
-    }
-
-    /** Helper method to run tests and return the listener that collected the results. */
-    private TestRunResult doRunTests(
-        String pkgName, String testClassName,
-        String testMethodName) throws DeviceNotAvailableException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-            pkgName, mRunner, mDevice.getIDevice());
-        if (testClassName != null && testMethodName != null) {
-            testRunner.setMethodName(testClassName, testMethodName);
-        } else if (testClassName != null) {
-            testRunner.setClassName(testClassName);
-        }
-
-        CollectingTestListener listener = createCollectingListener();
-        assertTrue(mDevice.runInstrumentationTests(testRunner, listener));
-        return listener.getCurrentRunResults();
-    }
-
-    @VisibleForTesting
-    protected CollectingTestListener createCollectingListener() {
-        return new CollectingTestListener();
-    }
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 07bfe83..a8c016c 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -20,6 +20,7 @@
 import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
 import com.android.compatibility.common.tradefed.result.SubPlanHelper;
 import com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
 import com.android.compatibility.common.tradefed.util.RetryType;
 import com.android.compatibility.common.tradefed.util.UniqueModuleCountUtil;
@@ -79,8 +80,10 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * A Test for running Compatibility Suites
+ * A Test for running Compatibility Suites.
+ * @deprecated use {@link CompatibilityTestSuite} instead.
  */
+@Deprecated
 @OptionClass(alias = "compatibility")
 public class CompatibilityTest implements IDeviceTest, IShardableTest, IBuildReceiver,
         IStrictShardableTest, ISystemStatusCheckerReceiver, ITestCollector,
@@ -473,7 +476,16 @@
                 moduleContext.addInvocationAttribute(IModuleDef.MODULE_ABI,
                         module.getAbi().getName());
                 mInvocationContext.setModuleInvocationContext(moduleContext);
+                // Populate the module context with devices and builds
+                for (String deviceName : mInvocationContext.getDeviceConfigNames()) {
+                    moduleContext.addAllocatedDevice(
+                            deviceName, mInvocationContext.getDevice(deviceName));
+                    moduleContext.addDeviceBuildInfo(
+                            deviceName, mInvocationContext.getBuildInfo(deviceName));
+                }
+                module.setInvocationContext(moduleContext);
                 try {
+                    listener.testModuleStarted(moduleContext);
                     module.run(listener);
                 } catch (DeviceUnresponsiveException due) {
                     // being able to catch a DeviceUnresponsiveException here implies that recovery
@@ -490,6 +502,7 @@
                     // clear out module invocation context since we are now done with module
                     // execution
                     mInvocationContext.setModuleInvocationContext(null);
+                    listener.testModuleEnded();
                 }
                 long duration = System.currentTimeMillis() - start;
                 long expected = module.getRuntimeHint();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
index fbddb22..d21ef81 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
@@ -15,7 +15,6 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -23,6 +22,7 @@
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.RunUtil;
 
 public class FailureListener extends ResultForwarder {
@@ -65,7 +65,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void testFailed(TestIdentifier test, String trace) {
+    public void testFailed(TestDescription test, String trace) {
         super.testFailed(test, trace);
         CLog.i("FailureListener.testFailed %s %b %b %b",
                 test.toString(), mBugReportOnFailure, mLogcatOnFailure, mScreenshotOnFailure);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index 7a2c44d..51b33a1 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -21,6 +21,7 @@
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.ITestCollector;
@@ -33,7 +34,7 @@
  * Container for Compatibility test info.
  */
 public interface IModuleDef extends Comparable<IModuleDef>, IBuildReceiver, IDeviceTest,
-        IRemoteTest, IRuntimeHintProvider, ITestCollector {
+        IRemoteTest, IRuntimeHintProvider, ITestCollector, IInvocationContextReceiver {
 
     /** key names used for saving module info into {@link IInvocationContext} */
     // This currently references ModuleDefinition so that there's only once source for String
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
index 5ef3a93..083acb6 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
@@ -36,8 +36,8 @@
 import java.lang.reflect.Modifier;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -114,6 +114,18 @@
                     } catch (ClassNotFoundException cnfe) {
                         throw new IllegalArgumentException(
                                 String.format("Cannot find test class %s", className));
+                    } catch (IllegalAccessError | NoClassDefFoundError err) {
+                        // IllegalAccessError can happen when the class or one of its super
+                        // class/interfaces are package-private. We can't load such class from
+                        // here (= outside of the pacakge). Since our intention is not to load
+                        // all classes in the jar, but to find our the main test classes, this
+                        // can be safely skipped.
+                        // NoClassDefFoundErrror is also okay because certain CTS test cases
+                        // might statically link to a jar library (e.g. tools.jar from JDK)
+                        // where certain internal classes in the library are referencing
+                        // classes that are not available in the jar. Again, since our goal here
+                        // is to find test classes, this can be safely skipped.
+                        continue;
                     }
                 }
             } catch (IOException e) {
@@ -136,11 +148,25 @@
      */
     @Override
     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
-        int numTests = countTestCases();
+        int numTests = 0;
+        RuntimeException bufferedException = null;
+        try {
+            numTests = countTestCases();
+        } catch (RuntimeException e) {
+            bufferedException = e;
+        }
         long startTime = System.currentTimeMillis();
         listener.testRunStarted(getClass().getName(), numTests);
-        super.run(new HostTestListener(listener));
-        listener.testRunEnded(System.currentTimeMillis() - startTime, Collections.emptyMap());
+        HostTestListener hostListener = new HostTestListener(listener);
+        try {
+            if (bufferedException != null) {
+                throw bufferedException;
+            }
+            super.run(hostListener);
+        } finally {
+            listener.testRunEnded(System.currentTimeMillis() - startTime,
+                    hostListener.getMetrics());
+        }
     }
 
     /**
@@ -150,6 +176,8 @@
      */
     public class HostTestListener extends ResultForwarder {
 
+        private Map<String, String> mCollectedMetrics = new HashMap<>();
+
         public HostTestListener(ITestInvocationListener listener) {
             super(listener);
         }
@@ -168,6 +196,14 @@
         @Override
         public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
             CLog.d("HostTestListener.testRunEnded(%d, %s)", elapsedTime, metrics.toString());
+            mCollectedMetrics.putAll(metrics);
+        }
+
+        /**
+         * Returns all the metrics reported by the tests
+         */
+        Map<String, String> getMetrics() {
+            return mCollectedMetrics;
         }
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index f51be93..ffb7ef7 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -26,6 +26,7 @@
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ResultForwarder;
@@ -37,6 +38,7 @@
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.ITestCollector;
@@ -68,6 +70,7 @@
     private ITestDevice mDevice;
     private Set<String> mPreparerWhitelist = new HashSet<>();
     private ConfigurationDescriptor mConfigurationDescriptor;
+    private IInvocationContext mContext;
 
     public ModuleDef(String name, IAbi abi, IRemoteTest test,
             List<ITargetPreparer> preparers, ConfigurationDescriptor configurationDescriptor) {
@@ -302,6 +305,9 @@
         if (mTest instanceof IDeviceTest) {
             ((IDeviceTest) mTest).setDevice(mDevice);
         }
+        if (mTest instanceof IInvocationContextReceiver) {
+            ((IInvocationContextReceiver) mTest).setInvocationContext(mContext);
+        }
     }
 
     /**
@@ -409,4 +415,16 @@
     public ConfigurationDescriptor getConfigurationDescriptor() {
         return mConfigurationDescriptor;
     }
+
+    /**
+     * @return the {@link IInvocationContext} for the module
+     */
+    protected IInvocationContext getInvocationContext() {
+        return mContext;
+    }
+
+    @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mContext = invocationContext;
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
index 40bf633..8a9f10a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
@@ -16,12 +16,13 @@
 package com.android.compatibility.common.tradefed.testtype.retry;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
 import com.android.compatibility.common.tradefed.util.RetryType;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
@@ -41,66 +42,76 @@
 
 import com.google.common.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Runner that creates a {@link CompatibilityTest} to re-run some previous results.
+ * Runner that creates a {@link CompatibilityTestSuite} to re-run some previous results.
  * Only the 'cts' plan is supported.
  * TODO: explore other new way to build the retry (instead of relying on one massive pair of
  * include/exclude filters)
  */
 @OptionClass(alias = "compatibility")
 public class RetryFactoryTest implements IRemoteTest, IDeviceTest, IBuildReceiver,
-        ISystemStatusCheckerReceiver, IInvocationContextReceiver, IShardableTest {
+        ISystemStatusCheckerReceiver, IInvocationContextReceiver, IShardableTest,
+        IConfigurationReceiver {
 
     /**
-     * Mirror the {@link CompatibilityTest} options in order to create it.
+     * Mirror the {@link CompatibilityTestSuite} options in order to create it.
      */
     public static final String RETRY_OPTION = "retry";
+    public static final String RETRY_TYPE_OPTION = "retry-type";
+
     @Option(name = RETRY_OPTION,
             shortName = 'r',
             description = "retry a previous session's failed and not executed tests.",
             mandatory = true)
     private Integer mRetrySessionId = null;
 
-    @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+    @Option(name = CompatibilityTestSuite.SUBPLAN_OPTION,
             description = "the subplan to run",
             importance = Importance.IF_UNSET)
     protected String mSubPlan;
 
-    @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+    @Option(name = CompatibilityTestSuite.INCLUDE_FILTER_OPTION,
             description = "the include module filters to apply.",
             importance = Importance.ALWAYS)
     protected Set<String> mIncludeFilters = new HashSet<>();
 
-    @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+    @Option(name = CompatibilityTestSuite.EXCLUDE_FILTER_OPTION,
             description = "the exclude module filters to apply.",
             importance = Importance.ALWAYS)
     protected Set<String> mExcludeFilters = new HashSet<>();
 
-    @Option(name = CompatibilityTest.ABI_OPTION,
+    @Option(name = CompatibilityTestSuite.ABI_OPTION,
             shortName = 'a',
             description = "the abi to test.",
             importance = Importance.IF_UNSET)
     protected String mAbiName = null;
 
-    @Option(name = CompatibilityTest.MODULE_OPTION,
+    @Option(name = CompatibilityTestSuite.MODULE_OPTION,
             shortName = 'm',
             description = "the test module to run.",
             importance = Importance.IF_UNSET)
     protected String mModuleName = null;
 
-    @Option(name = CompatibilityTest.TEST_OPTION,
-            shortName = CompatibilityTest.TEST_OPTION_SHORT_NAME,
+    @Option(name = CompatibilityTestSuite.TEST_OPTION,
+            shortName = CompatibilityTestSuite.TEST_OPTION_SHORT_NAME,
             description = "the test run.",
             importance = Importance.IF_UNSET)
     protected String mTestName = null;
 
-    @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
-            description = "used with " + CompatibilityTest.RETRY_OPTION + ", retry tests"
+    @Option(name = CompatibilityTestSuite.TEST_ARG_OPTION,
+            description = "the arguments to pass to a test. The expected format is "
+                    + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
+            importance = Importance.ALWAYS)
+    private List<String> mTestArgs = new ArrayList<>();
+
+    @Option(name = RETRY_TYPE_OPTION,
+            description = "used with " + RETRY_OPTION + ", retry tests"
             + " of a certain status. Possible values include \"failed\" and \"not_executed\".",
             importance = Importance.IF_UNSET)
     protected RetryType mRetryType = null;
@@ -109,6 +120,7 @@
     private IBuildInfo mBuildInfo;
     private ITestDevice mDevice;
     private IInvocationContext mContext;
+    private IConfiguration mMainConfiguration;
 
     @Override
     public void setSystemStatusChecker(List<ISystemStatusChecker> systemCheckers) {
@@ -135,6 +147,11 @@
         mContext = invocationContext;
     }
 
+    @Override
+    public void setConfiguration(IConfiguration configuration) {
+        mMainConfiguration = configuration;
+    }
+
     /**
      * Build a CompatibilityTest with appropriate filters to run only the tests of interests.
      */
@@ -176,11 +193,9 @@
 
         try {
             OptionSetter setter = new OptionSetter(test);
-            setter.setOptionValue("compatibility:test-arg",
-                    "com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true");
-            setter.setOptionValue("compatibility:test-arg",
-                    "com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:"
-                    + "false");
+            for (String testArg : mTestArgs) {
+                setter.setOptionValue("compatibility:test-arg", testArg);
+            }
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
         }
@@ -191,13 +206,19 @@
         test.setBuild(mBuildInfo);
         test.setSystemStatusChecker(mStatusCheckers);
         test.setInvocationContext(mContext);
+        test.setConfiguration(mMainConfiguration);
+        // reset the retry id - Ensure that retry of retry does not throw
+        test.resetRetryId();
+        test.isRetry();
         // clean the helper
         helper.tearDown();
         return test;
     }
 
-    @VisibleForTesting
-    RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+    /**
+     * @return a {@link RetryFilterHelper} created from the attributes of this object.
+     */
+    protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
         return new RetryFilterHelper(buildHelper, mRetrySessionId, mSubPlan, mIncludeFilters,
                 mExcludeFilters, mAbiName, mModuleName, mTestName, mRetryType);
     }
@@ -206,4 +227,11 @@
     CompatibilityTestSuite createTest() {
         return new CompatibilityTestSuite();
     }
+
+    /**
+     * @return the ID of the session to be retried.
+     */
+    protected Integer getRetrySessionId() {
+        return mRetrySessionId;
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilitySuiteModuleLoader.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilitySuiteModuleLoader.java
new file mode 100644
index 0000000..c8863d7
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilitySuiteModuleLoader.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.compatibility.common.tradefed.testtype.suite;
+
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.testtype.suite.SuiteModuleLoader;
+import com.android.tradefed.testtype.suite.SuiteTestFilter;
+import com.android.tradefed.util.AbiUtils;
+
+import java.util.List;
+import java.util.Map;
+
+public class CompatibilitySuiteModuleLoader extends SuiteModuleLoader {
+
+    /**
+     * Ctor for the CompatibilitySuiteModuleLoader.
+     *
+     * @param includeFilters The formatted and parsed include filters.
+     * @param excludeFilters The formatted and parsed exclude filters.
+     * @param testArgs the list of test ({@link IRemoteTest}) arguments.
+     * @param moduleArgs the list of module arguments.
+     */
+    public CompatibilitySuiteModuleLoader(
+            Map<String, List<SuiteTestFilter>> includeFilters,
+            Map<String, List<SuiteTestFilter>> excludeFilters,
+            List<String> testArgs,
+            List<String> moduleArgs) {
+        super(includeFilters,excludeFilters,testArgs,moduleArgs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addFiltersToTest(
+            IRemoteTest test,
+            IAbi abi,
+            String name,
+            Map<String, List<SuiteTestFilter>> includeFilters,
+            Map<String, List<SuiteTestFilter>> excludeFilters) {
+        String moduleId = AbiUtils.createId(abi.getName(), name);
+        // Override the default behavior. Compatibility Suites expect the filter receiver.
+        if (!(test instanceof ITestFilterReceiver)) {
+            throw new IllegalArgumentException(String.format(
+                    "Test in module %s must implement ITestFilterReceiver.", moduleId));
+        }
+        super.addFiltersToTest(test,abi,name,includeFilters,excludeFilters);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index bb74f23..2a52b69 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -16,60 +16,42 @@
 package com.android.compatibility.common.tradefed.testtype.suite;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.tradefed.testtype.ISubPlan;
 import com.android.compatibility.common.tradefed.testtype.SubPlan;
-import com.android.compatibility.common.util.TestFilter;
+import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest;
+import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.Abi;
 import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.suite.ITestSuite;
+import com.android.tradefed.testtype.suite.BaseTestSuite;
+import com.android.tradefed.testtype.suite.SuiteModuleLoader;
+import com.android.tradefed.testtype.suite.SuiteTestFilter;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
-import com.android.tradefed.util.AbiFormatter;
-import com.android.tradefed.util.AbiUtils;
-import com.android.tradefed.util.ArrayUtil;
-import com.android.tradefed.util.MultiMap;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
  * A Test for running Compatibility Test Suite with new suite system.
  */
 @OptionClass(alias = "compatibility")
-public class CompatibilityTestSuite extends ITestSuite {
+public class CompatibilityTestSuite extends BaseTestSuite {
 
-    private static final String INCLUDE_FILTER_OPTION = "include-filter";
-    private static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
-    private static final String SUBPLAN_OPTION = "subplan";
-    private static final String MODULE_OPTION = "module";
-    private static final String TEST_OPTION = "test";
-    private static final String MODULE_ARG_OPTION = "module-arg";
-    private static final String TEST_ARG_OPTION = "test-arg";
-    private static final String ABI_OPTION = "abi";
-    private static final String SKIP_HOST_ARCH_CHECK = "skip-host-arch-check";
-    private static final String PRIMARY_ABI_RUN = "primary-abi-only";
-    private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
+    public static final String SUBPLAN_OPTION = "subplan";
 
     // TODO: remove this option when CompatibilityTest goes away
-    @Option(name = CompatibilityTest.RETRY_OPTION,
+    @Option(name = RetryFactoryTest.RETRY_OPTION,
             shortName = 'r',
             description = "Copy of --retry from CompatibilityTest to prevent using it.")
     private Integer mRetrySessionId = null;
@@ -79,169 +61,48 @@
             importance = Importance.IF_UNSET)
     private String mSubPlan;
 
-    @Option(name = INCLUDE_FILTER_OPTION,
-            description = "the include module filters to apply.",
-            importance = Importance.ALWAYS)
-    private Set<String> mIncludeFilters = new HashSet<>();
-
-    @Option(name = EXCLUDE_FILTER_OPTION,
-            description = "the exclude module filters to apply.",
-            importance = Importance.ALWAYS)
-    private Set<String> mExcludeFilters = new HashSet<>();
-
-    @Option(name = MODULE_OPTION,
-            shortName = 'm',
-            description = "the test module to run.",
-            importance = Importance.IF_UNSET)
-    private String mModuleName = null;
-
-    @Option(name = TEST_OPTION,
-            shortName = 't',
-            description = "the test to run.",
-            importance = Importance.IF_UNSET)
-    private String mTestName = null;
-
-    @Option(name = MODULE_ARG_OPTION,
-            description = "the arguments to pass to a module. The expected format is"
-                    + "\"<module-name>:<arg-name>:[<arg-key>:=]<arg-value>\"",
-            importance = Importance.ALWAYS)
-    private List<String> mModuleArgs = new ArrayList<>();
-
-    @Option(name = TEST_ARG_OPTION,
-            description = "the arguments to pass to a test. The expected format is"
-                    + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
-            importance = Importance.ALWAYS)
-    private List<String> mTestArgs = new ArrayList<>();
-
-    @Option(name = ABI_OPTION,
-            shortName = 'a',
-            description = "the abi to test.",
-            importance = Importance.IF_UNSET)
-    private String mAbiName = null;
-
-    @Option(name = SKIP_HOST_ARCH_CHECK,
-            description = "Whether host architecture check should be skipped")
-    private boolean mSkipHostArchCheck = false;
-
-    @Option(name = PRIMARY_ABI_RUN,
-            description = "Whether to run tests with only the device primary abi. "
-                    + "This override the --abi option.")
-    private boolean mPrimaryAbiRun = false;
-
-    @Option(name = "module-metadata-include-filter",
-            description = "Include modules for execution based on matching of metadata fields: "
-                    + "for any of the specified filter name and value, if a module has a metadata "
-                    + "field with the same name and value, it will be included. When both module "
-                    + "inclusion and exclusion rules are applied, inclusion rules will be "
-                    + "evaluated first. Using this together with test filter inclusion rules may "
-                    + "result in no tests to execute if the rules don't overlap.")
-    private MultiMap<String, String> mModuleMetadataIncludeFilter = new MultiMap<>();
-
-    @Option(name = "module-metadata-exclude-filter",
-            description = "Exclude modules for execution based on matching of metadata fields: "
-                    + "for any of the specified filter name and value, if a module has a metadata "
-                    + "field with the same name and value, it will be excluded. When both module "
-                    + "inclusion and exclusion rules are applied, inclusion rules will be "
-                    + "evaluated first.")
-    private MultiMap<String, String> mModuleMetadataExcludeFilter = new MultiMap<>();
-
-    private ModuleRepoSuite mModuleRepo = new ModuleRepoSuite();
     private CompatibilityBuildHelper mBuildHelper;
+    /** Tag if the current instance is running as a retry from RetryFactory */
+    private boolean mIsRetry = false;
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public LinkedHashMap<String, IConfiguration> loadTests() {
-        if (mRetrySessionId != null) {
-            throw new IllegalArgumentException("--retry cannot be specified with cts-suite.xml. "
-                    + "Use 'run cts --retry <session id>' instead.");
-        }
-        try {
-            setupFilters();
-            Set<IAbi> abis = getAbis(getDevice());
-            // Initialize the repository, {@link CompatibilityBuildHelper#getTestsDir} can
-            // throw a {@link FileNotFoundException}
-            return mModuleRepo.loadConfigs(mBuildHelper.getTestsDir(),
-                    abis, mTestArgs, mModuleArgs, mIncludeFilters,
-                    mExcludeFilters, mModuleMetadataIncludeFilter, mModuleMetadataExcludeFilter);
-        } catch (DeviceNotAvailableException | FileNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void setBuild(IBuildInfo buildInfo) {
         super.setBuild(buildInfo);
         mBuildHelper = new CompatibilityBuildHelper(buildInfo);
     }
 
-    /**
-     * Gets the set of ABIs supported by both Compatibility and the device under test
-     *
-     * @return The set of ABIs to run the tests on
-     * @throws DeviceNotAvailableException
-     */
-    Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
-        Set<IAbi> abis = new LinkedHashSet<>();
-        Set<String> archAbis = getAbisForBuildTargetArch();
-        if (mPrimaryAbiRun) {
-            if (mAbiName == null) {
-                // Get the primary from the device and make it the --abi to run.
-                mAbiName = device.getProperty(PRODUCT_CPU_ABI_KEY).trim();
-            } else {
-                CLog.d("Option --%s supersedes the option --%s, using abi: %s", ABI_OPTION,
-                        PRIMARY_ABI_RUN, mAbiName);
-            }
-        }
-        if (mAbiName != null) {
-            // A particular abi was requested, it still needs to be supported by the build.
-            if ((!mSkipHostArchCheck && !archAbis.contains(mAbiName)) ||
-                    !AbiUtils.isAbiSupportedByCompatibility(mAbiName)) {
-                throw new IllegalArgumentException(String.format("Your CTS hasn't been built with "
-                        + "abi '%s' support, this CTS currently supports '%s'.",
-                        mAbiName, archAbis));
-            } else {
-                abis.add(new Abi(mAbiName, AbiUtils.getBitness(mAbiName)));
-                return abis;
-            }
-        } else {
-            // Run on all abi in common between the device and CTS.
-            List<String> deviceAbis = Arrays.asList(AbiFormatter.getSupportedAbis(device, ""));
-            for (String abi : deviceAbis) {
-                if ((mSkipHostArchCheck || archAbis.contains(abi)) &&
-                        AbiUtils.isAbiSupportedByCompatibility(abi)) {
-                    abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
-                } else {
-                    CLog.d("abi '%s' is supported by device but not by this CTS build (%s), tests "
-                            + "will not run against it.", abi, archAbis);
-                }
-            }
-            if (abis.isEmpty()) {
-                throw new IllegalArgumentException(String.format("None of the abi supported by this"
-                       + " CTS build ('%s') are supported by the device ('%s').",
-                       archAbis, deviceAbis));
-            }
-            return abis;
-        }
+    @Override
+    public File getTestsDir() throws FileNotFoundException {
+        return mBuildHelper.getTestsDir();
     }
 
-    /**
-     * Return the abis supported by the Host build target architecture.
-     * Exposed for testing.
-     */
-    protected Set<String> getAbisForBuildTargetArch() {
-        return AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
+    @Override
+    public SuiteModuleLoader createModuleLoader(
+            Map<String, List<SuiteTestFilter>> includeFiltersFormatted,
+            Map<String, List<SuiteTestFilter>> excludeFiltersFormatted,
+            List<String> testArgs,
+            List<String> moduleArgs) {
+        return new CompatibilitySuiteModuleLoader(includeFiltersFormatted,
+                excludeFiltersFormatted, testArgs, moduleArgs);
+    }
+
+    @Override
+    public LinkedHashMap<String, IConfiguration> loadTests() {
+        if (mRetrySessionId != null) {
+            throw new IllegalArgumentException(
+                    String.format("--retry cannot be specified with %s[*].xml. "
+                            + "Use 'run retry --retry <session id>' instead.",
+                            TestSuiteInfo.getInstance().getName().toLowerCase()));
+        }
+        return super.loadTests();
     }
 
     /**
      * Sets the include/exclude filters up based on if a module name was given or whether this is a
      * retry run.
      */
-    void setupFilters() throws FileNotFoundException {
+    @Override
+    final protected void setupFilters(File testDir) throws FileNotFoundException {
         if (mSubPlan != null) {
             try {
                 File subPlanFile = new File(mBuildHelper.getSubPlansDir(), mSubPlan + ".xml");
@@ -252,58 +113,53 @@
                 InputStream subPlanInputStream = new FileInputStream(subPlanFile);
                 ISubPlan subPlan = new SubPlan();
                 subPlan.parse(subPlanInputStream);
-                mIncludeFilters.addAll(subPlan.getIncludeFilters());
-                mExcludeFilters.addAll(subPlan.getExcludeFilters());
+                // Set include/exclude filter is additive
+                setIncludeFilter(subPlan.getIncludeFilters());
+                setExcludeFilter(subPlan.getExcludeFilters());
             } catch (ParseException e) {
                 throw new RuntimeException(
                         String.format("Unable to find or parse subplan %s", mSubPlan), e);
             }
         }
-        if (mModuleName != null) {
-            List<String> modules = ModuleRepoSuite.getModuleNamesMatching(
-                    mBuildHelper.getTestsDir(), mModuleName);
-            if (modules.size() == 0) {
-                throw new IllegalArgumentException(
-                        String.format("No modules found matching %s", mModuleName));
-            } else if (modules.size() > 1) {
-                throw new IllegalArgumentException(String.format(
-                        "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
-                        mModuleName, ArrayUtil.join("\n", modules)));
+        super.setupFilters(testDir);
+    }
+
+    /**
+     * Allow to reset the requested session id for retry.
+     */
+    public final void resetRetryId() {
+        mRetrySessionId = null;
+    }
+
+    /**
+     * Mark the instance of CompatibilityTestSuite as a retry.
+     */
+    public final void isRetry() {
+        mIsRetry = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LinkedHashMap<String, IConfiguration> loadingStrategy(
+            Set<IAbi> abis, File testsDir, String suitePrefix, String suiteTag) {
+        LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
+        // Load the configs that are part of the tests dir
+        loadedConfigs.putAll(
+                getModuleLoader().loadConfigsFromDirectory(testsDir, abis, suitePrefix, suiteTag));
+        // Add an extra check in CTS since we never expect the config folder to be empty.
+        if (loadedConfigs.size() == 0) {
+            if (mIsRetry) {
+                // Only log if it's a retry
+                CLog.logAndDisplay(LogLevel.DEBUG,
+                        "No module that needed to run in retry were found. nothing to do.");
             } else {
-                String moduleName = modules.get(0);
-                checkFilters(mIncludeFilters, moduleName);
-                checkFilters(mExcludeFilters, moduleName);
-                mIncludeFilters.add(new TestFilter(mAbiName, moduleName, mTestName).toString());
-            }
-        } else if (mTestName != null) {
-            throw new IllegalArgumentException(
-                    "Test name given without module name. Add --module <module-name>");
-        }
-    }
-
-    /* Helper method designed to remove filters in a list not applicable to the given module */
-    private static void checkFilters(Set<String> filters, String moduleName) {
-        Set<String> cleanedFilters = new HashSet<String>();
-        for (String filter : filters) {
-            if (moduleName.equals(TestFilter.createFrom(filter).getName())) {
-                cleanedFilters.add(filter); // Module name matches, filter passes
+                throw new IllegalArgumentException(
+                        String.format("No config files found in %s or in resources.",
+                                testsDir.getAbsolutePath()));
             }
         }
-        filters.clear();
-        filters.addAll(cleanedFilters);
-    }
-
-    /**
-     * Sets include-filters for the compatibility test
-     */
-    public void setIncludeFilter(Set<String> includeFilters) {
-        mIncludeFilters.addAll(includeFilters);
-    }
-
-    /**
-     * Sets exclude-filters for the compatibility test
-     */
-    public void setExcludeFilter(Set<String> excludeFilters) {
-        mExcludeFilters.addAll(excludeFilters);
+        return loadedConfigs;
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
deleted file mode 100644
index a889330..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.testtype.suite;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.compatibility.common.util.TestFilter;
-import com.android.tradefed.config.ConfigurationException;
-import com.android.tradefed.config.ConfigurationFactory;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationFactory;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.targetprep.ITargetPreparer;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.ITestFileFilterReceiver;
-import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.util.AbiUtils;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.MultiMap;
-import com.android.tradefed.util.StreamUtil;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * Retrieves Compatibility test module definitions from the repository.
- */
-public class ModuleRepoSuite {
-
-    private static final String CONFIG_EXT = ".config";
-    private Map<String, Map<String, List<String>>> mTestArgs = new HashMap<>();
-    private Map<String, Map<String, List<String>>> mModuleArgs = new HashMap<>();
-    private boolean mIncludeAll;
-    private Map<String, List<TestFilter>> mIncludeFilters = new HashMap<>();
-    private Map<String, List<TestFilter>> mExcludeFilters = new HashMap<>();
-    private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance();
-
-    /**
-     * Main loading of configurations, looking into testcases/ folder
-     */
-    public LinkedHashMap<String, IConfiguration> loadConfigs(File testsDir, Set<IAbi> abis,
-            List<String> testArgs, List<String> moduleArgs,
-            Set<String> includeFilters, Set<String> excludeFilters,
-            MultiMap<String, String> metadataIncludeFilters,
-            MultiMap<String, String> metadataExcludeFilters) {
-        CLog.d("Initializing ModuleRepo\nTests Dir:%s\nABIs:%s\n" +
-                "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s",
-                testsDir.getAbsolutePath(), abis, testArgs, moduleArgs,
-                includeFilters, excludeFilters);
-
-        LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
-
-        putArgs(testArgs, mTestArgs);
-        putArgs(moduleArgs, mModuleArgs);
-        mIncludeAll = includeFilters.isEmpty();
-        // Include all the inclusions
-        addFilters(includeFilters, mIncludeFilters, abis);
-        // Exclude all the exclusions
-        addFilters(excludeFilters, mExcludeFilters, abis);
-
-        File[] configFiles = testsDir.listFiles(new ConfigFilter());
-        if (configFiles.length == 0) {
-            throw new IllegalArgumentException(
-                    String.format("No config files found in %s", testsDir.getAbsolutePath()));
-        }
-        // Ensure stable initial order of configurations.
-        List<File> listConfigFiles = Arrays.asList(configFiles);
-        Collections.sort(listConfigFiles);
-        for (File configFile : listConfigFiles) {
-            final String name = configFile.getName().replace(CONFIG_EXT, "");
-            final String[] pathArg = new String[] { configFile.getAbsolutePath() };
-            try {
-                // Invokes parser to process the test module config file
-                // Need to generate a different config for each ABI as we cannot guarantee the
-                // configs are idempotent. This however means we parse the same file multiple times
-                for (IAbi abi : abis) {
-                    String id = AbiUtils.createId(abi.getName(), name);
-                    if (!shouldRunModule(id)) {
-                        // If the module should not run tests based on the state of filters,
-                        // skip this name/abi combination.
-                        continue;
-                    }
-                    IConfiguration config = mConfigFactory.createConfigurationFromArgs(pathArg);
-                    if (!filterByConfigMetadata(config,
-                            metadataIncludeFilters, metadataExcludeFilters)) {
-                        // if the module config did not pass the metadata filters, it's excluded
-                        // from execution
-                        continue;
-                    }
-                    Map<String, List<String>> args = new HashMap<>();
-                    if (mModuleArgs.containsKey(name)) {
-                        args.putAll(mModuleArgs.get(name));
-                    }
-                    if (mModuleArgs.containsKey(id)) {
-                        args.putAll(mModuleArgs.get(id));
-                    }
-                    injectOptionsToConfig(args, config);
-
-                    List<IRemoteTest> tests = config.getTests();
-                    for (IRemoteTest test : tests) {
-                        String className = test.getClass().getName();
-                        Map<String, List<String>> testArgsMap = new HashMap<>();
-                        if (mTestArgs.containsKey(className)) {
-                            testArgsMap.putAll(mTestArgs.get(className));
-                        }
-                        injectOptionsToConfig(testArgsMap, config);
-                        addFiltersToTest(test, abi, name);
-                        if (test instanceof IAbiReceiver) {
-                            ((IAbiReceiver)test).setAbi(abi);
-                        }
-                    }
-                    List<ITargetPreparer> preparers = config.getTargetPreparers();
-                    for (ITargetPreparer preparer : preparers) {
-                        if (preparer instanceof IAbiReceiver) {
-                            ((IAbiReceiver)preparer).setAbi(abi);
-                        }
-                    }
-                    // add the abi to the description
-                    config.getConfigurationDescription().setAbi(abi);
-                    toRun.put(id, config);
-                }
-            } catch (ConfigurationException e) {
-                throw new RuntimeException(String.format("Error parsing config file: %s",
-                        configFile.getName()), e);
-            }
-        }
-        return toRun;
-    }
-
-    /**
-     * Helper to inject options to a config.
-     */
-    @VisibleForTesting
-    void injectOptionsToConfig(Map<String, List<String>> optionMap, IConfiguration config)
-            throws ConfigurationException{
-        for (Entry<String, List<String>> entry : optionMap.entrySet()) {
-            for (String entryValue : entry.getValue()) {
-                String entryName = entry.getKey();
-                if (entryValue.contains(":=")) {
-                    // entryValue is key-value pair
-                    String key = entryValue.substring(0, entryValue.indexOf(":="));
-                    String value = entryValue.substring(entryValue.indexOf(":=") + 2);
-                    config.injectOptionValue(entryName, key, value);
-                } else {
-                    // entryValue is just the argument value
-                    config.injectOptionValue(entryName, entryValue);
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    protected boolean filterByConfigMetadata(IConfiguration config,
-            MultiMap<String, String> include, MultiMap<String, String> exclude) {
-        MultiMap<String, String> metadata = config.getConfigurationDescription().getAllMetaData();
-        boolean shouldInclude = false;
-        for (String key : include.keySet()) {
-            Set<String> filters = new HashSet<>(include.get(key));
-            if (metadata.containsKey(key)) {
-                filters.retainAll(metadata.get(key));
-                if (!filters.isEmpty()) {
-                    // inclusion filter is not empty and there's at least one matching inclusion
-                    // rule so there's no need to match other inclusion rules
-                    shouldInclude = true;
-                    break;
-                }
-            }
-        }
-        if (!include.isEmpty() && !shouldInclude) {
-            // if inclusion filter is not empty and we didn't find a match, the module will not be
-            // included
-            return false;
-        }
-        // Now evaluate exclusion rules, this ordering also means that exclusion rules may override
-        // inclusion rules: a config already matched for inclusion may still be excluded if matching
-        // rules exist
-        for (String key : exclude.keySet()) {
-            Set<String> filters = new HashSet<>(exclude.get(key));
-            if (metadata.containsKey(key)) {
-                filters.retainAll(metadata.get(key));
-                if (!filters.isEmpty()) {
-                    // we found at least one matching exclusion rules, so we are excluding this
-                    // this module
-                    return false;
-                }
-            }
-        }
-        // we've matched at least one inclusion rule (if there's any) AND we didn't match any of the
-        // exclusion rules (if there's any)
-        return true;
-    }
-
-    /**
-     * @return the {@link List} of modules whose name contains the given pattern.
-     */
-    public static List<String> getModuleNamesMatching(File directory, String pattern) {
-        String[] names = directory.list(new FilenameFilter(){
-            @Override
-            public boolean accept(File dir, String name) {
-                return name.contains(pattern) && name.endsWith(CONFIG_EXT);
-            }
-        });
-        List<String> modules = new ArrayList<String>(names.length);
-        for (String name : names) {
-            int index = name.indexOf(CONFIG_EXT);
-            if (index > 0) {
-                String module = name.substring(0, index);
-                if (module.equals(pattern)) {
-                    // Pattern represents a single module, just return a single-item list
-                    modules = new ArrayList<>(1);
-                    modules.add(module);
-                    return modules;
-                }
-                modules.add(module);
-            }
-        }
-        return modules;
-    }
-
-    private void addFilters(Set<String> stringFilters,
-            Map<String, List<TestFilter>> filters, Set<IAbi> abis) {
-        for (String filterString : stringFilters) {
-            TestFilter filter = TestFilter.createFrom(filterString);
-            String abi = filter.getAbi();
-            if (abi == null) {
-                for (IAbi a : abis) {
-                    addFilter(a.getName(), filter, filters);
-                }
-            } else {
-                addFilter(abi, filter, filters);
-            }
-        }
-    }
-
-    private void addFilter(String abi, TestFilter filter,
-            Map<String, List<TestFilter>> filters) {
-        getFilterList(filters, AbiUtils.createId(abi, filter.getName())).add(filter);
-    }
-
-    private List<TestFilter> getFilterList(Map<String, List<TestFilter>> filters, String id) {
-        List<TestFilter> fs = filters.get(id);
-        if (fs == null) {
-            fs = new ArrayList<>();
-            filters.put(id, fs);
-        }
-        return fs;
-    }
-
-    private void addFiltersToTest(IRemoteTest test, IAbi abi, String name) {
-        String moduleId = AbiUtils.createId(abi.getName(), name);
-        if (!(test instanceof ITestFilterReceiver)) {
-            throw new IllegalArgumentException(String.format(
-                    "Test in module %s must implement ITestFilterReceiver.", moduleId));
-        }
-        List<TestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId);
-        List<TestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId);
-        if (!mdIncludes.isEmpty()) {
-            addTestIncludes((ITestFilterReceiver) test, mdIncludes, name);
-        }
-        if (!mdExcludes.isEmpty()) {
-            addTestExcludes((ITestFilterReceiver) test, mdExcludes, name);
-        }
-    }
-
-    private boolean shouldRunModule(String moduleId) {
-        List<TestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId);
-        List<TestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId);
-        // if including all modules or includes exist for this module, and there are not excludes
-        // for the entire module, this module should be run.
-        return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes);
-    }
-
-    private void addTestIncludes(ITestFilterReceiver test, List<TestFilter> includes,
-            String name) {
-        if (test instanceof ITestFileFilterReceiver) {
-            File includeFile = createFilterFile(name, ".include", includes);
-            ((ITestFileFilterReceiver)test).setIncludeTestFile(includeFile);
-        } else {
-            // add test includes one at a time
-            for (TestFilter include : includes) {
-                String filterTestName = include.getTest();
-                if (filterTestName != null) {
-                    test.addIncludeFilter(filterTestName);
-                }
-            }
-        }
-    }
-
-    private void addTestExcludes(ITestFilterReceiver test, List<TestFilter> excludes,
-            String name) {
-        if (test instanceof ITestFileFilterReceiver) {
-            File excludeFile = createFilterFile(name, ".exclude", excludes);
-            ((ITestFileFilterReceiver)test).setExcludeTestFile(excludeFile);
-        } else {
-            // add test excludes one at a time
-            for (TestFilter exclude : excludes) {
-                test.addExcludeFilter(exclude.getTest());
-            }
-        }
-    }
-
-    private File createFilterFile(String prefix, String suffix, List<TestFilter> filters) {
-        File filterFile = null;
-        PrintWriter out = null;
-        try {
-            filterFile = FileUtil.createTempFile(prefix, suffix);
-            out = new PrintWriter(filterFile);
-            for (TestFilter filter : filters) {
-                String filterTest = filter.getTest();
-                if (filterTest != null) {
-                    out.println(filterTest);
-                }
-            }
-            out.flush();
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to create filter file");
-        } finally {
-            StreamUtil.close(out);
-        }
-        filterFile.deleteOnExit();
-        return filterFile;
-    }
-
-    /**
-     * Returns true iff one or more test filters in excludes apply to the entire module.
-     */
-    private boolean containsModuleExclude(Collection<TestFilter> excludes) {
-        for (TestFilter exclude : excludes) {
-            if (exclude.getTest() == null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * A {@link FilenameFilter} to find all the config files in a directory.
-     */
-    public static class ConfigFilter implements FilenameFilter {
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean accept(File dir, String name) {
-            return name.endsWith(CONFIG_EXT);
-        }
-    }
-
-    private static void putArgs(List<String> args,
-            Map<String, Map<String, List<String>>> argsMap) {
-        for (String arg : args) {
-            String[] parts = arg.split(":");
-            String target = parts[0];
-            String key = parts[1];
-            String value = parts[2];
-            Map<String, List<String>> map = argsMap.get(target);
-            if (map == null) {
-                map = new HashMap<>();
-                argsMap.put(target, map);
-            }
-            List<String> valueList = map.get(key);
-            if (valueList == null) {
-                valueList = new ArrayList<>();
-                map.put(key, valueList);
-            }
-            valueList.add(value);
-        }
-    }
-}
diff --git a/common/host-side/tradefed/tests/Android.mk b/common/host-side/tradefed/tests/Android.mk
index c61594a..9d8220b 100644
--- a/common/host-side/tradefed/tests/Android.mk
+++ b/common/host-side/tradefed/tests/Android.mk
@@ -17,8 +17,6 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_JAVA_RESOURCE_DIRS := ../res
 include cts/error_prone_rules.mk
 
 LOCAL_SUITE_BUILD_NUMBER := 2
@@ -28,6 +26,7 @@
 LOCAL_SUITE_VERSION := 1
 
 LOCAL_MODULE := compatibility-mock-tradefed
+LOCAL_STATIC_JAVA_LIBRARIES := cts-tradefed-harness
 include cts/error_prone_rules.mk
 include $(BUILD_COMPATIBILITY_SUITE)
 
diff --git a/common/host-side/tradefed/tests/res/test-dynamic-config.dynamic b/common/host-side/tradefed/tests/res/test-dynamic-config.dynamic
new file mode 100644
index 0000000..8762861
--- /dev/null
+++ b/common/host-side/tradefed/tests/res/test-dynamic-config.dynamic
@@ -0,0 +1,20 @@
+<!-- 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.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+         <value>https://dl.google.com/dl/android/cts/android-cts-media-1.4.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 5aaa542..32cacd6 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -30,18 +30,16 @@
 import com.android.compatibility.common.tradefed.result.ResultReporterBuildInfoTest;
 import com.android.compatibility.common.tradefed.result.ResultReporterTest;
 import com.android.compatibility.common.tradefed.result.SubPlanHelperTest;
+import com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusherTest;
 import com.android.compatibility.common.tradefed.targetprep.MediaPreparerTest;
 import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
 import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBaseTest;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
 import com.android.compatibility.common.tradefed.testtype.JarHostTestTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
 import com.android.compatibility.common.tradefed.testtype.SubPlanTest;
 import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTestTest;
-import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuiteTest;
-import com.android.compatibility.common.tradefed.testtype.suite.ModuleRepoSuiteTest;
 import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReaderTest;
 import com.android.compatibility.common.tradefed.util.OptionHelperTest;
@@ -85,12 +83,12 @@
     SubPlanHelperTest.class,
 
     // targetprep
+    DynamicConfigPusherTest.class,
     MediaPreparerTest.class,
     PropertyCheckTest.class,
     SettingsPreparerTest.class,
 
     // testtype
-    CompatibilityHostTestBaseTest.class,
     CompatibilityTestTest.class,
     JarHostTestTest.class,
     ModuleDefTest.class,
@@ -100,10 +98,6 @@
     // testtype.retry
     RetryFactoryTestTest.class,
 
-    // testype.suite
-    CompatibilityTestSuiteTest.class,
-    ModuleRepoSuiteTest.class,
-
     // util
     CollectorUtilTest.class,
     DynamicConfigFileReaderTest.class,
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
index 829dcc0..832bbe4 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -98,6 +98,9 @@
         assertEquals("Incorrect suite version", SUITE_VERSION, mHelper.getSuiteVersion());
     }
 
+    /**
+     * Test loading of CTS_ROOT is it exists or not.
+     */
     public void testProperty() throws Exception {
         setProperty(null);
         CompatibilityBuildProvider provider = new CompatibilityBuildProvider() {
@@ -106,19 +109,23 @@
                 return SUITE_NAME;
             }
         };
-        OptionSetter setter = new OptionSetter(provider);
-        setter.setOptionValue("plan", SUITE_PLAN);
-        setter.setOptionValue("dynamic-config-url", DYNAMIC_CONFIG_URL);
+        IBuildInfo info = null;
+        File rootDir = null;
         try {
-            // Should fail with root unset
-            new CompatibilityBuildHelper(provider.getBuild());
-            fail("Expected fail for unset root property");
-        } catch (IllegalArgumentException e) {
-            /* expected */
+            OptionSetter setter = new OptionSetter(provider);
+            setter.setOptionValue("plan", SUITE_PLAN);
+            setter.setOptionValue("dynamic-config-url", DYNAMIC_CONFIG_URL);
+            info = provider.getBuild();
+            rootDir = new CompatibilityBuildHelper(info).getRootDir();
+            assertNotNull(info.getBuildAttributes().get("ROOT_DIR"));
+        } finally {
+            provider.cleanUp(info);
         }
         setProperty(mRoot.getAbsolutePath());
         // Shouldn't fail with root set
-        new CompatibilityBuildHelper(provider.getBuild());
+        CompatibilityBuildHelper helper = new CompatibilityBuildHelper(provider.getBuild());
+        // If the root dir property is set then we use it.
+        assertFalse(helper.getRootDir().equals(rootDir));
     }
 
     public void testValidation() throws Exception {
@@ -178,6 +185,7 @@
      * references and not absolute path. When sharding, path are invalidated but Files are copied.
      */
     public void testAddDynamicFiles() throws Exception {
+        createDirStructure();
         File tmpDynamicFile = FileUtil.createTempFile("cts-test-file", ".dynamic");
         FileUtil.writeToFile("test string", tmpDynamicFile);
         try {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
index 50c5da3..f7116d7 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.compatibility.common.tradefed.build;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -69,7 +68,10 @@
         EasyMock.replay(mMockDevice);
         IBuildInfo info = mProvider.getBuild(mMockDevice);
         EasyMock.verify(mMockDevice);
-        assertFalse(info instanceof IDeviceBuildInfo);
+        // Still creates a device build for us.
+        assertTrue(info instanceof IDeviceBuildInfo);
+        // tests dir should be populated
+        assertNotNull(((IDeviceBuildInfo)info).getTestsDir());
     }
 
     /**
@@ -83,6 +85,8 @@
         EasyMock.expect(mMockDevice.getBuildId()).andReturn("8888");
         EasyMock.expect(mMockDevice.getBuildFlavor()).andReturn("flavor");
         EasyMock.expect(mMockDevice.getBuildAlias()).andReturn("alias");
+        EasyMock.expect(mMockDevice.getProperty("ro.product.name")).andReturn("product");
+        EasyMock.expect(mMockDevice.getProperty("ro.build.type")).andReturn("userdebug");
         EasyMock.replay(mMockDevice);
         IBuildInfo info = mProvider.getBuild(mMockDevice);
         EasyMock.verify(mMockDevice);
@@ -93,4 +97,22 @@
         mProvider.cleanUp(info);
         assertNotNull(((IDeviceBuildInfo)info).getTestsDir());
     }
+
+    /**
+     * Test that the {suite-name} pattern of the dynamic URL can be overriden by something different
+     * from the build-in suite name.
+     */
+    @Test
+    public void testDynamicUrlOverride() throws Exception {
+        final String uniquePattern = "UNIQUE_SUITE_NAME_PATTERN";
+        OptionSetter setter = new OptionSetter(mProvider);
+        setter.setOptionValue("url-suite-name-override", uniquePattern);
+        EasyMock.replay(mMockDevice);
+        IBuildInfo info = mProvider.getBuild(mMockDevice);
+        EasyMock.verify(mMockDevice);
+        String url = info.getBuildAttributes().get(
+                CompatibilityBuildProvider.DYNAMIC_CONFIG_OVERRIDE_URL);
+        assertTrue(String.format("URL was %s and should have contained %s", url, uniquePattern),
+                url.contains(uniquePattern));
+    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
index bcc82ac..3c85f8c 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
@@ -27,11 +27,15 @@
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.AndroidJUnitTest;
 import com.android.tradefed.testtype.HostTest;
 import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestFilterReceiver;
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.File;
 import java.io.FilenameFilter;
@@ -43,6 +47,7 @@
 /**
  * Test that configuration in CTS can load and have expected properties.
  */
+@RunWith(JUnit4.class)
 public class CtsConfigLoadingTest {
 
     private static final String METADATA_COMPONENT = "component";
@@ -52,6 +57,7 @@
             "art",
             "auth",
             "auto",
+            "autofill",
             "backup",
             "bionic",
             "bluetooth",
@@ -60,15 +66,19 @@
             "devtools",
             "framework",
             "graphics",
+            "inputmethod",
             "libcore",
             "location",
             "media",
             "metrics",
             "misc",
+            "mocking",
             "networking",
             "neuralnetworks",
+            "print",
             "renderscript",
             "security",
+            "statsd",
             "systems",
             "sysui",
             "telecom",
@@ -79,6 +89,37 @@
     ));
 
     /**
+     * List of the officially supported runners in CTS, they meet all the interfaces criteria as
+     * well as support sharding very well. Any new addition should go through a review.
+     */
+    private static final Set<String> SUPPORTED_CTS_TEST_TYPE = new HashSet<>(Arrays.asList(
+            // Cts runners
+            "com.android.compatibility.common.tradefed.testtype.JarHostTest",
+            "com.android.compatibility.testtype.DalvikTest",
+            "com.android.compatibility.testtype.LibcoreTest",
+            "com.drawelements.deqp.runner.DeqpTestRunner",
+            // Tradefed runners
+            "com.android.tradefed.testtype.AndroidJUnitTest",
+            "com.android.tradefed.testtype.HostTest",
+            "com.android.tradefed.testtype.GTest"
+    ));
+
+    /**
+     * In Most cases we impose the usage of the AndroidJUnitRunner because it supports all the
+     * features required (filtering, sharding, etc.). We do not typically expect people to need a
+     * different runner.
+     */
+    private static final String INSTRUMENTATION_RUNNER_NAME =
+            "android.support.test.runner.AndroidJUnitRunner";
+    private static final Set<String> RUNNER_EXCEPTION = new HashSet<>();
+    static {
+        // Used for a bunch of system-api cts tests
+        RUNNER_EXCEPTION.add("repackaged.android.test.InstrumentationTestRunner");
+        // Used by a UiRendering scenario where an activity is persisted between tests
+        RUNNER_EXCEPTION.add("android.uirendering.cts.runner.UiRenderingRunner");
+    }
+
+    /**
      * Test that configuration shipped in Tradefed can be parsed.
      * -> Exclude deprecated ApkInstaller.
      * -> Check if host-side tests are non empty.
@@ -121,6 +162,12 @@
             }
             // We can ensure that Host side tests are not empty.
             for (IRemoteTest test : c.getTests()) {
+                // Check that all the tests runners are well supported.
+                if (!SUPPORTED_CTS_TEST_TYPE.contains(test.getClass().getCanonicalName())) {
+                    throw new ConfigurationException(
+                            String.format("testtype %s is not officially supported by CTS.",
+                                    test.getClass().getCanonicalName()));
+                }
                 if (test instanceof HostTest) {
                     HostTest hostTest = (HostTest) test;
                     // We inject a made up folder so that it can find the tests.
@@ -128,7 +175,27 @@
                     int testCount = hostTest.countTestCases();
                     if (testCount == 0) {
                         throw new ConfigurationException(
-                                String.format("%s: %s reports 0 test cases.", config, test));
+                                String.format("%s: %s reports 0 test cases.",
+                                        config.getName(), test));
+                    }
+                }
+                // Tests are expected to implement that interface.
+                if (!(test instanceof ITestFilterReceiver)) {
+                    throw new IllegalArgumentException(String.format(
+                            "Test in module %s must implement ITestFilterReceiver.",
+                            config.getName()));
+                }
+                // Ensure that the device runner is the AJUR one
+                if (test instanceof AndroidJUnitTest) {
+                    AndroidJUnitTest instru = (AndroidJUnitTest) test;
+                    if (!INSTRUMENTATION_RUNNER_NAME.equals(instru.getRunnerName())) {
+                        // Some runner are exempt
+                        if (!RUNNER_EXCEPTION.contains(instru.getRunnerName())) {
+                            throw new ConfigurationException(
+                                    String.format("%s: uses '%s' instead of the '%s' that is "
+                                            + "expected", config.getName(), instru.getRunnerName(),
+                                            INSTRUMENTATION_RUNNER_NAME));
+                        }
                     }
                 }
             }
@@ -148,6 +215,13 @@
             Assert.assertTrue(String.format("Module config contains unknown \"component\" metadata "
                     + "field \"%s\", supported ones are: %s\nconfig: %s",
                     cmp, KNOWN_COMPONENTS, config), KNOWN_COMPONENTS.contains(cmp));
+
+            // Ensure each CTS module is tagged with <option name="test-suite-tag" value="cts" />
+            Assert.assertTrue(String.format(
+                    "Module config %s does not contains "
+                    + "'<option name=\"test-suite-tag\" value=\"cts\" />'", config.getName()),
+                    cd.getSuiteTags().contains("cts"));
+
             // Check not-shardable: JarHostTest cannot create empty shards so it should never need
             // to be not-shardable.
             if (cd.isNotShardable()) {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
index 4dbd3a9..a6e4090 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/IntegrationTest.java
@@ -24,7 +24,6 @@
 import com.android.compatibility.common.tradefed.testtype.IModuleDef;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.TestStatus;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.OptionSetter;
@@ -35,6 +34,7 @@
 import com.android.tradefed.invoker.ShardListener;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IInvocationContextReceiver;
@@ -259,7 +259,7 @@
                         .getAttributes().get(IModuleDef.MODULE_ABI));
             }
             @Override
-            public void testEnded(TestIdentifier test, long endTime,
+            public void testEnded(TestDescription test, long endTime,
                     Map<String, String> testMetrics) {
                 receivedComponentsTestEnded.addAll(myContext.getModuleInvocationContext()
                         .getConfigurationDescriptor().getMetaData("component"));
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
index 9102f1a..4f00387 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
@@ -16,9 +16,9 @@
 
 package com.android.compatibility.common.tradefed.result;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.AbiUtils;
 
 import junit.framework.TestCase;
@@ -94,24 +94,24 @@
 
     /** Run 4 test, but one is ignored */
     private void runTests() {
-        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        TestDescription test1 = new TestDescription(CLASS, METHOD_1);
         mReporter.testStarted(test1);
         mReporter.testEnded(test1, new HashMap<String, String>());
         assertFalse(mReporter.getTestFailed());
 
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         assertFalse(mReporter.getTestFailed());
         mReporter.testFailed(test2, STACK_TRACE);
         assertTrue(mReporter.getTestFailed());
 
-        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        TestDescription test3 = new TestDescription(CLASS, METHOD_3);
         mReporter.testStarted(test3);
         assertFalse(mReporter.getTestFailed());
         mReporter.testFailed(test3, STACK_TRACE);
         assertTrue(mReporter.getTestFailed());
 
-        TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+        TestDescription test4 = new TestDescription(CLASS, METHOD_3);
         mReporter.testStarted(test4);
         assertFalse(mReporter.getTestFailed());
         mReporter.testIgnored(test4);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
index 1f80dec..ba59ac1 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
@@ -18,12 +18,12 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.AbiUtils;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.RunUtil;
 
@@ -115,23 +115,23 @@
 
     /** Run 4 test. */
     private void runTests(long waitTime) {
-        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        TestDescription test1 = new TestDescription(CLASS, METHOD_1);
         mReporter.testStarted(test1);
         RunUtil.getDefault().sleep(waitTime);
         mReporter.testEnded(test1, new HashMap<String, String>());
 
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         RunUtil.getDefault().sleep(waitTime);
         mReporter.testEnded(test1, new HashMap<String, String>());
 
-        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        TestDescription test3 = new TestDescription(CLASS, METHOD_3);
         mReporter.testStarted(test3);
         RunUtil.getDefault().sleep(waitTime);
         mReporter.testFailed(test3, STACK_TRACE);
         mReporter.testEnded(test3, new HashMap<String, String>());
 
-        TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+        TestDescription test4 = new TestDescription(CLASS, METHOD_3);
         mReporter.testStarted(test4);
         RunUtil.getDefault().sleep(waitTime);
         mReporter.testIgnored(test4);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index c24e9df..d5382b8 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -24,7 +24,6 @@
 import com.android.compatibility.common.util.IModuleResult;
 import com.android.compatibility.common.util.ITestResult;
 import com.android.compatibility.common.util.TestStatus;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.invoker.IInvocationContext;
@@ -32,6 +31,7 @@
 import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 
@@ -144,13 +144,13 @@
     public void testResultReporting() throws Exception {
         mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 2);
-        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        TestDescription test1 = new TestDescription(CLASS, METHOD_1);
         mReporter.testStarted(test1);
         mReporter.testEnded(test1, new HashMap<String, String>());
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         mReporter.testFailed(test2, STACK_TRACE);
-        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        TestDescription test3 = new TestDescription(CLASS, METHOD_3);
         mReporter.testStarted(test3);
         mReporter.testFailed(test3, STACK_TRACE);
         mReporter.testEnded(test3, new HashMap<String, String>());
@@ -187,7 +187,7 @@
         mReporter.testRunStarted(ID, methods.length);
 
         for (int i = 0; i < methods.length; i++) {
-            TestIdentifier test = new TestIdentifier(CLASS, methods[i]);
+            TestDescription test = new TestDescription(CLASS, methods[i]);
             mReporter.testStarted(test);
             if (!passes[i]) {
                 mReporter.testFailed(test, STACK_TRACE);
@@ -263,11 +263,11 @@
         testResult2.setRetry(true);
 
         // Flip results for the current session
-        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        TestDescription test1 = new TestDescription(CLASS, METHOD_1);
         mReporter.testStarted(test1);
         mReporter.testFailed(test1, STACK_TRACE);
         mReporter.testEnded(test1, new HashMap<String, String>());
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         mReporter.testEnded(test2, new HashMap<String, String>());
 
@@ -323,7 +323,7 @@
         // been collected. Thus, module "done" value should switch.
         mReporter.testRunStarted(ID, 1);
 
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         mReporter.testEnded(test2, new HashMap<String, String>());
 
@@ -362,7 +362,7 @@
         // and don't run any non-executed tests, so module "done" value should not switch.
         mReporter.testRunStarted(ID, 1);
 
-        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        TestDescription test2 = new TestDescription(CLASS, METHOD_2);
         mReporter.testStarted(test2);
         mReporter.testEnded(test2, new HashMap<String, String>());
 
@@ -382,7 +382,7 @@
     public void testResultReporting_moduleNotDone() throws Exception {
         mReporter.invocationStarted(mContext);
         mReporter.testRunStarted(ID, 2);
-        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        TestDescription test1 = new TestDescription(CLASS, METHOD_1);
         mReporter.testStarted(test1);
         mReporter.testEnded(test1, new HashMap<String, String>());
         mReporter.testRunFailed("error");
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java
new file mode 100644
index 0000000..a59634b
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusherTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.targetprep;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Unit tests for {@link DynamicConfigPusher}.
+ */
+@RunWith(JUnit4.class)
+public class DynamicConfigPusherTest {
+    private static final String RESOURCE_DYNAMIC_CONFIG = "test-dynamic-config";
+    private DynamicConfigPusher mPreparer;
+    private ITestDevice mMockDevice;
+    private CompatibilityBuildHelper mMockBuildHelper;
+    private IBuildInfo mMockBuildInfo;
+
+    @Before
+    public void setUp() {
+        mPreparer = new DynamicConfigPusher();
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
+        mMockBuildHelper = new CompatibilityBuildHelper(mMockBuildInfo);
+        EasyMock.expect(mMockDevice.getDeviceDescriptor()).andStubReturn(null);
+    }
+
+    /**
+     * Test that when we look up resources locally, we search them from the build helper.
+     */
+    @Test
+    public void testLocalRead() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", "config-test-name");
+        setter.setOptionValue("extract-from-resource", "false");
+
+        File check = new File("anyfilewilldo");
+        mMockBuildHelper = new CompatibilityBuildHelper(mMockBuildInfo) {
+            @Override
+            public File getTestFile(String filename) throws FileNotFoundException {
+                return check;
+            }
+        };
+
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+        File res = mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+        assertEquals(check, res);
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test that when we look up resources locally, we search them from the build helper and throw
+     * if it's not found.
+     */
+    @Test
+    public void testLocalRead_fileNotFound() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", "config-test-name");
+        setter.setOptionValue("extract-from-resource", "false");
+
+        mMockBuildHelper = new CompatibilityBuildHelper(mMockBuildInfo) {
+            @Override
+            public File getTestFile(String filename) throws FileNotFoundException {
+                throw new FileNotFoundException("test");
+            }
+        };
+        try {
+            EasyMock.replay(mMockDevice, mMockBuildInfo);
+            mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+            fail("Should have thrown an exception.");
+        } catch (TargetSetupError expected) {
+            // expected
+            assertEquals("Cannot get local dynamic config file from test directory null",
+                    expected.getMessage());
+        }
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test when we try to unpack a resource but it does not exists.
+     */
+    @Test
+    public void testResourceRead_notFound() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", "not-an-existing-resource-name");
+        setter.setOptionValue("extract-from-resource", "true");
+        try {
+            EasyMock.replay(mMockDevice, mMockBuildInfo);
+            mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+            fail("Should have thrown an exception.");
+        } catch (TargetSetupError expected) {
+            // expected
+            assertEquals("Fail to unpack 'not-an-existing-resource-name.dynamic' from resources "
+                    + "null", expected.getMessage());
+        }
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test when we get a config from the resources.
+     */
+    @Test
+    public void testResourceRead() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", RESOURCE_DYNAMIC_CONFIG);
+        setter.setOptionValue("extract-from-resource", "true");
+        File res = null;
+        try {
+            EasyMock.replay(mMockDevice, mMockBuildInfo);
+            res = mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+            assertTrue(res.exists());
+            assertTrue(FileUtil.readStringFromFile(res).contains("<dynamicConfig>"));
+        } finally {
+            FileUtil.deleteFile(res);
+        }
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Test when we get a config from the resources under the alternative name.
+     */
+    @Test
+    public void testResourceRead_resourceFileName() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("config-filename", "moduleName");
+        setter.setOptionValue("extract-from-resource", "true");
+        // Look up the file under that name instead of the config-filename
+        setter.setOptionValue("dynamic-resource-name", RESOURCE_DYNAMIC_CONFIG);
+        File res = null;
+        try {
+            EasyMock.replay(mMockDevice, mMockBuildInfo);
+            res = mPreparer.getLocalConfigFile(mMockBuildHelper, mMockDevice);
+            assertTrue(res.exists());
+            assertTrue(FileUtil.readStringFromFile(res).contains("<dynamicConfig>"));
+        } finally {
+            FileUtil.deleteFile(res);
+        }
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java
deleted file mode 100644
index 66d05fc..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.tradefed.testtype;
-
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.JUnit4ResultForwarder;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.IDeviceTest;
-
-import junit.framework.TestCase;
-
-import org.easymock.EasyMock;
-import org.junit.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.RunWith;
-import org.junit.runner.Runner;
-
-import java.util.Collections;
-
-/**
- * Tests for the CompatibilityHostTestBase class.
- */
-public class CompatibilityHostTestBaseTest extends TestCase {
-
-    private static final String DEVICE_TEST_PKG = "com.android.foo";
-
-    @RunWith(DeviceJUnit4ClassRunner.class)
-    public static class MockTest extends CompatibilityHostTestBase {
-
-        @Test
-        public void testRunDeviceTests() throws Exception {
-            runDeviceTests(DEVICE_TEST_PKG, null, null);
-        }
-
-        @Override
-        protected CollectingTestListener createCollectingListener() {
-            return new CollectingTestListener() {
-                @Override
-                public TestRunResult getCurrentRunResults() {
-                    TestRunResult result = new TestRunResult();
-                    TestIdentifier t1 = new TestIdentifier("class1", "test1");
-                    result.testStarted(t1);
-                    result.testEnded(t1, Collections.emptyMap());
-                    return result;
-                }
-            };
-        }
-
-    }
-
-    public void testRunMockDeviceTests() throws Exception {
-        final TestIdentifier testRunDeviceTests =
-                new TestIdentifier(MockTest.class.getName(), "testRunDeviceTests");
-
-        ITestInvocationListener listener = EasyMock.createStrictMock(ITestInvocationListener.class);
-        ITestDevice device = EasyMock.createMock(ITestDevice.class);
-
-        listener.testStarted(testRunDeviceTests);
-        EasyMock.expect(device.getIDevice()).andReturn(EasyMock.createMock(IDevice.class)).once();
-        EasyMock.expect(device.runInstrumentationTests((RemoteAndroidTestRunner)
-                EasyMock.anyObject(), (CollectingTestListener) EasyMock.anyObject())).andReturn(
-                true).once();
-        listener.testEnded(testRunDeviceTests, Collections.emptyMap());
-        EasyMock.replay(listener, device);
-
-        JUnitCore runnerCore = new JUnitCore();
-        runnerCore.addListener(new JUnit4ResultForwarder(listener));
-        Runner checkRunner = Request.aClass(MockTest.class).getRunner();
-        ((IDeviceTest) checkRunner).setDevice(device);
-        ((IBuildReceiver) checkRunner).setBuild(EasyMock.createMock(IBuildInfo.class));
-        ((IAbiReceiver) checkRunner).setAbi(EasyMock.createMock(IAbi.class));
-        runnerCore.run(checkRunner);
-        EasyMock.verify(listener, device);
-    }
-
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
index a06bca0..fb361d2 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
@@ -15,19 +15,28 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 import com.android.tradefed.testtype.HostTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.util.FileUtil;
 
-import junit.framework.TestCase;
-
 import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
@@ -39,17 +48,21 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Unit tests for {@link JarHostTest}.
  */
-public class JarHostTestTest extends TestCase {
+@RunWith(JUnit4.class)
+public class JarHostTestTest {
 
     private static final String TEST_JAR1 = "/testtype/testJar1.jar";
     private static final String TEST_JAR2 = "/testtype/testJar2.jar";
     private JarHostTest mTest;
     private File mTestDir = null;
+    private ITestInvocationListener mListener;
 
     /**
      * More testable version of {@link JarHostTest}
@@ -74,23 +87,16 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mTest = new JarHostTest();
         mTestDir = FileUtil.createTempDir("jarhostest");
+        mListener = EasyMock.createMock(ITestInvocationListener.class);
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         FileUtil.recursiveDelete(mTestDir);
-        super.tearDown();
     }
 
     /**
@@ -123,13 +129,18 @@
     @RunWith(JUnit4.class)
     public static class Junit4TestClass2  {
         public Junit4TestClass2() {}
+        @Rule public TestMetrics metrics = new TestMetrics();
+
         @org.junit.Test
-        public void testPass2() {}
+        public void testPass2() {
+            metrics.addTestMetric("key", "value");
+        }
     }
 
     /**
      * Test that {@link JarHostTest#split()} inherited from {@link HostTest} is still good.
      */
+    @Test
     public void testSplit_withoutJar() throws Exception {
         OptionSetter setter = new OptionSetter(mTest);
         setter.setOptionValue("class", "com.android.compatibility.common.tradefed.testtype."
@@ -146,6 +157,7 @@
     /**
      * Test that {@link JarHostTest#split()} can split classes coming from a jar.
      */
+    @Test
     public void testSplit_withJar() throws Exception {
         File testJar = getJarResource(TEST_JAR1, mTestDir);
         mTest = new JarHostTestable(mTestDir);
@@ -165,6 +177,7 @@
     /**
      * Test that {@link JarHostTest#getTestShard(int, int)} can split classes coming from a jar.
      */
+    @Test
     public void testGetTestShard_withJar() throws Exception {
         File testJar = getJarResource(TEST_JAR2, mTestDir);
         mTest = new JarHostTestLoader(mTestDir, testJar);
@@ -259,4 +272,45 @@
             return child;
         }
     }
+
+    /**
+     * If a jar file is not found, the countTest will fail but we still want to report a
+     * testRunStart and End pair for results.
+     */
+    @Test
+    public void testCountTestFails() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue("jar", "thisjardoesnotexistatall.jar");
+        mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(0));
+        mListener.testRunEnded(EasyMock.anyLong(), (Map<String, String>) EasyMock.anyObject());
+        EasyMock.replay(mListener);
+        try {
+            mTest.run(mListener);
+            fail("Should have thrown an exception.");
+        } catch(RuntimeException expected) {
+            // expected
+        }
+        EasyMock.verify(mListener);
+    }
+
+    /**
+     * Test that metrics from tests in JarHost are reported and accounted for.
+     */
+    @Test
+    public void testJarHostMetrics() throws Exception {
+        OptionSetter setter = new OptionSetter(mTest);
+        setter.setOptionValue("class", "com.android.compatibility.common.tradefed.testtype."
+                + "JarHostTestTest$Junit4TestClass2");
+        mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(1));
+        TestDescription tid = new TestDescription("com.android.compatibility.common.tradefed."
+                + "testtype.JarHostTestTest$Junit4TestClass2", "testPass2");
+        mListener.testStarted(EasyMock.eq(tid), EasyMock.anyLong());
+        Map<String, String> metrics = new HashMap<>();
+        metrics.put("key", "value");
+        mListener.testEnded(EasyMock.eq(tid), EasyMock.anyLong(), EasyMock.eq(metrics));
+        mListener.testRunEnded(EasyMock.anyLong(), (Map<String, String>) EasyMock.anyObject());
+        EasyMock.replay(mListener);
+        mTest.run(mListener);
+        EasyMock.verify(mListener);
+    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
index 331f978..aacda6a 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/SimpleTestStub.java
@@ -15,10 +15,10 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
@@ -52,7 +52,7 @@
     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
         // We report 1 passing tes
         listener.testRunStarted("module-run", 1);
-        TestIdentifier tid = new TestIdentifier("TestStub", "test1");
+        TestDescription tid = new TestDescription("TestStub", "test1");
         listener.testStarted(tid);
         listener.testEnded(tid, Collections.emptyMap());
         listener.testRunEnded(0, Collections.emptyMap());
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
index a154f31..a493d3f 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStub.java
@@ -15,10 +15,10 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
@@ -51,7 +51,7 @@
     @Option(name = "internal-retry")
     protected boolean mRetry = false;
 
-    protected List<TestIdentifier> mShardedTestToRun;
+    protected List<TestDescription> mShardedTestToRun;
     protected Integer mShardIndex = null;
 
     /**
@@ -60,18 +60,18 @@
     private void testAttempt(ITestInvocationListener listener) {
      // We report 3 tests: 2 pass, 1 failed
         listener.testRunStarted("module-run", 3);
-        TestIdentifier tid = new TestIdentifier("TestStub", "test1");
+        TestDescription tid = new TestDescription("TestStub", "test1");
         listener.testStarted(tid);
         listener.testEnded(tid, Collections.emptyMap());
 
         if (mIsComplete) {
             // possibly skip this one to create some not_executed case.
-            TestIdentifier tid2 = new TestIdentifier("TestStub", "test2");
+            TestDescription tid2 = new TestDescription("TestStub", "test2");
             listener.testStarted(tid2);
             listener.testEnded(tid2, Collections.emptyMap());
         }
 
-        TestIdentifier tid3 = new TestIdentifier("TestStub", "test3");
+        TestDescription tid3 = new TestDescription("TestStub", "test3");
         listener.testStarted(tid3);
         if (mDoesOneTestFail) {
             listener.testFailed(tid3, "ouch this is bad.");
@@ -105,18 +105,18 @@
                 }
 
                 if (mIsComplete) {
-                    for (TestIdentifier tid : mShardedTestToRun) {
+                    for (TestDescription tid : mShardedTestToRun) {
                         listener.testStarted(tid);
                         listener.testEnded(tid, Collections.emptyMap());
                     }
                 } else {
-                    TestIdentifier tid = mShardedTestToRun.get(0);
+                    TestDescription tid = mShardedTestToRun.get(0);
                     listener.testStarted(tid);
                     listener.testEnded(tid, Collections.emptyMap());
                 }
 
                 if (mDoesOneTestFail) {
-                    TestIdentifier tid = new TestIdentifier("TestStub", "failed" + mShardIndex);
+                    TestDescription tid = new TestDescription("TestStub", "failed" + mShardIndex);
                     listener.testStarted(tid);
                     listener.testFailed(tid, "shard failed this one.");
                     listener.testEnded(tid, Collections.emptyMap());
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java
index 2789b82..d3d7b41 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestStubShardable.java
@@ -15,8 +15,8 @@
  */
 package com.android.compatibility.common.tradefed.testtype;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IStrictShardableTest;
 
@@ -32,10 +32,10 @@
         TestStubShardable test = new TestStubShardable();
         OptionCopier.copyOptionsNoThrow(this, test);
         test.mShardedTestToRun = new ArrayList<>();
-        TestIdentifier tid = new TestIdentifier("TestStub", "test" + shardIndex);
+        TestDescription tid = new TestDescription("TestStub", "test" + shardIndex);
         test.mShardedTestToRun.add(tid);
         if (mIsComplete == false) {
-            TestIdentifier tid2 = new TestIdentifier("TestStub", "test" + shardIndex + 100);
+            TestDescription tid2 = new TestDescription("TestStub", "test" + shardIndex + 100);
             test.mShardedTestToRun.add(tid2);
             test.mIsComplete = false;
         }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
index d67619a..1f2c544 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
@@ -15,18 +15,24 @@
  */
 package com.android.compatibility.common.tradefed.testtype.retry;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
 import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.ConfigurationDef;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.suite.checker.ISystemStatusChecker;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.StubTest;
 
@@ -40,6 +46,7 @@
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Unit tests for {@link RetryFactoryTest}.
@@ -51,8 +58,14 @@
     private ITestInvocationListener mMockListener;
     private RetryFilterHelper mSpyFilter;
 
+    private List<ISystemStatusChecker> mCheckers;
+    private IBuildInfo mMockInfo;
+    private ITestDevice mMockDevice;
+    private IConfiguration mMockMainConfiguration;
+    private IInvocationContext mMockContext;
+
     /**
-     * A {@link CompatibilityTest} that does not run anything.
+     * A {@link CompatibilityTestSuite} that does not run anything.
      */
     @OptionClass(alias = "compatibility")
     public static class VoidCompatibilityTest extends CompatibilityTestSuite {
@@ -71,8 +84,28 @@
         }
     }
 
+    @OptionClass(alias = "compatibility")
+    public static class TestCompatibilityTestSuite extends CompatibilityTestSuite {
+        @Override
+        public LinkedHashMap<String, IConfiguration> loadTests() {
+            LinkedHashMap<String, IConfiguration> tests = new LinkedHashMap<>();
+            IConfiguration config = new Configuration("test", "test");
+            config.setTest(new StubTest());
+            tests.put("module1", config);
+            return tests;
+        }
+    }
+
     @Before
     public void setUp() {
+        mMockMainConfiguration = new Configuration("mockMain", "mockMain");
+        mCheckers = new ArrayList<>();
+        mMockInfo = EasyMock.createMock(IDeviceBuildInfo.class);
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockContext = new InvocationContext();
+        mMockContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
+        mMockContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockInfo);
+
         mSpyFilter = new RetryFilterHelper() {
             @Override
             public void validateBuildFingerprint(ITestDevice device)
@@ -90,7 +123,7 @@
         };
         mFactory = new RetryFactoryTest() {
             @Override
-            RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+            protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
                 return mSpyFilter;
             }
             @Override
@@ -108,6 +141,7 @@
     public void testRetry_receiveOption() throws Exception {
         OptionSetter setter = new OptionSetter(mFactory);
         setter.setOptionValue("retry", "10599");
+        setter.setOptionValue("test-arg", "abcd");
         EasyMock.replay(mMockListener);
         mFactory.run(mMockListener);
         EasyMock.verify(mMockListener);
@@ -124,4 +158,36 @@
         assertEquals(2, res.size());
         EasyMock.verify(mMockListener);
     }
+
+    /**
+     * This test is meant to validate more end-to-end that the retry can create the runner, and
+     * running it works properly for the main use case.
+     */
+    @Test
+    public void testValidation() throws Exception {
+        mFactory = new RetryFactoryTest() {
+            @Override
+            protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+                return mSpyFilter;
+            }
+            @Override
+            CompatibilityTestSuite createTest() {
+                return new TestCompatibilityTestSuite();
+            }
+        };
+        mFactory.setBuild(mMockInfo);
+        mFactory.setDevice(mMockDevice);
+        mFactory.setSystemStatusChecker(mCheckers);
+        mFactory.setConfiguration(mMockMainConfiguration);
+        mFactory.setInvocationContext(mMockContext);
+
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testRunStarted("module1", 0);
+        mMockListener.testRunEnded(EasyMock.anyLong(), (Map<String, String>) EasyMock.anyObject());
+        mMockListener.testModuleEnded();
+
+        EasyMock.replay(mMockListener, mMockInfo, mMockDevice);
+        mFactory.run(mMockListener);
+        EasyMock.verify(mMockListener, mMockInfo, mMockDevice);
+    }
 }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
deleted file mode 100644
index 86569ef..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.testtype.suite;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.util.AbiUtils;
-
-import org.easymock.EasyMock;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Unit tests for {@link CompatibilityTestSuite}.
- */
-public class CompatibilityTestSuiteTest {
-
-    private static final String FAKE_HOST_ARCH = "arm";
-    private CompatibilityTestSuite mTest;
-    private ITestDevice mMockDevice;
-
-    @Before
-    public void setUp() throws Exception {
-        mTest = new CompatibilityTestSuite() {
-            @Override
-            protected Set<String> getAbisForBuildTargetArch() {
-                return AbiUtils.getAbisForArch(FAKE_HOST_ARCH);
-            }
-        };
-        mMockDevice = EasyMock.createMock(ITestDevice.class);
-        mTest.setDevice(mMockDevice);
-    }
-
-    /**
-     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning a proper
-     * intersection of CTS supported architectures and Device supported architectures.
-     */
-    @Test
-    public void testGetAbis() throws DeviceNotAvailableException {
-        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
-                .andReturn("arm64-v8a,armeabi-v7a,armeabi");
-        Set<String> expectedAbis = new HashSet<>();
-        expectedAbis.add("arm64-v8a");
-        expectedAbis.add("armeabi-v7a");
-        EasyMock.replay(mMockDevice);
-        Set<IAbi> res = mTest.getAbis(mMockDevice);
-        assertEquals(2, res.size());
-        for (IAbi abi : res) {
-            assertTrue(expectedAbis.contains(abi.getName()));
-        }
-        EasyMock.verify(mMockDevice);
-    }
-
-    /**
-     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception when
-     * none of the CTS build supported abi match the device abi.
-     */
-    @Test
-    public void testGetAbis_notSupported() throws DeviceNotAvailableException {
-        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
-                .andReturn("armeabi");
-        EasyMock.replay(mMockDevice);
-        try {
-            mTest.getAbis(mMockDevice);
-            fail("Should have thrown an exception");
-        } catch (IllegalArgumentException e) {
-            assertEquals("None of the abi supported by this CTS build ('[armeabi-v7a, arm64-v8a]')"
-                    + " are supported by the device ('[armeabi]').", e.getMessage());
-        }
-        EasyMock.verify(mMockDevice);
-    }
-
-    /**
-     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning only the device
-     * primary abi.
-     */
-    @Test
-    public void testGetAbis_primaryAbiOnly() throws Exception {
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
-        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
-                .andReturn("arm64-v8a");
-        Set<String> expectedAbis = new HashSet<>();
-        expectedAbis.add("arm64-v8a");
-        EasyMock.replay(mMockDevice);
-        Set<IAbi> res = mTest.getAbis(mMockDevice);
-        assertEquals(1, res.size());
-        for (IAbi abi : res) {
-            assertTrue(expectedAbis.contains(abi.getName()));
-        }
-        EasyMock.verify(mMockDevice);
-    }
-
-    /**
-     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception if
-     * the primary abi is not supported.
-     */
-    @Test
-    public void testGetAbis_primaryAbiOnly_NotSupported() throws Exception {
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
-        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
-                .andReturn("armeabi");
-        EasyMock.replay(mMockDevice);
-        try {
-            mTest.getAbis(mMockDevice);
-            fail("Should have thrown an exception");
-        } catch (IllegalArgumentException e) {
-            assertEquals("Your CTS hasn't been built with abi 'armeabi' support, "
-                    + "this CTS currently supports '[armeabi-v7a, arm64-v8a]'.", e.getMessage());
-        }
-        EasyMock.verify(mMockDevice);
-    }
-
-    /**
-     * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning the list of abi
-     * supported by Compatibility and the device, and not the particular CTS build.
-     */
-    @Test
-    public void testGetAbis_skipCtsArchCheck() throws Exception {
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
-        EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
-                .andReturn("x86_64,x86,armeabi");
-        Set<String> expectedAbis = new HashSet<>();
-        expectedAbis.add("x86_64");
-        expectedAbis.add("x86");
-        EasyMock.replay(mMockDevice);
-        Set<IAbi> res = mTest.getAbis(mMockDevice);
-        assertEquals(2, res.size());
-        for (IAbi abi : res) {
-            assertTrue(expectedAbis.contains(abi.getName()));
-        }
-        EasyMock.verify(mMockDevice);
-    }
-
-    /**
-     * Test {@link CompatibilityTestSuite#getAbis(ITestDevice)} when we skip the Cts side
-     * architecture check and want to run x86 abi.
-     */
-    @Test
-    public void testGetAbis_skipCtsArchCheck_abiSpecified() throws Exception {
-        OptionSetter setter = new OptionSetter(mTest);
-        setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
-        setter.setOptionValue(CompatibilityTest.ABI_OPTION, "x86");
-        Set<String> expectedAbis = new HashSet<>();
-        expectedAbis.add("x86");
-        EasyMock.replay(mMockDevice);
-        Set<IAbi> res = mTest.getAbis(mMockDevice);
-        assertEquals(1, res.size());
-        for (IAbi abi : res) {
-            assertTrue(expectedAbis.contains(abi.getName()));
-        }
-        EasyMock.verify(mMockDevice);
-    }
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java
deleted file mode 100644
index c67afc0..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.testtype.suite;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.ConfigurationDescriptor;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.util.MultiMap;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Unit tests for {@link ModuleRepoSuite}.
- */
-@RunWith(JUnit4.class)
-public class ModuleRepoSuiteTest {
-
-    private static final MultiMap<String, String> METADATA_INCLUDES = new MultiMap<>();
-    private static final MultiMap<String, String> METADATA_EXCLUDES = new MultiMap<>();
-    private ModuleRepoSuite mRepo;
-
-    @Before
-    public void setUp() {
-        mRepo = new ModuleRepoSuite();
-    }
-
-    /**
-     * When there are no metadata based filters specified, config should be included.
-     */
-    @Test
-    public void testMetadataFilter_emptyFilters() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        assertTrue("config not included when metadata filters are empty",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When inclusion filter is specified, config matching the filter is included.
-     */
-    @Test
-    public void testMetadataFilter_matchInclude() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo");
-        assertTrue("config not included with matching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When inclusion filter is specified, config not matching the filter is excluded
-     */
-    @Test
-    public void testMetadataFilter_noMatchInclude_mismatchValue() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "bar");
-        assertFalse("config not excluded with mismatching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When inclusion filter is specified, config not matching the filter is excluded.
-     */
-    @Test
-    public void testMetadataFilter_noMatchInclude_mismatchKey() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("group", "bar");
-        assertFalse("config not excluded with mismatching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When exclusion filter is specified, config matching the filter is excluded.
-     */
-    @Test
-    public void testMetadataFilter_matchExclude() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("component", "foo");
-        assertFalse("config not excluded with matching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When exclusion filter is specified, config not matching the filter is included.
-     */
-    @Test
-    public void testMetadataFilter_noMatchExclude_mismatchKey() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("component", "bar");
-        assertTrue("config not included with mismatching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When exclusion filter is specified, config not matching the filter is included.
-     */
-    @Test
-    public void testMetadataFilter_noMatchExclude_mismatchValue() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("group", "bar");
-        assertTrue("config not included with mismatching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When inclusion filter is specified, config with one of the metadata field matching the filter
-     * is included.
-     */
-    @Test
-    public void testMetadataFilter_matchInclude_multipleMetadataField() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        metadata.put("component", "bar");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo");
-        assertTrue("config not included with matching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When exclusion filter is specified, config with one of the metadata field matching the filter
-     * is excluded.
-     */
-    @Test
-    public void testMetadataFilter_matchExclude_multipleMetadataField() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        metadata.put("component", "bar");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("component", "foo");
-        assertFalse("config not excluded with matching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When inclusion filters are specified, config with metadata field matching one of the filter
-     * is included.
-     */
-    @Test
-    public void testMetadataFilter_matchInclude_multipleFilters() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo");
-        includeFilter.put("component", "bar");
-        assertTrue("config not included with matching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When exclusion filters are specified, config with metadata field matching one of the filter
-     * is excluded.
-     */
-    @Test
-    public void testMetadataFilter_matchExclude_multipleFilters() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("component", "foo");
-        excludeFilter.put("component", "bar");
-        assertFalse("config not excluded with matching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When inclusion filters are specified, config with metadata field matching one of the filter
-     * is included.
-     */
-    @Test
-    public void testMetadataFilter_matchInclude_multipleMetadataAndFilters() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo1");
-        metadata.put("group", "bar1");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo1");
-        includeFilter.put("group", "bar2");
-        assertTrue("config not included with matching inclusion filter",
-                mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
-    }
-
-    /**
-     * When exclusion filters are specified, config with metadata field matching one of the filter
-     * is excluded.
-     */
-    @Test
-    public void testMetadataFilter_matchExclude_multipleMetadataAndFilters() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo1");
-        metadata.put("group", "bar1");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("component", "foo1");
-        excludeFilter.put("group", "bar2");
-        assertFalse("config not excluded with matching exclusion filter",
-                mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
-    }
-
-    /**
-     * When inclusion and exclusion filters are both specified, config can pass through the filters
-     * as expected.
-     */
-    @Test
-    public void testMetadataFilter_includeAndExclude() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        metadata.put("group", "bar1");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo");
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("group", "bar2");
-        assertTrue("config not included with matching inclusion and mismatching exclusion filters",
-                mRepo.filterByConfigMetadata(config, includeFilter, excludeFilter));
-    }
-
-    /**
-     * When inclusion and exclusion filters are both specified, config be excluded as specified
-     */
-    @Test
-    public void testMetadataFilter_includeThenExclude() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        ConfigurationDescriptor desc = config.getConfigurationDescription();
-        MultiMap<String, String> metadata = new MultiMap<>();
-        metadata.put("component", "foo");
-        metadata.put("group", "bar");
-        desc.setMetaData(metadata);
-        MultiMap<String, String> includeFilter = new MultiMap<>();
-        includeFilter.put("component", "foo");
-        MultiMap<String, String> excludeFilter = new MultiMap<>();
-        excludeFilter.put("group", "bar");
-        assertFalse("config not excluded with matching inclusion and exclusion filters",
-                mRepo.filterByConfigMetadata(config, includeFilter, excludeFilter));
-    }
-
-    public static class TestInject implements IRemoteTest {
-        @Option(name = "simple-string")
-        public String test = null;
-        @Option(name = "list-string")
-        public List<String> testList = new ArrayList<>();
-        @Option(name = "map-string")
-        public Map<String, String> testMap = new HashMap<>();
-
-        @Override
-        public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
-        }
-    }
-
-    /**
-     * Test that the different format for module-arg and test-arg can properly be passed to the
-     * configuration.
-     */
-    @Test
-    public void testInjectConfig() throws Exception {
-        IConfiguration config = new Configuration("foo", "bar");
-        TestInject checker = new TestInject();
-        config.setTest(checker);
-        Map<String, List<String>> optionMap = new HashMap<String, List<String>>();
-        List<String> option1 = new ArrayList<>();
-        option1.add("value1");
-        optionMap.put("simple-string", option1);
-
-        List<String> option2 = new ArrayList<>();
-        option2.add("value2");
-        option2.add("value3");
-        option2.add("set-option:moreoption");
-        optionMap.put("list-string", option2);
-
-        List<String> option3 = new ArrayList<>();
-        option3.add("set-option:=moreoption");
-        optionMap.put("map-string", option3);
-
-        mRepo.injectOptionsToConfig(optionMap, config);
-
-        assertEquals("value1", checker.test);
-        assertEquals(option2, checker.testList);
-        Map<String, String> resMap = new HashMap<>();
-        resMap.put("set-option", "moreoption");
-        assertEquals(resMap, checker.testMap);
-    }
-}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
index 5d303e5..369189d 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
@@ -18,13 +18,12 @@
 import static org.junit.Assert.fail;
 
 import com.android.compatibility.common.util.HostInfoStore;
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
-import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 
@@ -38,37 +37,18 @@
  * Collect device information from host and write to a JSON file.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public abstract class DeviceInfo implements IDeviceTest {
+public abstract class DeviceInfo extends BaseHostJUnit4Test {
 
     // Default name of the directory for storing device info files within the result directory
     public static final String RESULT_DIR_NAME = "device-info-files";
 
     public static final String FILE_SUFFIX = ".deviceinfo.json";
 
-    /** A reference to the device under test. */
-    protected ITestDevice mDevice;
-
     private HostInfoStore mStore;
 
     @Rule
     public TestLogData mLogger = new TestLogData();
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public ITestDevice getDevice() {
-        return mDevice;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setDevice(ITestDevice device) {
-        mDevice = device;
-    }
-
     @Test
     public void testCollectDeviceInfo() throws Exception {
         String deviceInfoName = getClass().getSimpleName() + FILE_SUFFIX;
diff --git a/common/util/src/com/android/compatibility/common/util/BusinessLogic.java b/common/util/src/com/android/compatibility/common/util/BusinessLogic.java
index adab1eb..26fae1e 100644
--- a/common/util/src/com/android/compatibility/common/util/BusinessLogic.java
+++ b/common/util/src/com/android/compatibility/common/util/BusinessLogic.java
@@ -30,6 +30,9 @@
 
     /* A map from testcase name to the business logic rules for the test case */
     protected Map<String, List<BusinessLogicRule>> mRules;
+    /* Feature flag determining if device specific tests are executed. */
+    public boolean mConditionalTestsEnabled;
+    private AuthenticationStatusEnum mAuthenticationStatus = AuthenticationStatusEnum.UNKNOWN;
 
     /**
      * Determines whether business logic exists for a given test name
@@ -61,6 +64,40 @@
         }
     }
 
+    public void setAuthenticationStatus(String authenticationStatus) {
+        try {
+            mAuthenticationStatus = Enum.valueOf(AuthenticationStatusEnum.class,
+                    authenticationStatus);
+        } catch (IllegalArgumentException e) {
+            // Invalid value, set to unknown
+            mAuthenticationStatus = AuthenticationStatusEnum.UNKNOWN;
+        }
+    }
+
+    public boolean isAuthorized() {
+        return AuthenticationStatusEnum.AUTHORIZED.equals(mAuthenticationStatus);
+    }
+
+    /**
+     * Builds a user readable string tha explains the authentication status and the effect on tests
+     * which require authentication to execute.
+     */
+    public String getAuthenticationStatusMessage() {
+        switch (mAuthenticationStatus) {
+            case AUTHORIZED:
+                return "Authorized";
+            case NOT_AUTHENTICATED:
+                return "authorization failed, please ensure the service account key is "
+                        + "properly installed.";
+            case NOT_AUTHORIZED:
+                return "service account is not authorized to access information for this device. "
+                        + "Please verify device properties are set correctly and account "
+                        + "permissions are configured in Google's systems.";
+            default:
+                return "something went wrong, please try again.";
+        }
+    }
+
     /**
      * Nested class representing an Business Logic Rule. Stores a collection of conditions
      * and actions for later invokation.
@@ -153,4 +190,15 @@
                     mMethodArgs.toArray(new String[mMethodArgs.size()]));
         }
     }
+
+    /**
+     * Nested enum of the possible authentication statuses.
+     */
+    protected enum AuthenticationStatusEnum {
+        UNKNOWN,
+        NOT_AUTHENTICATED,
+        NOT_AUTHORIZED,
+        AUTHORIZED
+    }
+
 }
diff --git a/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java b/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
index 2d3db01..4a0087e 100644
--- a/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
+++ b/common/util/src/com/android/compatibility/common/util/BusinessLogicFactory.java
@@ -51,6 +51,9 @@
     private static final String METHOD_NAME = "methodName";
     // Name of method args array of strings
     private static final String METHOD_ARGS = "methodArgs";
+    // Name of the field in the response object that stores that the auth status of the request.
+    private static final String AUTHENTICATION_STATUS = "authenticationStatus";
+    public static final String CONDITIONAL_TESTS_ENABLED = "conditionalTestsEnabled";
 
     /**
      * Create a BusinessLogic instance from a file of business logic data, formatted in JSON.
@@ -65,6 +68,14 @@
             String businessLogicString = readFile(f);
             JSONObject root = new JSONObject(businessLogicString);
             JSONArray rulesLists = null;
+            if (root.has(AUTHENTICATION_STATUS)){
+                String authStatus = root.getString(AUTHENTICATION_STATUS);
+                bl.setAuthenticationStatus(authStatus);
+            }
+            if (root.has(CONDITIONAL_TESTS_ENABLED)){
+                boolean enabled = root.getBoolean(CONDITIONAL_TESTS_ENABLED);
+                bl.mConditionalTestsEnabled = enabled;
+            }
             try {
                 rulesLists = root.getJSONArray(BUSINESS_LOGIC_RULES_LISTS);
             } catch (JSONException e) {
diff --git a/common/util/src/com/android/compatibility/common/util/LogcatInspector.java b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
new file mode 100644
index 0000000..ed82307
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
@@ -0,0 +1,130 @@
+package com.android.compatibility.common.util;
+
+import static junit.framework.TestCase.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Inherit this class and implement {@link #executeShellCommand(String)} to be able to assert that
+ * logcat contains what you want.
+ */
+public abstract class LogcatInspector {
+    private static final int SMALL_LOGCAT_DELAY = 1000;
+
+    /**
+     * Should execute adb shell {@param command} and return an {@link InputStream} with the result.
+     */
+    protected abstract InputStream executeShellCommand(String command) throws IOException;
+
+    /**
+     * Logs an unique string using tag {@param tag} and wait until it appears to continue execution.
+     *
+     * @return a unique separator string.
+     * @throws IOException if error while executing command.
+     */
+    public String mark(String tag) throws IOException {
+        String uniqueString = ":::" + UUID.randomUUID().toString();
+        executeShellCommand("log -t " + tag + " " + uniqueString);
+        // This is to guarantee that we only return after the string has been logged, otherwise
+        // in practice the case where calling Log.?(<message1>) right after clearAndMark() resulted
+        // in <message1> appearing before the unique identifier. It's not guaranteed per the docs
+        // that log command will have written when returning, so better be safe. 5s should be fine.
+        assertLogcatContainsInOrder(tag + ":* *:S", 5, uniqueString);
+        return uniqueString;
+    }
+
+    /**
+     * Wait for up to {@param maxTimeoutInSeconds} for the given {@param logcatStrings} strings to
+     * appear in logcat in the given order. By passing the separator returned by {@link
+     * #mark(String)} as the first string you can ensure that only logs emitted after that
+     * call to mark() are found. Repeated strings are not supported.
+     *
+     * @throws AssertionError if the strings are not found in the given time.
+     * @throws IOException if error while reading.
+     */
+    public void assertLogcatContainsInOrder(
+            String filterSpec, int maxTimeoutInSeconds, String... logcatStrings)
+            throws AssertionError, IOException {
+        try {
+            int nextStringIndex =
+                    numberOfLogcatStringsFound(filterSpec, maxTimeoutInSeconds, logcatStrings);
+            if (nextStringIndex < logcatStrings.length) {
+                fail(
+                        "Couldn't find "
+                                + logcatStrings[nextStringIndex]
+                                + (nextStringIndex > 0
+                                        ? " after " + logcatStrings[nextStringIndex - 1]
+                                        : "")
+                                + " within "
+                                + maxTimeoutInSeconds
+                                + " seconds ");
+            }
+        } catch (InterruptedException e) {
+            fail("Thread interrupted unexpectedly: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Wait for up to {@param timeInSeconds}, if all the strings {@param logcatStrings} are found in
+     * order then the assertion fails, otherwise it succeeds.
+     *
+     * @throws AssertionError if all the strings are found in order in the given time.
+     * @throws IOException if error while reading.
+     */
+    public void assertLogcatDoesNotContainInOrder(int timeInSeconds, String... logcatStrings)
+            throws IOException {
+        try {
+            int stringsFound = numberOfLogcatStringsFound("", timeInSeconds, logcatStrings);
+            if (stringsFound == logcatStrings.length) {
+                fail("Found " + Joiner.on(", ").join(logcatStrings) + " that weren't expected");
+            }
+        } catch (InterruptedException e) {
+            fail("Thread interrupted unexpectedly: " + e.getMessage());
+        }
+    }
+
+    private int numberOfLogcatStringsFound(
+            String filterSpec, int timeInSeconds, String... logcatStrings)
+            throws InterruptedException, IOException {
+        long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(timeInSeconds);
+        int stringIndex = 0;
+        while (timeout >= System.currentTimeMillis()) {
+            InputStream logcatStream = executeShellCommand("logcat -v brief -d " + filterSpec);
+            BufferedReader logcat = new BufferedReader(new InputStreamReader(logcatStream));
+            String line;
+            stringIndex = 0;
+            while ((line = logcat.readLine()) != null) {
+                if (line.contains(logcatStrings[stringIndex])) {
+                    stringIndex++;
+                    if (stringIndex >= logcatStrings.length) {
+                        drainAndClose(logcat);
+                        return stringIndex;
+                    }
+                }
+            }
+            Closeables.closeQuietly(logcat);
+            // In case the key has not been found, wait for the log to update before
+            // performing the next search.
+            Thread.sleep(SMALL_LOGCAT_DELAY);
+        }
+        return stringIndex;
+    }
+
+    private static void drainAndClose(BufferedReader reader) {
+        try {
+            while (reader.read() >= 0) {
+                // do nothing.
+            }
+        } catch (IOException ignored) {
+        }
+        Closeables.closeQuietly(reader);
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 4308947..d85c678 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -25,7 +25,6 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
@@ -44,6 +43,7 @@
 import java.util.Locale;
 import java.util.Map.Entry;
 import java.util.Set;
+
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
@@ -115,17 +115,20 @@
     private static final String SUMMARY_TAG = "Summary";
     private static final String TEST_TAG = "Test";
 
+    private static final String LATEST_RESULT_DIR = "latest";
 
     /**
      * Returns IInvocationResults that can be queried for general reporting information, but that
      * do not store underlying module data. Useful for summarizing invocation history.
      * @param resultsDir
-     * @param useChecksum
      */
     public static List<IInvocationResult> getLightResults(File resultsDir) {
         List<IInvocationResult> results = new ArrayList<>();
         List<File> files = getResultDirectories(resultsDir);
         for (File resultDir : files) {
+            if (LATEST_RESULT_DIR.equals(resultDir.getName())) {
+                continue;
+            }
             IInvocationResult result = getResultFromDir(resultDir, false);
             if (result != null) {
                 results.add(new LightInvocationResult(result));
@@ -153,8 +156,9 @@
      * @return an IInvocationResult for this result, or null upon error
      */
     public static IInvocationResult getResultFromDir(File resultDir, Boolean useChecksum) {
+        File resultFile = null;
         try {
-            File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
+            resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
             if (!resultFile.exists()) {
                 return null;
             }
@@ -274,6 +278,9 @@
             parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
             return invocation;
         } catch (XmlPullParserException | IOException e) {
+            System.out.println(
+                    String.format("Exception when trying to load %s",
+                            resultFile.getAbsolutePath()));
             e.printStackTrace();
             return null;
         }
@@ -491,8 +498,7 @@
     /**
      * Find the IInvocationResult for the given sessionId.
      */
-    public static IInvocationResult findResult(File resultsDir, Integer sessionId)
-            throws FileNotFoundException {
+    public static IInvocationResult findResult(File resultsDir, Integer sessionId) {
         return findResult(resultsDir, sessionId, true);
     }
 
@@ -500,7 +506,7 @@
      * Find the IInvocationResult for the given sessionId.
      */
     private static IInvocationResult findResult(
-            File resultsDir, Integer sessionId, Boolean useChecksum) throws FileNotFoundException {
+            File resultsDir, Integer sessionId, Boolean useChecksum) {
         if (sessionId < 0) {
             throw new IllegalArgumentException(
                 String.format("Invalid session id [%d] ", sessionId));
@@ -532,7 +538,7 @@
     /**
      * Get a list of child directories that contain test invocation results
      * @param resultsDir the root test result directory
-     * @return
+     * @return the list of {@link File} results directory.
      */
     public static List<File> getResultDirectories(File resultsDir) {
         List<File> directoryList = new ArrayList<>();
diff --git a/error_prone_rules.mk b/error_prone_rules.mk
index 25d9344..33382dd 100644
--- a/error_prone_rules.mk
+++ b/error_prone_rules.mk
@@ -21,6 +21,7 @@
                           -Xep:GetClassOnClass:ERROR \
                           -Xep:IdentityBinaryExpression:ERROR \
                           -Xep:JUnit3TestNotRun:ERROR \
+                          -Xep:JUnit4ClassUsedInJUnit3:ERROR \
                           -Xep:JUnitAmbiguousTestClass:ERROR \
                           -Xep:MissingFail:ERROR \
                           -Xep:MissingOverride:ERROR \
diff --git a/error_prone_rules_tests.mk b/error_prone_rules_tests.mk
index d17828d..a8d88b0 100644
--- a/error_prone_rules_tests.mk
+++ b/error_prone_rules_tests.mk
@@ -17,9 +17,14 @@
 # Goal is to eventually merge with error_prone_rules.mk
 LOCAL_ERROR_PRONE_FLAGS:= -Xep:ArrayToString:ERROR \
                           -Xep:CollectionIncompatibleType:ERROR \
+                          -Xep:EqualsIncompatibleType:ERROR \
                           -Xep:EqualsNaN:ERROR \
                           -Xep:FormatString:ERROR \
+                          -Xep:IdentityBinaryExpression:ERROR \
                           -Xep:JUnit3TestNotRun:ERROR \
+                          -Xep:JUnit4ClassUsedInJUnit3:ERROR \
+                          -Xep:JUnitAmbiguousTestClass:ERROR \
+                          -Xep:MissingFail:ERROR \
                           -Xep:SizeGreaterThanOrEqualsZero:ERROR \
                           -Xep:TryFailThrowable:ERROR
 
diff --git a/hostsidetests/aadb/AndroidTest.xml b/hostsidetests/aadb/AndroidTest.xml
index 2224df2..a98ee5a 100644
--- a/hostsidetests/aadb/AndroidTest.xml
+++ b/hostsidetests/aadb/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS aadb host tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest">
         <option name="jar" value="CtsAadbHostTestCases.jar" />
diff --git a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
index a570232..6719923 100644
--- a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
+++ b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
@@ -57,16 +57,14 @@
      * Simple testcase to ensure that the grabbing a bugreport from a real TestDevice works.
      */
     public void testBugreport() throws Exception {
-        InputStreamSource bugreport = mTestDevice.getBugreport();
         InputStream bugreportData = null;
-        try {
+        try (InputStreamSource bugreport = mTestDevice.getBugreport()) {
             bugreportData = bugreport.createInputStream();
             String data = StreamUtil.getStringFromStream(bugreportData);
             assertTrue(String.format("Expected at least %d characters; only saw %d",
                     mMinBugreportBytes, data.length()), data.length() >= mMinBugreportBytes);
             // TODO: check the captured report more extensively, perhaps using loganalysis
         } finally {
-            StreamUtil.cancel(bugreport);
             StreamUtil.close(bugreportData);
         }
     }
@@ -366,10 +364,9 @@
         while (!passed) {
             // sleep a small amount of time to ensure last log message makes it into capture
             RunUtil.getDefault().sleep(10);
-            InputStreamSource source = getDevice().getLogcat(100 * 1024);
-            assertNotNull(source);
             File tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
-            try {
+            try (InputStreamSource source = getDevice().getLogcat(100 * 1024)) {
+                assertNotNull(source);
                 FileUtil.writeToFile(source.createInputStream(), tmpTxtFile);
                 CLog.i("Created file at %s", tmpTxtFile.getAbsolutePath());
                 // ensure last log message is present in log
@@ -379,7 +376,6 @@
                 }
             } finally {
                 FileUtil.deleteFile(tmpTxtFile);
-                source.cancel();
             }
             retry++;
             if ((retry > 100) && !passed) {
diff --git a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
index 3f2ffa8..75ffc85 100644
--- a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
+++ b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
@@ -33,8 +33,8 @@
  */
 public class TestDeviceStressTest extends DeviceTestCase {
 
-    private static final int TEST_FILE_COUNT= 200;
-    private int mIterations = 25;
+    private static final int TEST_FILE_COUNT= 100;
+    private int mIterations = 20;
     private ITestDevice mTestDevice;
 
     @Override
diff --git a/hostsidetests/abioverride/AndroidTest.xml b/hostsidetests/abioverride/AndroidTest.xml
index 18b0063..99f731c 100644
--- a/hostsidetests/abioverride/AndroidTest.xml
+++ b/hostsidetests/abioverride/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS AbiOverride host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="webview" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/abioverride/app/Android.mk b/hostsidetests/abioverride/app/Android.mk
index a246f72..9dd8d1f 100755
--- a/hostsidetests/abioverride/app/Android.mk
+++ b/hostsidetests/abioverride/app/Android.mk
@@ -27,7 +27,10 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsabioverride
 
diff --git a/hostsidetests/abioverride/app/AndroidManifest.xml b/hostsidetests/abioverride/app/AndroidManifest.xml
index c42d3a8..6135732 100755
--- a/hostsidetests/abioverride/app/AndroidManifest.xml
+++ b/hostsidetests/abioverride/app/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.abioverride.app">
 
     <application android:use32bitAbi="true" android:multiArch="true">
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name=".AbiOverrideActivity" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/api/Android.mk b/hostsidetests/api/Android.mk
new file mode 100644
index 0000000..fff9f44
--- /dev/null
+++ b/hostsidetests/api/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)
+
+LOCAL_MODULE := CtsUnofficialApisUsageTestCases
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+LOCAL_STATIC_JAVA_LIBRARIES := dexlib2 doclava jsilver guavalib antlr-runtime host-jdk-tools-prebuilt \
+    compatibility-host-util
+
+# These are list of api txt files that are considered as approved APIs
+LOCAL_JAVA_RESOURCE_FILES := $(addprefix frameworks/base/,\
+api/current.txt \
+api/system-current.txt \
+test-base/api/android-test-base-current.txt \
+test-runner/api/android-test-runner-current.txt \
+test-mock/api/android-test-mock-current.txt)
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := host-jdk-tools-prebuilt:../../../$(HOST_JDK_TOOLS_JAR)
+include $(BUILD_HOST_PREBUILT)
diff --git a/hostsidetests/api/AndroidTest.xml b/hostsidetests/api/AndroidTest.xml
new file mode 100644
index 0000000..df163b0
--- /dev/null
+++ b/hostsidetests/api/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 unofficial APIs usage test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+      <option name="jar" value="CtsUnofficialApisUsageTestCases.jar" />
+      <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/hostsidetests/api/src/com/android/cts/api/ApprovedApis.java b/hostsidetests/api/src/com/android/cts/api/ApprovedApis.java
new file mode 100644
index 0000000..5cde741
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/ApprovedApis.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.doclava.ClassInfo;
+import com.google.doclava.FieldInfo;
+import com.google.doclava.MethodInfo;
+import com.google.doclava.ParameterInfo;
+import com.google.doclava.TypeInfo;
+import com.google.doclava.apicheck.ApiFile;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+/**
+ * Parses API text files (e.g. current.txt) into classes, methods, and fields.
+ */
+public class ApprovedApis {
+    private final Map<String, DefinedClass> definedApiClasses = new HashMap<>();
+    private final Map<String, DefinedMethod> definedApiMethods = new HashMap<>();
+    private final Map<String, DefinedField> definedApiFields = new HashMap<>();
+
+    public ApprovedApis(Stream<File> apiFiles) {
+        apiFiles.forEach(file -> parse(file));
+
+        // build the maps for methods and fields
+        definedApiMethods.putAll(definedApiClasses.values().stream()
+                .flatMap(c -> c.getMethods().stream()) // all methods from all classes
+                .collect(Collectors.toMap(DefinedMethod::getFullSignature,
+                        Function.identity()))); // map by their full signatures
+
+        definedApiFields.putAll(definedApiClasses.values().stream()
+                .flatMap(c -> c.getFields().stream()) // all fields from all classes
+                .collect(Collectors.toMap(DefinedField::getFullSignature,
+                        Function.identity()))); // map by their full signatures
+
+        if (UnofficialApisUsageTest.DEBUG) {
+            System.err.println("-------------------API------------------");
+
+            definedApiClasses.values().stream().sorted()
+                    .forEach(def -> System.err.println(" Defined class: " + def.getName()));
+
+            definedApiMethods.values().stream().sorted()
+                    .forEach(def -> System.err
+                            .println(" Defined method: " + def.getFullSignature()));
+
+            definedApiFields.values().stream().sorted()
+                    .forEach(def -> System.err
+                            .println(" Defined field: " + def.getFullSignature()));
+        }
+    }
+
+    public Map<String, DefinedClass> getDefinedClasses() {
+        return definedApiClasses;
+    }
+
+    public Map<String, DefinedMethod> getDefinedMethods() {
+        return definedApiMethods;
+    }
+
+    public Map<String, DefinedField> getDefinedFields() {
+        return definedApiFields;
+    }
+
+    private void parse(File apiFile) {
+        try {
+            InputStream is = null;
+            try {
+                is = this.getClass().getResourceAsStream(apiFile.toString());
+                ApiInfo apiInfo = ApiFile.parseApi(apiFile.toString(), is);
+                apiInfo.getPackages().values().stream()
+                        .flatMap(packageInfo -> packageInfo.allClasses().values().stream())
+                        .forEach(classInfo -> {
+                            DefinedClass c = definedApiClasses.get(classInfo.qualifiedName());
+                            if (c != null) {
+                                // if the same class has already been defined by other api file,
+                                // then update it
+                                DefinedClass newClass = definedClassFrom(classInfo);
+                                c.addNewMembers(newClass.getMethods(), newClass.getFields());
+                            } else {
+                                c = definedClassFrom(classInfo);
+                                definedApiClasses.put(c.getName(), c);
+                            }
+                        });
+
+            } finally {
+                if (is != null) {
+                    is.close();
+                }
+            }
+        } catch (ApiParseException | IOException e) {
+            throw new RuntimeException("Failed to parse " + apiFile, e);
+        }
+    }
+
+    private static DefinedClass definedClassFrom(ClassInfo def) {
+        String name = def.qualifiedName();
+        String superClassName = def.superclassName();
+        Collection<String> interfaces = def.interfaces().stream().map(ClassInfo::qualifiedName)
+                .collect(Collectors.toList());
+
+        Collection<DefinedMethod> methods = new ArrayList<>(
+                def.allConstructors().size() + def.selfMethods().size());
+        Collection<DefinedField> fields = new ArrayList<>(
+                def.enumConstants().size() + def.selfFields().size());
+
+        methods = Stream.concat(
+                def.allConstructors().stream().map(ApprovedApis::definedMethodFromConstructor),
+                def.selfMethods().stream().map(ApprovedApis::definedMethodFromMethod))
+                .collect(Collectors.toSet());
+
+        fields = Stream.concat(def.enumConstants().stream(), def.selfFields().stream())
+                .map(ApprovedApis::definedFieldFrom).collect(Collectors.toSet());
+
+        return new DefinedClass(name, superClassName, interfaces, methods, fields);
+    }
+
+    private static DefinedMethod definedMethodFromConstructor(MethodInfo def) {
+        return definedMethodFrom(def, true);
+    }
+
+    private static DefinedMethod definedMethodFromMethod(MethodInfo def) {
+        return definedMethodFrom(def, false);
+    }
+
+    private static DefinedMethod definedMethodFrom(MethodInfo def, boolean isCtor) {
+        StringBuffer sb = new StringBuffer();
+        if (isCtor) {
+            sb.append("<init>");
+        } else {
+            sb.append(def.name());
+        }
+        sb.append('(');
+        StringJoiner joiner = new StringJoiner(",");
+        for (int i = 0; i < def.parameters().size(); i++) {
+            ParameterInfo param = def.parameters().get(i);
+            TypeInfo type = param.type();
+            TypeInfo typeParameterType = def.getTypeParameter(type.qualifiedTypeName());
+            String typeName;
+            if (typeParameterType != null) {
+                List<TypeInfo> bounds = typeParameterType.extendsBounds();
+                if (bounds == null || bounds.size() != 1) {
+                    typeName = "java.lang.Object" + type.dimension();
+                } else {
+                    typeName = bounds.get(0).qualifiedTypeName() + type.dimension();
+                }
+            } else {
+                typeName = type.qualifiedTypeName() + type.dimension();
+            }
+            if (i == def.parameters().size() - 1 && def.isVarArgs()) {
+                typeName += "[]";
+            }
+            joiner.add(typeName);
+        }
+        sb.append(joiner.toString());
+        sb.append(')');
+        if (!isCtor) {
+            TypeInfo type = def.returnType();
+            TypeInfo typeParameterType = def.getTypeParameter(type.qualifiedTypeName());
+            if (typeParameterType != null) {
+                List<TypeInfo> bounds = typeParameterType.extendsBounds();
+                if (bounds == null || bounds.size() != 1) {
+                    sb.append("java.lang.Object" + type.dimension());
+                } else {
+                    sb.append(bounds.get(0).qualifiedTypeName() + type.dimension());
+                }
+            } else {
+                sb.append(type.qualifiedTypeName() + type.dimension());
+            }
+        }
+
+        String signature = sb.toString();
+        String containingClass = def.containingClass().qualifiedName();
+        return new DefinedMethod(signature, containingClass);
+    }
+
+    private static DefinedField definedFieldFrom(FieldInfo def) {
+        StringBuffer sb = new StringBuffer(def.name());
+        sb.append(":");
+
+        TypeInfo type = def.type();
+        TypeInfo typeParameterType = def.containingClass()
+                .getTypeParameter(type.qualifiedTypeName());
+        if (typeParameterType != null) {
+            List<TypeInfo> bounds = typeParameterType.extendsBounds();
+            if (bounds == null || bounds.size() != 1) {
+                sb.append("java.lang.Object" + type.dimension());
+            } else {
+                sb.append(bounds.get(0).qualifiedTypeName() + type.dimension());
+            }
+        } else {
+            sb.append(type.qualifiedTypeName() + type.dimension());
+        }
+
+        String signature = sb.toString();
+        String containingClass = def.containingClass().qualifiedName();
+        return new DefinedField(signature, containingClass);
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/DefinedClass.java b/hostsidetests/api/src/com/android/cts/api/DefinedClass.java
new file mode 100644
index 0000000..e4adc71
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/DefinedClass.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.api;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+/**
+ * A class defined in a DEX or in an API file
+ */
+public final class DefinedClass implements Comparable<DefinedClass> {
+    private final String name;
+    private final String superClassName;
+    private final Collection<String> interfaceNames;
+    private final Collection<DefinedMethod> methods;
+    private final Collection<DefinedField> fields;
+    private final Collection<String> methodSignatures;
+    private final Collection<String> fieldSignatures;
+
+    DefinedClass(String name, String superClassName, Collection<String> interfaceNames,
+            Collection<DefinedMethod> methods, Collection<DefinedField> fields) {
+        this.name = name;
+        this.superClassName = superClassName;
+        this.interfaceNames = interfaceNames;
+        // these are Set instead of List to eliminate duplication from multiple api files
+        this.methods = new HashSet<>();
+        this.fields = new HashSet<>();
+
+        // signatures is okay to be list because they are always re-generated
+        // in addMembersFrom
+        this.methodSignatures = new ArrayList<>();
+        this.fieldSignatures = new ArrayList<>();
+
+        addNewMembers(methods, fields);
+    }
+
+    void addNewMembers(Collection<DefinedMethod> newMethods, Collection<DefinedField> newFields) {
+        methods.addAll(newMethods);
+        fields.addAll(newFields);
+
+        // re-generate the signatures list
+        methodSignatures.clear();
+        methodSignatures.addAll(methods.stream()
+                .map(DefinedMethod::getSignature)
+                .collect(Collectors.toList()));
+
+        fieldSignatures.clear();
+        fieldSignatures.addAll(fields.stream()
+                .map(DefinedField::getSignature)
+                .collect(Collectors.toList()));
+    }
+
+    /**
+     * Returns canonical name of a class
+     */
+    public String getName() {
+        return name;
+    }
+
+    String getSuperClass() {
+        return superClassName;
+    }
+
+    Collection<String> getInterfaces() {
+        return interfaceNames;
+    }
+
+    Collection<DefinedMethod> getMethods() {
+        return methods;
+    }
+
+    Collection<DefinedField> getFields() {
+        return fields;
+    }
+
+    Collection<String> getMethodSignatures() {
+        return methodSignatures;
+    }
+
+    Collection<String> getFieldSignatures() {
+        return fieldSignatures;
+    }
+
+    boolean isEnum() {
+        return getSuperClass().equals("java.lang.Enum");
+    }
+
+    @Override
+    public int compareTo(DefinedClass o) {
+        if (o != null) {
+            return getName().compareTo(o.getName());
+        }
+        return 0;
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/DefinedField.java b/hostsidetests/api/src/com/android/cts/api/DefinedField.java
new file mode 100644
index 0000000..0fe1796
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/DefinedField.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 com.android.cts.api;
+
+/**
+ * A field or enum constant defined in a DEX or in an API file
+ */
+public final class DefinedField implements Comparable<DefinedField> {
+    private final String signature;
+    private final String definingClass;
+
+    DefinedField(String signature, String definingClass) {
+        this.signature = signature;
+        this.definingClass = definingClass;
+    }
+
+    /**
+     * Returns name:type
+     */
+    String getSignature() {
+        return signature;
+    }
+
+    /**
+     * Returns class.name:type
+     */
+    public String getFullSignature() {
+        return getDefiningClass() + "." + getSignature();
+    }
+
+    String getDefiningClass() {
+        return definingClass;
+    }
+
+    @Override
+    public int compareTo(DefinedField o) {
+        if (o != null) {
+            return getFullSignature().compareTo(o.getFullSignature());
+        }
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return getFullSignature().hashCode();
+    }
+
+    /**
+     * Fields are fully identified by their full signature. Two fields with same signature are
+     * considered to be the same.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o != null && o instanceof DefinedField) {
+            return getFullSignature().equals(((DefinedField) o).getFullSignature());
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/DefinedMethod.java b/hostsidetests/api/src/com/android/cts/api/DefinedMethod.java
new file mode 100644
index 0000000..9d78157
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/DefinedMethod.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 com.android.cts.api;
+
+/**
+ * A method or constructor defined in a DEX or in an API file
+ */
+public final class DefinedMethod implements Comparable<DefinedMethod> {
+    private final String signature;
+    private final String definingClass;
+
+    DefinedMethod(String signature, String definingClass) {
+        this.signature = signature;
+        this.definingClass = definingClass;
+    }
+
+    /**
+     * Returns name(arg1,arg2,...)type. For constructor, name is <init> and type is empty.
+     */
+    String getSignature() {
+        return signature;
+    }
+
+    /**
+     * Returns class.name(arg1,arg2,...)type. For constructor, name is <init> and type is empty.
+     */
+    public String getFullSignature() {
+        return getDefiningClass() + "." + getSignature();
+    }
+
+    String getDefiningClass() {
+        return definingClass;
+    }
+
+    @Override
+    public int compareTo(DefinedMethod o) {
+        if (o != null) {
+            return getFullSignature().compareTo(o.getFullSignature());
+        }
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return getFullSignature().hashCode();
+    }
+
+    /**
+     * Methods are fully identified by their full signature. Two methods with same signature are
+     * considered to be the same.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o != null && o instanceof DefinedMethod) {
+            return getFullSignature().equals(((DefinedMethod) o).getFullSignature());
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/DexAnalyzer.java b/hostsidetests/api/src/com/android/cts/api/DexAnalyzer.java
new file mode 100644
index 0000000..37d1759
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/DexAnalyzer.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.ReferenceType;
+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;
+
+public class DexAnalyzer {
+    private static Map<Character, String> primitiveTypes = new HashMap<>();
+    static {
+        primitiveTypes.put('Z', "boolean");
+        primitiveTypes.put('B', "byte");
+        primitiveTypes.put('C', "char");
+        primitiveTypes.put('S', "short");
+        primitiveTypes.put('I', "int");
+        primitiveTypes.put('J', "long");
+        primitiveTypes.put('F', "float");
+        primitiveTypes.put('D', "double");
+        primitiveTypes.put('V', "void");
+    }
+
+    // [[Lcom/foo/bar/MyClass$Inner; becomes
+    // com.foo.bar.MyClass.Inner[][]
+    // and [[I becomes int[][]
+    private static String toCanonicalName(String name) {
+        int arrayDepth = 0;
+        for (int i = 0; i < name.length(); i++) {
+            if (name.charAt(i) == '[') {
+                arrayDepth++;
+            } else {
+                break;
+            }
+        }
+
+        // test the first character.
+        final char firstChar = name.charAt(arrayDepth);
+        if (primitiveTypes.containsKey(firstChar)) {
+            name = primitiveTypes.get(firstChar);
+        } else if (firstChar == 'L') {
+            // omit the leading 'L' and the trailing ';'
+            name = name.substring(arrayDepth + 1, name.length() - 1);
+
+            // replace '/' and '$' to '.'
+            name = name.replace('/', '.').replace('$', '.');
+        } else {
+            throw new RuntimeException("Invalid type name " + name);
+        }
+
+        // add []'s, if any
+        if (arrayDepth > 0) {
+            for (int i = 0; i < arrayDepth; i++) {
+                name += "[]";
+            }
+        }
+        return name;
+    }
+
+    public static abstract class Ref {
+        private final List<String> fromList; // list of files that this reference was found
+
+        protected Ref(String from) {
+            fromList = new ArrayList<>();
+            fromList.add(from);
+        }
+
+        void merge(Ref other) {
+            this.fromList.addAll(other.fromList);
+        }
+
+        String printReferencedFrom() {
+            StringJoiner joiner = new StringJoiner(", ");
+            fromList.stream().forEach(name -> joiner.add(name));
+            return joiner.toString();
+        }
+    }
+
+    public static class TypeRef extends Ref implements Comparable<TypeRef> {
+        private final DexBackedTypeReference ref;
+        private final String name;
+
+        TypeRef(DexBackedTypeReference ref, String from) {
+            super(from);
+            this.ref = ref;
+            name = toCanonicalName(ref.getType());
+        }
+
+        String getName() {
+            return name;
+        }
+
+        boolean isArray() {
+            return ref.getType().charAt(0) == '[';
+        }
+
+        boolean isPrimitiveType() {
+            String name = ref.getType();
+            if (name.length() == 1) {
+                return primitiveTypes.containsKey(name.charAt(0));
+            } else if (name.charAt(0) == '[') {
+                return primitiveTypes.containsKey(name.replaceAll("\\[+", "").charAt(0));
+            }
+            return false;
+        }
+
+        @Override
+        public int compareTo(TypeRef o) {
+            if (o != null) {
+                return getName().compareTo(o.getName());
+            }
+            return 0;
+        }
+    }
+
+    // common parent class for MethodRef and FieldRef
+    public static abstract class MemberRef extends Ref implements Comparable<MemberRef> {
+        private final String signature;
+        private String definingClass;
+
+        protected MemberRef(String signature, String initialDefiningClass, String from) {
+            super(from);
+            this.signature = signature;
+            // This might be incorrect since DexBacked[Method|Field]Reference.getDefiningClass()
+            // only returns the type name of the target object. For example,
+            //
+            // class Super { void foo() {...} }
+            // class Child extends Super { }
+            // Child obj = new Child();
+            // obj.foo();
+            //
+            // then method reference for `obj.foo()` is `Child.foo()` since obj is declared as type
+            // Child. The true defining class is somewhere in the type hierarchy, which is Super
+            // in this case.
+            definingClass = initialDefiningClass;
+        }
+
+        String getSignature() {
+            return signature;
+        }
+
+        String getDefiningClass() {
+            return definingClass;
+        }
+
+        void setDefiningClass(String name) {
+            definingClass = name;
+        }
+
+        String getFullSignature() {
+            return getDefiningClass() + "." + getSignature();
+        }
+
+        @Override
+        public int compareTo(MemberRef o) {
+            if (o != null) {
+                return getFullSignature().compareTo(o.getFullSignature());
+            }
+            return 0;
+        }
+    }
+
+    public static class MethodRef extends MemberRef {
+        private final boolean isConstructor;
+
+        MethodRef(DexBackedMethodReference ref, String from) {
+            super(makeSignature(ref), toCanonicalName(ref.getDefiningClass()), from);
+            isConstructor = ref.getName().equals("<init>");
+        }
+
+        private static String makeSignature(DexBackedMethodReference ref) {
+            StringBuffer sb = new StringBuffer();
+            sb.append(ref.getName());
+            sb.append('(');
+            StringJoiner joiner = new StringJoiner(",");
+            for (String param : ref.getParameterTypes()) {
+                joiner.add(toCanonicalName(param));
+            }
+            sb.append(joiner.toString());
+            sb.append(')');
+            if (!ref.getName().equals("<init>")) {
+                sb.append(toCanonicalName(ref.getReturnType()));
+            }
+            return sb.toString();
+        }
+
+        boolean isConstructor() {
+            return isConstructor;
+        }
+    }
+
+    public static class FieldRef extends MemberRef {
+        FieldRef(DexBackedFieldReference ref, String from) {
+            super(makeSignature(ref), toCanonicalName(ref.getDefiningClass()), from);
+        }
+
+        private static String makeSignature(DexBackedFieldReference ref) {
+            return ref.getName() + ":" + toCanonicalName(ref.getType());
+        }
+    }
+
+    private final Map<String, DefinedClass> definedClassesInDex = new HashMap<>();
+    private final Map<String, DefinedMethod> definedMethodsInDex = new HashMap<>();
+    private final Map<String, DefinedField> definedFieldsInDex = new HashMap<>();
+
+    private final Map<String, TypeRef> typeReferences = new HashMap<>();
+    private final Map<String, MethodRef> methodReferences = new HashMap<>();
+    private final Map<String, FieldRef> fieldReferences = new HashMap<>();
+
+    private final ApprovedApis approvedApis;
+
+    public DexAnalyzer(Stream<PulledFile> files, ApprovedApis approvedApis) {
+        this.approvedApis = approvedApis;
+
+        files.forEach(file -> parse(file));
+
+        // Maps for methods and fields are constructed AFTER all files are parsed.
+        // This is because different dex files can have the same class with different sets of
+        // members - if they are statically linking to the same class which are built
+        // with source code at different times. In that case, members of the classes are
+        // merged together.
+        definedMethodsInDex.putAll(definedClassesInDex.values().stream()
+                .map(DefinedClass::getMethods)
+                .flatMap(methods -> methods.stream())
+                .collect(Collectors.toMap(DefinedMethod::getFullSignature,
+                        Function.identity())));
+
+        definedFieldsInDex.putAll(definedClassesInDex.values().stream()
+                .map(DefinedClass::getFields)
+                .flatMap(fields -> fields.stream())
+                .collect(
+                        Collectors.toMap(DefinedField::getFullSignature, Function.identity())));
+
+        if (UnofficialApisUsageTest.DEBUG) {
+            definedClassesInDex.values().stream().sorted()
+                    .forEach(def -> System.err.println(" Defined class: " + def.getName()));
+
+            definedMethodsInDex.values().stream().sorted()
+                    .forEach(def -> System.err
+                            .println(" Defined method: " + def.getFullSignature()));
+
+            definedFieldsInDex.values().stream().sorted()
+                    .forEach(def -> System.err
+                            .println(" Defined field: " + def.getFullSignature()));
+
+            typeReferences.values().stream().sorted().forEach(
+                    ref -> System.err.println(" type ref: " + ref.getName()));
+            methodReferences.values().stream().sorted().forEach(
+                    ref -> System.err.println(" method ref: " + ref.getFullSignature()));
+            fieldReferences.values().stream().sorted().forEach(
+                    ref -> System.err.println(" field ref: " + ref.getFullSignature()));
+        }
+
+        updateDefiningClassInReferences();
+    }
+
+    /**
+     * Parse a dex file to extract symbols defined in the file and symbols referenced from the file
+     */
+    private void parse(PulledFile file) {
+        try {
+            if (UnofficialApisUsageTest.DEBUG) {
+                System.err.println("Analyzing file: " + file.pathInDevice);
+            }
+
+            DexBackedDexFile dexFile = DexFileFactory.loadDexFile(file.fileInHost,
+                    Opcodes.getDefault());
+
+            // 1. extract defined symbols and add them to the maps
+            dexFile.getClasses().stream().forEach(classDef -> {
+                // If the same class is found (defined from one of the previous files), then
+                // merge the members of this class to the old class.
+                DefinedClass c = definedClassFrom(classDef);
+                if (definedClassesInDex.containsKey(c.getName())) {
+                    definedClassesInDex.get(c.getName()).addNewMembers(c.getMethods(),
+                            c.getFields());
+                } else {
+                    definedClassesInDex.put(c.getName(), c);
+                }
+            });
+
+            // 2. extract referenced symbols and add then to the sets
+            // Note that these *Ref classes are identified by their names or full signatures.
+            // This is required since a same reference can be created by different dex files.
+
+            // array types and primitive types are filtered-out
+            dexFile.getReferences(ReferenceType.TYPE).stream()
+                    .map(t -> new TypeRef((DexBackedTypeReference) t, file.pathInDevice))
+                    .filter(((Predicate<TypeRef>) TypeRef::isArray).negate())
+                    .filter(((Predicate<TypeRef>) TypeRef::isPrimitiveType).negate())
+                    .forEach(ref -> {
+                        if (typeReferences.containsKey(ref.getName())) {
+                            typeReferences.get(ref.getName()).merge(ref);
+                        } else {
+                            typeReferences.put(ref.getName(), ref);
+                        }
+                    });
+
+            dexFile.getReferences(ReferenceType.METHOD).stream()
+                    .map(m -> new MethodRef((DexBackedMethodReference) m, file.pathInDevice))
+                    .forEach(ref -> {
+                        if (methodReferences.containsKey(ref.getFullSignature())) {
+                            methodReferences.get(ref.getFullSignature()).merge(ref);
+                        } else {
+                            methodReferences.put(ref.getFullSignature(), ref);
+                        }
+                    });
+
+            dexFile.getReferences(ReferenceType.FIELD).stream()
+                    .map(f -> new FieldRef((DexBackedFieldReference) f, file.pathInDevice))
+                    .forEach(ref -> {
+                        if (fieldReferences.containsKey(ref.getFullSignature())) {
+                            fieldReferences.get(ref.getFullSignature()).merge(ref);
+                        } else {
+                            fieldReferences.put(ref.getFullSignature(), ref);
+                        }
+                    });
+
+        } catch (IOException e) {
+            throw new RuntimeException("Cannot parse dex file in " + file, e);
+        }
+    }
+
+    /**
+     * For For each method/field reference, try to find a class defining it. If found, update the
+     * reference in the set since its full signature has changed.
+     */
+    private void updateDefiningClassInReferences() {
+        Stream.concat(methodReferences.values().stream(), fieldReferences.values().stream())
+                .forEach(ref -> {
+                    DefinedClass c = findDefiningClass(ref);
+                    if (c != null && !ref.getDefiningClass().equals(c.getName())) {
+                        ref.setDefiningClass(c.getName());
+                    }
+                });
+    }
+
+    /**
+     * Try to find a true class defining the given member.
+     */
+    private DefinedClass findDefiningClass(MemberRef ref) {
+        return findMemberInner(ref, ref.getDefiningClass());
+    }
+
+    /**
+     * Try to find a class defining a member by from the given class up to the parent classes
+     */
+    private DefinedClass findMemberInner(MemberRef ref, String className) {
+        final Function<DefinedClass, Collection<String>> getMethods = (DefinedClass c) -> c
+                .getMethodSignatures();
+        final Function<DefinedClass, Collection<String>> getFields = (DefinedClass c) -> c
+                .getFieldSignatures();
+
+        Function<DefinedClass, Collection<String>> getMembers = ref instanceof MethodRef
+                ? getMethods
+                : getFields;
+
+        final boolean classFoundInDex = definedClassesInDex.containsKey(className);
+        final boolean classFoundInApi = approvedApis.getDefinedClasses().containsKey(className);
+        if (!classFoundInDex && !classFoundInApi) {
+            // unknown class.
+            return null;
+        }
+
+        if (classFoundInDex) {
+            DefinedClass c = definedClassesInDex.get(className);
+            if (getMembers.apply(c).contains(ref.getSignature())) {
+                // method was found in the class
+                return c;
+            }
+        }
+
+        if (classFoundInApi) {
+            DefinedClass c = approvedApis.getDefinedClasses().get(className);
+            if (getMembers.apply(c).contains(ref.getSignature())) {
+                // method was found in the class
+                return c;
+            }
+        }
+
+        // member was not found in the class. try finding in parent classes.
+        // first gather the name of parent classes both from dex and api
+        Set<String> parentClasses = new HashSet<>();
+        if (classFoundInDex) {
+            DefinedClass c = definedClassesInDex.get(className);
+            parentClasses.add(c.getSuperClass());
+            parentClasses.addAll(c.getInterfaces());
+        }
+        if (classFoundInApi) {
+            DefinedClass c = approvedApis.getDefinedClasses().get(className);
+            parentClasses.add(c.getSuperClass());
+            parentClasses.addAll(c.getInterfaces());
+        }
+        // null can be in parentClasses, because null might have been added as getSuperClass() is
+        // null for java.lang.Object
+        parentClasses.remove(null);
+
+        for (String pc : parentClasses) {
+            DefinedClass foundClass = findMemberInner(ref, pc);
+            if (foundClass != null) {
+                return foundClass;
+            }
+        }
+        return null;
+    }
+
+    private static class SkipIfNeeded implements Predicate<Ref> {
+        @Override
+        public boolean test(Ref ref) {
+            String className = (ref instanceof TypeRef) ?
+                    ((TypeRef)ref).getName() : ((MemberRef)ref).getDefiningClass();
+            if (className.endsWith("[]")) {
+                // Reference to array type is skipped
+                return true;
+            }
+            if (className.startsWith("dalvik.annotation.")
+                    || className.startsWith("javax.annotation.")) {
+                // These annotation classes are not part of API but they are not explicitly used.
+                return true;
+            }
+            if (className.startsWith("java.lang.")) {
+                // core java libraries are exempted.
+                return true;
+            }
+            return false;
+        }
+    }
+
+    // for each type, method and field references, find the ones that are found neither in dex
+    // nor in api
+    public Stream<TypeRef> collectUndefinedTypeReferences() {
+        return typeReferences.values().stream()
+                .filter(ref -> !definedClassesInDex.containsKey(ref.getName())
+                        && !approvedApis.getDefinedClasses().containsKey(ref.getName()))
+                .filter((new SkipIfNeeded()).negate());
+    }
+
+    public Stream<MethodRef> collectUndefinedMethodReferences() {
+        return methodReferences.values().stream()
+                .filter(ref -> !definedMethodsInDex.containsKey(ref.getFullSignature())
+                        && !approvedApis.getDefinedMethods().containsKey(ref.getFullSignature()))
+                .filter((new SkipIfNeeded()).negate());
+    }
+
+    public Stream<FieldRef> collectUndefinedFieldReferences() {
+        return fieldReferences.values().stream()
+                .filter(ref -> !definedFieldsInDex.containsKey(ref.getFullSignature())
+                        && !approvedApis.getDefinedFields().containsKey(ref.getFullSignature()))
+                .filter((new SkipIfNeeded()).negate());
+    }
+
+    private static DefinedClass definedClassFrom(DexBackedClassDef def) {
+        String name = toCanonicalName(def.getType());
+        String superClassName = toCanonicalName(def.getSuperclass());
+        Collection<String> interfaceNames = def.getInterfaces().stream()
+                .map(n -> toCanonicalName(n))
+                .collect(Collectors.toList());
+
+        Collection<DefinedMethod> methods = StreamSupport
+                .stream(def.getMethods().spliterator(), false /* parallel */)
+                .map(DexAnalyzer::definedMethodFrom).collect(Collectors.toList());
+
+        Collection<DefinedField> fields = StreamSupport
+                .stream(def.getFields().spliterator(), false /* parallel */)
+                .map(DexAnalyzer::definedFieldFrom).collect(Collectors.toList());
+
+        return new DefinedClass(name, superClassName, interfaceNames, methods, fields);
+    }
+
+    private static DefinedMethod definedMethodFrom(DexBackedMethod def) {
+        StringBuffer sb = new StringBuffer();
+        sb.append(def.getName());
+        sb.append('(');
+        StringJoiner joiner = new StringJoiner(",");
+        for (String param : def.getParameterTypes()) {
+            joiner.add(toCanonicalName(param));
+        }
+        sb.append(joiner.toString());
+        sb.append(')');
+        final boolean isConstructor = def.getName().equals("<init>");
+        if (!isConstructor) {
+            sb.append(toCanonicalName(def.getReturnType()));
+        }
+
+        String signature = sb.toString();
+        String definingClass = toCanonicalName(def.getDefiningClass());
+        return new DefinedMethod(signature, definingClass);
+    }
+
+    private static DefinedField definedFieldFrom(DexBackedField def) {
+        String signature = def.getName() + ":" + toCanonicalName(def.getType());
+        String definingClass = toCanonicalName(def.getDefiningClass());
+        return new DefinedField(signature, definingClass);
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/FilePuller.java b/hostsidetests/api/src/com/android/cts/api/FilePuller.java
new file mode 100644
index 0000000..5e3b7cb
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/FilePuller.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.api;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Pulls files from a non-rooted device
+ */
+class FilePuller {
+    private final ITestDevice device;
+    private final Path hostDir;
+
+    FilePuller(ITestDevice device) {
+        this.device = device;
+        try {
+            hostDir = Files.createTempDirectory("pulled_files");
+            hostDir.toFile().deleteOnExit();
+        } catch (IOException e) {
+            throw new RuntimeException("Cannot create directory for pulled files", e);
+        }
+    }
+
+    void clean() {
+        hostDir.toFile().delete();
+    }
+
+    PulledFile pullFromDevice(String path, String name) {
+        try {
+            File outputFile = new File(hostDir.toFile(), name);
+            FileOutputStream outputStream = new FileOutputStream(outputFile);
+
+            // For files on vendor partition, `adb shell pull` does not work on non-rooted device,
+            // due to the permission. Thus using `cat` to copy file content to outside of the
+            // device, which might a little bit slower than adb pull, but should be acceptable for
+            // testing.
+            device.executeShellCommand(String.format("cat %s", path),
+                    new IShellOutputReceiver() {
+
+                        @Override
+                        public void addOutput(byte[] data, int offset, int len) {
+                            try {
+                                outputStream.write(data, offset, len);
+                            } catch (IOException e) {
+                                throw new RuntimeException("Error pulling file " + path, e);
+                            }
+                        }
+
+                        @Override
+                        public void flush() {
+                            try {
+                                outputStream.close();
+                            } catch (IOException e) {
+                                throw new RuntimeException("Error saving file " + path,
+                                        e);
+                            }
+                        }
+
+                        @Override
+                        public boolean isCancelled() {
+                            // don't cancel at any time.
+                            return false;
+                        }
+                    });
+            return new PulledFile(outputFile, path);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException("Cannot connect to the device", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to pull file " + path, e);
+        }
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/PulledFile.java b/hostsidetests/api/src/com/android/cts/api/PulledFile.java
new file mode 100644
index 0000000..4742d52
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/PulledFile.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 com.android.cts.api;
+
+import java.io.File;
+
+class PulledFile {
+    final File fileInHost;
+    final String pathInDevice;
+
+    PulledFile(File fileInHost, String pathInDevice) {
+        this.fileInHost = fileInHost;
+        this.pathInDevice = pathInDevice;
+    }
+}
diff --git a/hostsidetests/api/src/com/android/cts/api/UnofficialApisUsageTest.java b/hostsidetests/api/src/com/android/cts/api/UnofficialApisUsageTest.java
new file mode 100644
index 0000000..0cc817f
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/UnofficialApisUsageTest.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 com.android.cts.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.compatibility.common.util.PropertyUtil;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+/**
+ * Ensures that java modules in vendor partition on the device are not using any non-approved APIs
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UnofficialApisUsageTest extends DeviceTestCase {
+    public final static boolean DEBUG = true;
+    private ITestDevice device;
+    private FilePuller filePuller;
+    private Stream<PulledFile> pulledFiles;
+    private Stream<File> apiFiles;
+    private ApprovedApis approvedApis;
+    private DexAnalyzer extractedApis;
+
+    @Override
+    protected void setUp() throws Exception {
+        device = getDevice();
+        filePuller = new FilePuller(device);
+
+        try {
+            // pulls packages and libraries which are in vendor partition and have code.
+            pulledFiles = Stream.concat(getPackages(), getLibraries())
+                    .filter(module -> getRealPath(module.path).startsWith("/vendor"))
+                    .map(module -> filePuller.pullFromDevice(module.path, module.name))
+                    .filter(file -> hasCode(file.fileInHost));
+
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException("Cannot connect to device", e);
+        }
+
+        apiFiles = Arrays.stream(new String[] {
+            "/current.txt",
+            "/system-current.txt",
+            "/android-test-base-current.txt",
+            "/android-test-runner-current.txt",
+            "/android-test-mock-current.txt"
+        }).map(name -> new File(name));
+
+        approvedApis = new ApprovedApis(apiFiles);
+        extractedApis = new DexAnalyzer(pulledFiles, approvedApis);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        filePuller.clean();
+    }
+
+    private static class JavaModule {
+        enum Type {
+            JAR, APK,
+        }
+
+        public final String name;
+        public final String path;
+        public final Type type;
+
+        private JavaModule(String name, String path, Type type) {
+            this.name = name;
+            this.path = path;
+            this.type = type;
+        }
+
+        public static JavaModule newPackageFromLine(String line) {
+            // package:/path/to/apk=com.foo.bar
+            line = line.split(":")[1]; // filter-out "package:" prefix
+            int separatorPos = line.lastIndexOf('=');
+            String path = line.substring(0, separatorPos);
+            String name = line.substring(separatorPos + 1);
+            return new JavaModule(name, path, Type.APK);
+        }
+
+        public static JavaModule newLibraryFromLine(String line) {
+            // com.foo.bar -> (jar) /path/to/jar
+            String[] tokens = line.trim().split(" ");
+            String name = tokens[0];
+            String type = tokens[3];
+            String path = tokens[4];
+            return new JavaModule(name, path, type.equals("(jar)") ? Type.JAR : Type.APK);
+        }
+    }
+
+    private Stream<JavaModule> getPackages() throws DeviceNotAvailableException {
+        return Arrays.stream(device.executeShellCommand("cmd package list packages -f").split("\n"))
+                .map(line -> JavaModule.newPackageFromLine(line));
+    }
+
+    private Stream<JavaModule> getLibraries() throws DeviceNotAvailableException {
+        // cmd package list libraries only shows the name of the libraries, but not their paths.
+        // Until it shows the paths as well, let's use the old dumpsys package.
+        return Arrays.stream(device.executeShellCommand("dumpsys package libraries").split("\n"))
+                .skip(1) // for "Libraries:" header
+                .map(line -> JavaModule.newLibraryFromLine(line))
+                .filter(module -> module.type == JavaModule.Type.JAR); // only jars
+    }
+
+    private String getRealPath(String path) {
+        try {
+            return device.executeShellCommand(String.format("realpath %s", path));
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException("Cannot connect to device", e);
+        }
+    }
+
+    /**
+     * Tests whether the downloaded file has code or not, by examining the existence of classes.dex
+     * in it
+     */
+    private boolean hasCode(File file) {
+        try {
+            ZipFile zipFile = null;
+            try {
+                zipFile = new ZipFile(file);
+                Enumeration<? extends ZipEntry> entries = zipFile.entries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry e = entries.nextElement();
+                    if (e.getName().equals("classes.dex")) {
+                        return true;
+                    }
+                }
+            } finally {
+                if (zipFile != null) {
+                    zipFile.close();
+                }
+            }
+            return false;
+        } catch (IOException e) {
+            throw new RuntimeException("Error while examining whether code is in " + file, e);
+        }
+    }
+
+    /**
+     * The main test. If there is any type/method/field reference to unknown type/method/field, then
+     * it indicates that vendors are using non-approved APIs.
+     */
+    @Test
+    public void testNonApiReferences() throws Exception {
+        if (PropertyUtil.propertyEquals(device, "ro.treble.enabled", "true") &&
+               PropertyUtil.getFirstApiLevel(device) > 27 /* O_MR1 */) {
+            StringBuffer sb = new StringBuffer(10000);
+            extractedApis.collectUndefinedTypeReferences().sorted().forEach(
+                    ref -> sb.append("Undefined type ref: " + ref.getName() + " from: "
+                            + ref.printReferencedFrom() + "\n"));
+            extractedApis.collectUndefinedMethodReferences().sorted().forEach(
+                    ref -> sb.append("Undefined method ref: " + ref.getFullSignature() + " from: "
+                            + ref.printReferencedFrom() + "\n"));
+            extractedApis.collectUndefinedFieldReferences().sorted().forEach(
+                    ref -> sb.append("Undefined field ref: " + ref.getFullSignature() + " from: "
+                            + ref.printReferencedFrom() + "\n"));
+            if (sb.length() != 0) {
+                fail(sb.toString());
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/AndroidTest.xml b/hostsidetests/appsecurity/AndroidTest.xml
index 6a9b557..2109a47 100644
--- a/hostsidetests/appsecurity/AndroidTest.xml
+++ b/hostsidetests/appsecurity/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="android.appsecurity.cts.AppSecurityPreparer" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk
new file mode 100644
index 0000000..07c356e
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk
new file mode 100644
index 0000000..8eeac8c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permdef.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permdef.apk
new file mode 100644
index 0000000..9bb3d1f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-permdef.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk
new file mode 100644
index 0000000..f56c03b
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1.apk
new file mode 100644
index 0000000..a320b5d
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-1.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permcli-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permcli-companion.apk
new file mode 100644
index 0000000..93b4a40
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permcli-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permdef.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permdef.apk
new file mode 100644
index 0000000..bd785d7
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-permdef.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-sharedUid.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-sharedUid.apk
new file mode 100644
index 0000000..0d0cba3
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-sharedUid.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permcli-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permcli-companion.apk
new file mode 100644
index 0000000..916f1e4
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permcli-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permdef.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permdef.apk
new file mode 100644
index 0000000..7db3423
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permdef.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk
new file mode 100644
index 0000000..fe5eee4
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk
new file mode 100644
index 0000000..3cdc0fe
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permcli-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permcli-companion.apk
new file mode 100644
index 0000000..043e97b
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permcli-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk
new file mode 100644
index 0000000..ce9901d
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk
new file mode 100644
index 0000000..aee503c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk
new file mode 100644
index 0000000..8b08a79
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk
new file mode 100644
index 0000000..773ae5e
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2-full-caps.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2-full-caps.apk
new file mode 100644
index 0000000..4ec9450
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2-full-caps.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2_3-full-caps.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2_3-full-caps.apk
new file mode 100644
index 0000000..0fdda20
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_1_2_3-full-caps.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_2_3-full-caps.apk b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_2_3-full-caps.apk
new file mode 100644
index 0000000..c399c60
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v3-rsa-pkcs1-sha256-2048-3-with-por_2_3-full-caps.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index afd7245..aa7e4a0 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -23,13 +23,17 @@
 import static android.appsecurity.cts.SplitTests.CLASS;
 import static android.appsecurity.cts.SplitTests.PKG;
 
-import com.android.tradefed.build.IBuildInfo;
+import static org.junit.Assert.fail;
+
 import com.android.tradefed.device.CollectingOutputReceiver;
-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 com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
@@ -37,45 +41,57 @@
 /**
  * Set of tests that verify behavior of adopted storage media, if supported.
  */
-public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
-    private IAbi mAbi;
-    private IBuildInfo mCtsBuild;
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AdoptableHostTest extends BaseHostJUnit4Test {
 
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
+    public static final String FEATURE_ADOPTABLE_STORAGE = "feature:android.software.adoptable_storage";
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mCtsBuild);
+    @Before
+    public void setUp() throws Exception {
+        // Start all possible users to make sure their storage is unlocked
+        Utils.prepareMultipleUsers(getDevice(), Integer.MAX_VALUE);
 
         getDevice().uninstallPackage(PKG);
+
+        // Enable a virtual disk to give us the best shot at being able to pass
+        // the various tests below. This helps verify devices that may not
+        // currently have an SD card inserted.
+        if (isSupportedDevice()) {
+            getDevice().executeShellCommand("sm set-virtual-disk true");
+        }
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
+
+        if (isSupportedDevice()) {
+            getDevice().executeShellCommand("sm set-virtual-disk false");
+        }
     }
 
+    /**
+     * Ensure that we have consistency between the feature flag and what we
+     * sniffed from the underlying fstab.
+     */
+    @Test
+    public void testFeatureConsistent() throws Exception {
+        final boolean hasFeature = hasFeature();
+        final boolean hasFstab = hasFstab();
+        if (hasFeature != hasFstab) {
+            fail("Inconsistent adoptable storage status; feature claims " + hasFeature
+                    + " but fstab claims " + hasFstab);
+        }
+    }
+
+    @Test
     public void testApps() throws Exception {
-        if (!hasAdoptable()) return;
+        if (!isSupportedDevice()) return;
         final String diskId = getAdoptionDisk();
         try {
-            final String abi = mAbi.getName();
+            final String abi = getAbi().getName();
             final String apk = ABI_TO_APK.get(abi);
-            assertNotNull("Failed to find APK for ABI " + abi, apk);
+            Assert.assertNotNull("Failed to find APK for ABI " + abi, apk);
 
             // Install simple app on internal
             new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
@@ -119,8 +135,9 @@
         }
     }
 
+    @Test
     public void testPrimaryStorage() throws Exception {
-        if (!hasAdoptable()) return;
+        if (!isSupportedDevice()) return;
         final String diskId = getAdoptionDisk();
         try {
             final String originalVol = getDevice()
@@ -218,8 +235,9 @@
      * Verify that we can install both new and inherited packages directly on
      * adopted volumes.
      */
+    @Test
     public void testPackageInstaller() throws Exception {
-        if (!hasAdoptable()) return;
+        if (!isSupportedDevice()) return;
         final String diskId = getAdoptionDisk();
         try {
             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -246,8 +264,9 @@
      * Verify behavior when changes occur while adopted device is ejected and
      * returned at a later time.
      */
+    @Test
     public void testEjected() throws Exception {
-        if (!hasAdoptable()) return;
+        if (!isSupportedDevice()) return;
         final String diskId = getAdoptionDisk();
         try {
             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -291,7 +310,15 @@
         }
     }
 
-    private boolean hasAdoptable() throws Exception {
+    private boolean isSupportedDevice() throws Exception {
+        return hasFeature() || hasFstab();
+    }
+
+    private boolean hasFeature() throws Exception {
+        return getDevice().hasFeature(FEATURE_ADOPTABLE_STORAGE);
+    }
+
+    private boolean hasFstab() throws Exception {
         return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
     }
 
@@ -335,11 +362,6 @@
         getDevice().executeShellCommand("sm forget all");
     }
 
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
-
     private static void assertSuccess(String str) {
         if (str == null || !str.startsWith("Success")) {
             throw new AssertionError("Expected success string but found " + str);
@@ -367,7 +389,7 @@
 
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
         public InstallMultiple() {
-            super(getDevice(), mCtsBuild, mAbi);
+            super(getDevice(), getBuild(), getAbi());
         }
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
index 90b89af..797ffcb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
@@ -29,7 +29,6 @@
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.util.StreamUtil;
 
 /**
  * Creates secondary and tertiary users for use during a test suite.
@@ -56,11 +55,8 @@
                         "Created secondary user " + device.createUser("CTS_" + System.nanoTime()));
             }
         } catch (IllegalStateException e) {
-            InputStreamSource logcat = device.getLogcatDump();
-            try {
+            try (InputStreamSource logcat = device.getLogcatDump()) {
                 mLogger.testLog("AppSecurityPrep_failed_create_user", LogDataType.LOGCAT, logcat);
-            } finally {
-                StreamUtil.cancel(logcat);
             }
             throw new TargetSetupError("Failed to create user.", e, device.getDeviceDescriptor());
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 00bac12..02e62d3 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -16,32 +16,31 @@
 
 package android.appsecurity.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 com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
-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 com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.AbiUtils;
-import com.android.tradefed.util.RunUtil;
 
-import java.io.BufferedReader;
-import java.io.EOFException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Set of tests that verify various security checks involving multiple apps are
  * properly enforced.
  */
-public class AppSecurityTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AppSecurityTests extends BaseHostJUnit4Test {
 
     // testSharedUidDifferentCerts constants
     private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
@@ -86,39 +85,22 @@
 
     private static final String LOG_TAG = "AppSecurityTests";
 
-    private IAbi mAbi;
-    private IBuildInfo mCtsBuild;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
     private File getTestAppFile(String fileName) throws FileNotFoundException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         return buildHelper.getTestFile(fileName);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
-        assertNotNull(mCtsBuild);
+        assertNotNull(getBuild());
     }
 
     /**
      * Test that an app that declares the same shared uid as an existing app, cannot be installed
      * if it is signed with a different certificate.
      */
+    @Test
     public void testSharedUidDifferentCerts() throws Exception {
         Log.i(LOG_TAG, "installing apks with shared uid, but different certs");
         try {
@@ -126,7 +108,7 @@
             getDevice().uninstallPackage(SHARED_UI_PKG);
             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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),
@@ -147,13 +129,14 @@
      * Test that an app update cannot be installed over an existing app if it has a different
      * certificate.
      */
+    @Test
     public void testAppUpgradeDifferentCerts() 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);
 
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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),
@@ -172,6 +155,7 @@
     /**
      * Test that an app cannot access another app's private data.
      */
+    @Test
     public void testAppFailAccessPrivateData() throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
         try {
@@ -179,7 +163,7 @@
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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),
@@ -202,13 +186,14 @@
     /**
      * Test that uninstall of an app removes its private data.
      */
+    @Test
     public void testUninstallRemovesData() 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(mAbi.getName())};
+            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),
@@ -233,6 +218,7 @@
     /**
      * Test that an app cannot instrument another app that is signed with different certificate.
      */
+    @Test
     public void testInstrumentationDiffCert() throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
         try {
@@ -240,7 +226,7 @@
             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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",
@@ -266,6 +252,7 @@
      * Test that an app cannot use a signature-enforced permission if it is signed with a different
      * certificate than the app that declared the permission.
      */
+    @Test
     public void testPermissionDiffCert() throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
         try {
@@ -274,7 +261,7 @@
             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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",
@@ -302,18 +289,14 @@
     /**
      * 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");
-        assertEquals("Error text", "Error: APK content must be streamed\n", output);
+        assertTrue("Error text", output.contains("Error"));
     }
 
     private void runDeviceTests(String packageName) throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName);
-    }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+        runDeviceTests(packageName, null);
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
index 920e0a5..5e2a97b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
@@ -17,18 +17,18 @@
 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 com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Assert;
+import org.junit.Before;
 
 import java.util.ArrayList;
 
 /**
  * Base class.
  */
-public class BaseAppSecurityTest extends DeviceTestCase implements IBuildReceiver {
-    protected IBuildInfo mBuildInfo;
+abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
 
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
@@ -37,15 +37,9 @@
     /** Users we shouldn't delete in the tests */
     private ArrayList<Integer> mFixedUsers;
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertNotNull(mBuildInfo); // ensure build has been set before test is run.
+    @Before
+    public void setUp() throws Exception {
+        Assert.assertNotNull(getBuild()); // ensure build has been set before test is run.
 
         mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
         mIsSplitSystemUser = checkIfSplitSystemUser();
@@ -70,8 +64,8 @@
         if (userId < 0) {
             userId = mPrimaryUserId;
         }
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackageForUser(
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        Assert.assertNull(getDevice().installPackageForUser(
                 buildHelper.getTestFile(apk), true, false, userId, "-t"));
     }
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
index e291771..f4f6d9e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.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.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-public class ClassloaderSplitsTest 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 ClassloaderSplitsTest extends BaseHostJUnit4Test implements IBuildReceiver {
     private static final String PKG = "com.android.cts.classloadersplitapp";
     private static final String TEST_CLASS = PKG + ".SplitAppTest";
 
@@ -39,54 +45,48 @@
     private static final String APK_FEATURE_A = "CtsClassloaderSplitAppFeatureA.apk";
     private static final String APK_FEATURE_B = "CtsClassloaderSplitAppFeatureB.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);
     }
 
+    @Test
     public void testBaseClassLoader() throws Exception {
         new InstallMultiple().addApk(APK_BASE).run();
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+        runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
     }
 
+    @Test
     public void testFeatureAClassLoader() throws Exception {
         new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).run();
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
+        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();
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureBClassLoader");
+        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();
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
-        Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testAllReceivers");
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
+        runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+        runDeviceTests(getDevice(), PKG, TEST_CLASS, "testAllReceivers");
     }
 
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
         public InstallMultiple() {
-            super(getDevice(), mBuildInfo, null);
+            super(getDevice(), getBuild(), null);
         }
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index 2174fa0..7ba2eb9 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -16,15 +16,20 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.CollectingOutputReceiver;
 import com.android.ddmlib.Log;
-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 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;
 
 /**
  * Set of tests that verify behavior of direct boot, if supported.
@@ -32,58 +37,40 @@
  * Note that these tests drive PIN setup manually instead of relying on device
  * administrators, which are not supported by all devices.
  */
-public class DirectBootHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class DirectBootHostTest extends BaseHostJUnit4Test {
     private static final String TAG = "DirectBootHostTest";
 
     private static final String PKG = "com.android.cts.encryptionapp";
-    private static final String CLASS = ".EncryptionAppTest";
+    private static final String CLASS = PKG + ".EncryptionAppTest";
     private static final String APK = "CtsEncryptionApp.apk";
 
     private static final String OTHER_APK = "CtsSplitApp.apk";
     private static final String OTHER_PKG = "com.android.cts.splitapp";
-    private static final String OTHER_CLASS = ".SplitAppTest";
 
     private static final String MODE_NATIVE = "native";
     private static final String MODE_EMULATED = "emulated";
     private static final String MODE_NONE = "none";
 
-    private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin\n";
-    private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive\n";
+    private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin";
+    private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
 
     private static final long SHUTDOWN_TIME_MS = 30 * 1000;
 
-    private String mFeatureList = null;
-
     private int[] mUsers;
-    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 {
         mUsers = Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mCtsBuild);
+        assertNotNull(getAbi());
+        assertNotNull(getBuild());
 
         getDevice().uninstallPackage(PKG);
         getDevice().uninstallPackage(OTHER_PKG);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
         getDevice().uninstallPackage(OTHER_PKG);
     }
@@ -91,6 +78,7 @@
     /**
      * Automotive devices MUST support native FBE.
      */
+    @Test
     public void testAutomotiveNativeFbe() throws Exception {
         if (!isSupportedDevice()) {
             Log.v(TAG, "Device not supported; skipping test");
@@ -107,6 +95,7 @@
     /**
      * If device has native FBE, verify lifecycle.
      */
+    @Test
     public void testDirectBootNative() throws Exception {
         if (!isSupportedDevice()) {
             Log.v(TAG, "Device not supported; skipping test");
@@ -122,6 +111,7 @@
     /**
      * If device doesn't have native FBE, enable emulation and verify lifecycle.
      */
+    @Test
     public void testDirectBootEmulated() throws Exception {
         if (!isSupportedDevice()) {
             Log.v(TAG, "Device not supported; skipping test");
@@ -137,6 +127,7 @@
     /**
      * If device doesn't have native FBE, verify normal lifecycle.
      */
+    @Test
     public void testDirectBootNone() throws Exception {
         if (!isSupportedDevice()) {
             Log.v(TAG, "Device not supported; skipping test");
@@ -213,7 +204,7 @@
             int... users) throws DeviceNotAvailableException {
         for (int user : users) {
             Log.d(TAG, "runDeviceTests " + testMethodName + " u" + user);
-            Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, user);
+            runDeviceTests(getDevice(), packageName, testClassName, testMethodName, user, null);
         }
     }
 
@@ -236,20 +227,12 @@
         return "1".equals(output);
     }
 
-    private boolean hasSystemFeature(final String feature) throws Exception {
-        if (mFeatureList == null) {
-            mFeatureList = getDevice().executeShellCommand("pm list features");
-        }
-
-        return mFeatureList.contains(feature);
-    }
-
     private boolean isSupportedDevice() throws Exception {
-        return hasSystemFeature(FEATURE_DEVICE_ADMIN);
+        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN);
     }
 
     private boolean isAutomotiveDevice() throws Exception {
-        return hasSystemFeature(FEATURE_AUTOMOTIVE);
+        return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
     }
 
     private void waitForBootCompleted() throws Exception {
@@ -269,7 +252,7 @@
 
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
         public InstallMultiple() {
-            super(getDevice(), mCtsBuild, mAbi);
+            super(getDevice(), getBuild(), getAbi());
         }
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index 914f4ef..7e401e8 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -188,6 +188,19 @@
         runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartEphemeral");
     }
 
+    public void testEphemeralGetInstaller01() throws Exception {
+        installEphemeralApp(EPHEMERAL_1_APK, "com.android.cts.normalapp");
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller01");
+    }
+    public void testEphemeralGetInstaller02() throws Exception {
+        installApp(NORMAL_APK, "com.android.cts.normalapp");
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller03");
+    }
+    public void testEphemeralGetInstaller03() throws Exception {
+        installEphemeralApp(EPHEMERAL_1_APK, "com.android.cts.normalapp");
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller02");
+    }
+
     public void testExposedSystemActivities() throws Exception {
         for (Map<String, String> testArgs : EXPECTED_EXPOSED_INTENTS) {
             final boolean exposed = isIntentExposed(testArgs);
@@ -219,6 +232,7 @@
         runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInstallPermissionGranted");
     }
 
+    /** Test for android.permission.INSTANT_APP_FOREGROUND_SERVICE */
     public void testStartForegrondService() throws Exception {
         // Make sure the test package does not have INSTANT_APP_FOREGROUND_SERVICE
         getDevice().executeShellCommand("cmd package revoke " + EPHEMERAL_1_PKG
@@ -226,6 +240,41 @@
         runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartForegroundService");
     }
 
+    /** Test for android.permission.RECORD_AUDIO */
+    public void testRecordAudioPermission() throws Exception {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testRecordAudioPermission");
+    }
+
+    /** Test for android.permission.CAMERA */
+    public void testCameraPermission() throws Exception {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testCameraPermission");
+    }
+
+    /** Test for android.permission.READ_PHONE_NUMBERS */
+    public void testReadPhoneNumbersPermission() throws Exception {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testReadPhoneNumbersPermission");
+    }
+
+    /** Test for android.permission.ACCESS_COARSE_LOCATION */
+    public void testAccessCoarseLocationPermission() throws Throwable {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testAccessCoarseLocationPermission");
+    }
+
+    /** Test for android.permission.NETWORK */
+    public void testInternetPermission() throws Throwable {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInternetPermission");
+    }
+
+    /** Test for android.permission.VIBRATE */
+    public void testVibratePermission() throws Throwable {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testVibratePermission");
+    }
+
+    /** Test for android.permission.WAKE_LOCK */
+    public void testWakeLockPermission() throws Throwable {
+        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testWakeLockPermission");
+    }
+
     private static final HashMap<String, String> makeArgs(
             String action, String category, String mimeType) {
         if (action == null || action.length() == 0) {
@@ -284,11 +333,23 @@
         assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
     }
 
+    private void installApp(String apk, String installer) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false,
+                "-i", installer));
+    }
+
     private void installEphemeralApp(String apk) throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
         assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false, "--ephemeral"));
     }
 
+    private void installEphemeralApp(String apk, String installer) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false,
+                "--ephemeral", "-i", installer));
+    }
+
     private void installTestPackages() throws Exception {
         installApp(NORMAL_APK);
         installApp(UNEXPOSED_APK);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index b9712a1..0822760 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -16,24 +16,32 @@
 
 package android.appsecurity.cts;
 
+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 com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
-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 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;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Set of tests that verify behavior of external storage devices.
  */
-public class ExternalStorageHostTest extends DeviceTestCase
-        implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ExternalStorageHostTest extends BaseHostJUnit4Test {
     private static final String TAG = "ExternalStorageHostTest";
 
     private static final String COMMON_CLASS =
@@ -41,59 +49,41 @@
 
     private static final String NONE_APK = "CtsExternalStorageApp.apk";
     private static final String NONE_PKG = "com.android.cts.externalstorageapp";
-    private static final String NONE_CLASS = ".ExternalStorageTest";
+    private static final String NONE_CLASS = NONE_PKG + ".ExternalStorageTest";
     private static final String READ_APK = "CtsReadExternalStorageApp.apk";
     private static final String READ_PKG = "com.android.cts.readexternalstorageapp";
-    private static final String READ_CLASS = ".ReadExternalStorageTest";
+    private static final String READ_CLASS = READ_PKG + ".ReadExternalStorageTest";
     private static final String WRITE_APK = "CtsWriteExternalStorageApp.apk";
     private static final String WRITE_PKG = "com.android.cts.writeexternalstorageapp";
-    private static final String WRITE_CLASS = ".WriteExternalStorageTest";
+    private static final String WRITE_CLASS = WRITE_PKG + ".WriteExternalStorageTest";
     private static final String MULTIUSER_APK = "CtsMultiUserStorageApp.apk";
     private static final String MULTIUSER_PKG = "com.android.cts.multiuserstorageapp";
-    private static final String MULTIUSER_CLASS = ".MultiUserStorageTest";
+    private static final String MULTIUSER_CLASS = MULTIUSER_PKG + ".MultiUserStorageTest";
 
     private int[] mUsers;
-    private IAbi mAbi;
-    private IBuildInfo mCtsBuild;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
 
     private File getTestAppFile(String fileName) throws FileNotFoundException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         return buildHelper.getTestFile(fileName);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         mUsers = Utils.prepareMultipleUsers(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mCtsBuild);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+        assertNotNull(getAbi());
+        assertNotNull(getBuild());
     }
 
     /**
      * Verify that app with no external storage permissions works correctly.
      */
+    @Test
     public void testExternalStorageNone() throws Exception {
         try {
             wipePrimaryExternalStorage();
 
             getDevice().uninstallPackage(NONE_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
 
             for (int user : mUsers) {
@@ -110,12 +100,13 @@
      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
      * correctly.
      */
+    @Test
     public void testExternalStorageRead() throws Exception {
         try {
             wipePrimaryExternalStorage();
 
             getDevice().uninstallPackage(READ_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
             assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
 
             for (int user : mUsers) {
@@ -132,12 +123,13 @@
      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
      * correctly.
      */
+    @Test
     public void testExternalStorageWrite() throws Exception {
         try {
             wipePrimaryExternalStorage();
 
             getDevice().uninstallPackage(WRITE_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
             assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
 
             for (int user : mUsers) {
@@ -153,6 +145,7 @@
      * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
      * directories belonging to other apps, and those apps can read.
      */
+    @Test
     public void testExternalStorageGifts() throws Exception {
         try {
             wipePrimaryExternalStorage();
@@ -160,20 +153,20 @@
             getDevice().uninstallPackage(NONE_PKG);
             getDevice().uninstallPackage(READ_PKG);
             getDevice().uninstallPackage(WRITE_PKG);
-            final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            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) {
-                runDeviceTests(WRITE_PKG, ".WriteGiftTest", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", user);
             }
 
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, ".ReadGiftTest", user);
-                runDeviceTests(NONE_PKG, ".GiftTest", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", user);
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", user);
             }
         } finally {
             getDevice().uninstallPackage(NONE_PKG);
@@ -186,6 +179,7 @@
      * Test multi-user emulated storage environment, ensuring that each user has
      * isolated storage.
      */
+    @Test
     public void testMultiUserStorageIsolated() throws Exception {
         try {
             if (mUsers.length == 1) {
@@ -198,7 +192,7 @@
 
             // Install our test app
             getDevice().uninstallPackage(MULTIUSER_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
             final String installResult = getDevice()
                     .installPackage(getTestAppFile(MULTIUSER_APK), false, options);
             assertNull("Failed to install: " + installResult, installResult);
@@ -231,6 +225,7 @@
      * Test that apps with read permissions see the appropriate permissions
      * when apps with r/w permission levels move around their files.
      */
+    @Test
     public void testMultiViewMoveConsistency() throws Exception {
         try {
             wipePrimaryExternalStorage();
@@ -238,32 +233,32 @@
             getDevice().uninstallPackage(NONE_PKG);
             getDevice().uninstallPackage(READ_PKG);
             getDevice().uninstallPackage(WRITE_PKG);
-            final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
 
             assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
 
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, ".ReadMultiViewTest", "testFolderSetup", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadMultiViewTest", "testFolderSetup", user);
             }
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, ".ReadMultiViewTest", "testRWAccess", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadMultiViewTest", "testRWAccess", user);
             }
 
             for (int user : mUsers) {
-                runDeviceTests(WRITE_PKG, ".WriteMultiViewTest", "testMoveAway", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteMultiViewTest", "testMoveAway", user);
             }
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, ".ReadMultiViewTest", "testROAccess", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadMultiViewTest", "testROAccess", user);
             }
 
             // for fuse file system
             Thread.sleep(10000);
             for (int user : mUsers) {
-                runDeviceTests(WRITE_PKG, ".WriteMultiViewTest", "testMoveBack", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteMultiViewTest", "testMoveBack", user);
             }
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, ".ReadMultiViewTest", "testRWAccess", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadMultiViewTest", "testRWAccess", user);
             }
         } finally {
             getDevice().uninstallPackage(NONE_PKG);
@@ -273,13 +268,14 @@
     }
 
     /** Verify that app without READ_EXTERNAL can play default URIs in external storage. */
+    @Test
     public void testExternalStorageReadDefaultUris() throws Exception {
         try {
             wipePrimaryExternalStorage();
 
             getDevice().uninstallPackage(NONE_PKG);
             getDevice().uninstallPackage(WRITE_PKG);
-            final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
 
             assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
@@ -303,6 +299,45 @@
         }
     }
 
+    /**
+     * For security reasons, the shell user cannot access the shared storage of
+     * secondary users. Instead, developers should use the {@code content} shell
+     * tool to read/write files in those locations.
+     */
+    @Test
+    public void testSecondaryUsersInaccessible() throws Exception {
+        List<String> mounts = new ArrayList<>();
+        for (String line : getDevice().executeShellCommand("cat /proc/mount").split("\n")) {
+            String[] split = line.split(" ");
+            if (split[1].startsWith("/storage/") || split[1].startsWith("/mnt/")) {
+                mounts.add(split[1]);
+            }
+        }
+
+        for (int user : mUsers) {
+            String probe = "/sdcard/../" + user;
+            if (user == Utils.USER_SYSTEM) {
+                // Primary user should always be visible. Skip checking raw
+                // mount points, since we'd get false-positives for physical
+                // devices that aren't multi-user aware.
+                assertTrue(probe, access(probe));
+            } else {
+                // Secondary user should never be visible.
+                assertFalse(probe, access(probe));
+                for (String mount : mounts) {
+                    probe = mount + "/" + user;
+                    assertFalse(probe, access(probe));
+                }
+            }
+        }
+    }
+
+    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();
@@ -326,11 +361,11 @@
 
     private void runDeviceTests(String packageName, String testClassName, int userId)
             throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, userId);
+        runDeviceTests(getDevice(), packageName, testClassName, null, userId, null);
     }
 
     private void runDeviceTests(String packageName, String testClassName, String testMethodName,
             int userId) throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
+        runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, null);
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/KeySetHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/KeySetHostTest.java
index 3574191..03a1189 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/KeySetHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/KeySetHostTest.java
@@ -17,17 +17,15 @@
 package android.appsecurity.cts;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 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;
 
@@ -119,7 +117,7 @@
 
             /* build a meaningful error message */
             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                 result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
new file mode 100644
index 0000000..15c3d3c
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.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;
+
+/**
+ * Test that install of apps using major version codes is being handled properly.
+ */
+public class MajorVersionTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+    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();
+
+        Utils.prepareSingleUser(getDevice());
+        assertNotNull(mAbi);
+        assertNotNull(mBuildHelper);
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    public void testInstallMinorVersion() throws Exception {
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        getDevice().uninstallPackage(PKG);
+    }
+
+    public void testInstallMajorVersion() throws Exception {
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000ff00000000), false, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        getDevice().uninstallPackage(PKG);
+    }
+
+    public void testInstallUpdateAcrossMinorMajorVersion() throws Exception {
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000ffffffffff), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        getDevice().uninstallPackage(PKG);
+    }
+
+    public void testInstallDowngradeAcrossMajorMinorVersion() throws Exception {
+        assertNull(getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000ffffffffff), false, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+                mBuildHelper.getTestFile(APK_000000000000ffff), true, false));
+        assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+        runVersionDeviceTests("testCheckVersion");
+        getDevice().uninstallPackage(PKG);
+    }
+
+    private void runVersionDeviceTests(String testMethodName)
+            throws DeviceNotAvailableException {
+        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 b543fbd6ae..cf903db 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -17,7 +17,6 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
@@ -44,13 +43,16 @@
     }
 
     public void testInstallingOverlayHasNoEffect() throws Exception {
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
-        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        getDevice().getIDevice().executeShellCommand("cmd overlay list", receiver);
+        assertFalse(getDevice().getInstalledPackageNames().contains(PKG));
+
+        // Try to install the overlay, but expect an error.
+        assertNotNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
+
+        // The install should have failed.
+        assertFalse(getDevice().getInstalledPackageNames().contains(PKG));
 
         // The package of the installed overlay should not appear in the overlay manager list.
-        final String output = receiver.getOutput();
-        assertFalse(output.contains(PKG));
+        assertFalse(getDevice().executeShellCommand("cmd overlay list").contains(PKG));
     }
 
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java
new file mode 100644
index 0000000..680798a
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.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 com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for package resolution.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageResolutionHostTest extends BaseAppSecurityTest {
+
+    private static final String TINY_APK = "CtsOrderedActivityApp.apk";
+    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
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(TINY_PKG);
+    }
+
+    @Test
+    public void testResolveOrderedActivity() throws Exception {
+        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+        Utils.runDeviceTests(getDevice(), TINY_PKG,
+                ".PackageResolutionTest", "queryActivityOrdered");
+        getDevice().uninstallPackage(TINY_PKG);
+    }
+
+    @Test
+    public void testResolveOrderedService() throws Exception {
+        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+        Utils.runDeviceTests(getDevice(), TINY_PKG,
+                ".PackageResolutionTest", "queryServiceOrdered");
+        getDevice().uninstallPackage(TINY_PKG);
+    }
+
+    @Test
+    public void testResolveOrderedReceiver() throws Exception {
+        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+        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 033f257..5bb70d1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
@@ -16,11 +16,21 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
+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 visibility of packages installed in one user, in a different user.
  */
+@RunWith(DeviceJUnit4ClassRunner.class)
 public class PackageVisibilityTest extends BaseAppSecurityTest {
 
     private static final String TINY_APK = "CtsPkgInstallTinyApp.apk";
@@ -35,8 +45,8 @@
     private int[] mUsers;
     private String mOldVerifierValue;
 
-    public void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUpPackage() throws Exception {
 
         mUsers = Utils.prepareMultipleUsers(getDevice());
         mOldVerifierValue =
@@ -47,14 +57,15 @@
         installTestAppForUser(TEST_APK, mPrimaryUserId);
     }
 
+    @After
     public void tearDown() throws Exception {
         getDevice().uninstallPackage(TEST_PKG);
         getDevice().uninstallPackage(TINY_PKG);
         getDevice().executeShellCommand("settings put global package_verifier_enable "
                 + mOldVerifierValue);
-        super.tearDown();
     }
 
+    @Test
     public void testUninstalledPackageVisibility() throws Exception {
         if (!mSupportsMultiUser) {
             return;
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index caabeb5..e4b0f09 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -16,6 +16,8 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.Presubmit;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -46,6 +48,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 SCREEN_OFF_TIMEOUT_NS = "system";
     private static final String SCREEN_OFF_TIMEOUT_KEY = "screen_off_timeout";
     private String mScreenTimeoutBeforeTest;
@@ -74,6 +80,7 @@
         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,
@@ -95,6 +102,7 @@
         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 {
@@ -265,6 +273,7 @@
                 "testRequestNonExistentPermission");
     }
 
+    @Presubmit
     public void testRequestPermissionFromTwoGroups23() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
@@ -347,6 +356,49 @@
                 "testNoProtectionFlagsAddedToNonSignatureProtectionPermissions");
     }
 
+    public void testLegacyAppAccessSerial() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+                APK_PERMISSION_POLICY_25), false, false));
+        runDeviceTests(PERMISSION_POLICY_25_PKG,
+                "com.android.cts.permission.policy.PermissionPolicyTest25",
+                "testNoProtectionFlagsAddedToNonSignatureProtectionPermissions");
+    }
+
+    public void testNullPermissionRequest() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
+                "testNullPermissionRequest");
+    }
+
+    public void testNullAndRealPermission() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
+                "testNullAndRealPermission");
+    }
+
+    public void testInvalidPermission() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
+                "testInvalidPermission");
+    }
+
+
+    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");
+    }
+
     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/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 589d3b9..f444bbc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -16,14 +16,17 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.SecurityTest;
+
+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 android.platform.test.annotations.SecurityTest;
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -36,6 +39,10 @@
 public class PkgInstallSignatureVerificationTest extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
+    private static final String COMPANION_TEST_PKG = "android.appsecurity.cts.tinyapp_companion";
+    private static final String DEVICE_TESTS_APK = "CtsV3SigningSchemeRotationTest.apk";
+    private static final String DEVICE_TESTS_PKG = "android.appsecurity.cts.v3rotationtests";
+    private static final String DEVICE_TESTS_CLASS = DEVICE_TESTS_PKG + ".V3RotationTest";
     private static final String TEST_APK_RESOURCE_PREFIX = "/pkgsigverify/";
 
     private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"};
@@ -58,12 +65,14 @@
         Utils.prepareSingleUser(getDevice());
         assertNotNull(mCtsBuild);
         uninstallPackage();
+        uninstallCompanionPackage();
+        installDeviceTestPkg();
     }
 
     @Override
     protected void tearDown() throws Exception {
         try {
-            uninstallPackage();
+            uninstallPackages();
         } catch (DeviceNotAvailableException ignored) {
         } finally {
             super.tearDown();
@@ -234,6 +243,16 @@
                 );
     }
 
+    public void testInstallV1SignatureOnlyDoesNotVerify() throws Exception {
+        // APK signed with v1 scheme only, but not all digests match those recorded in
+        // META-INF/MANIFEST.MF.
+        String error = "META-INF/MANIFEST.MF has invalid digest";
+
+        // Bitflip in classes.dex of otherwise good file.
+        assertInstallFailsWithError(
+                "v1-only-with-tampered-classes-dex.apk", error);
+    }
+
     public void testInstallV2SignatureDoesNotVerify() throws Exception {
         // APK signed with v2 scheme only, but the signature over signed-data does not verify.
         String error = "signature did not verify";
@@ -469,9 +488,10 @@
     }
 
     public void testInstallEphemeralRequiresV2Signature() throws Exception {
-        String expectedNoSigError = "No APK Signature Scheme v2 signature in ephemeral package";
-        assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk", expectedNoSigError);
-        assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk", expectedNoSigError);
+        assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk",
+                "Failed to collect certificates");
+        assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk",
+                "must be signed with APK Signature Scheme v2 or greater");
         assertInstallEphemeralSucceeds("v2-only-ephemeral.apk");
         assertInstallEphemeralSucceeds("v1-v2-ephemeral.apk"); // signed with both schemes
     }
@@ -499,6 +519,213 @@
         assertInstallFailsWithError("v2-only-starts-with-dex-magic.apk", error);
     }
 
+    public void testInstallV3KeyRotation() throws Exception {
+        // tests that a v3 signed APK with RSA key can rotate to a new key
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+    }
+
+    public void testInstallV3KeyRotationMultipleHops() throws Exception {
+        // tests that a v3 signed APK with RSA key can rotate to a new key which is the result of
+        // multiple rotations from the original: APK signed with key 1 can be updated by key 3, when
+        // keys were: 1 -> 2 -> 3
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-3-with-por_1_2_3-full-caps.apk");
+    }
+
+    public void testInstallV3PorSignerMismatch() throws Exception {
+        // tests that an APK with a proof-of-rotation struct that doesn't include the current
+        // signing certificate fails to install
+        assertInstallFails("v3-rsa-pkcs1-sha256-2048-3-with-por_1_2-full-caps.apk");
+    }
+
+    public void testInstallV3KeyRotationWrongPor() throws Exception {
+        // tests that a valid APK with a proof-of-rotation record can't upgrade an APK with a
+        // signing certificate that isn't in the proof-of-rotation record
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
+        assertInstallFails("v3-rsa-pkcs1-sha256-2048-3-with-por_2_3-full-caps.apk");
+    }
+
+    public void testInstallV3KeyRotationSharedUid() throws Exception {
+        // tests that a v3 signed sharedUid APK can still be sharedUid with apps with its older
+        // signing certificate, if it so desires
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk");
+    }
+
+    public void testInstallV3KeyRotationOlderSharedUid() throws Exception {
+
+        // tests that a sharedUid APK can still install with another app that is signed by a newer
+        // signing certificate, but which allows sharedUid with the older one
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
+    }
+
+    public void testInstallV3KeyRotationSharedUidNoCap() throws Exception {
+        // tests that a v3 signed sharedUid APK cannot be sharedUid with apps with its older
+        // signing certificate, when it has not granted that certificate the sharedUid capability
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
+        assertInstallFails(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
+    }
+
+    public void testInstallV3KeyRotationOlderSharedUidNoCap() throws Exception {
+        // tests that a sharedUid APK signed with an old certificate cannot install with
+        // an app having a proof-of-rotation structure that hasn't granted the older
+        // certificate the sharedUid capability
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
+        assertInstallFails("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
+    }
+
+    public void testInstallV3NoRotationSharedUid() throws Exception {
+        // tests that a sharedUid APK signed with a new certificate installs with
+        // an app having a proof-of-rotation structure that hasn't granted an older
+        // certificate the sharedUid capability
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-sharedUid.apk");
+    }
+
+    public void testInstallV3KeyRotationSigPerm() throws Exception {
+        // tests that a v3 signed APK can still get a signature permission from an app with its
+        // older signing certificate.
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permcli-companion.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testInstallV3KeyRotationOlderSigPerm() throws Exception {
+        // tests that an apk with an older signing certificate than the one which defines a
+        // signature permission it wants gets the permission if the defining APK grants the
+        // capability
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permdef.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testInstallV3KeyRotationSigPermNoCap() throws Exception {
+        // tests that an APK signed by an older signing certificate is unable to get a requested
+        // signature permission when the defining APK has rotated to a newer signing certificiate
+        // and does not grant the permission capability to the older cert
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
+    }
+
+    public void testInstallV3KeyRotationOlderSigPermNoCap() throws Exception {
+        // tests that an APK signed by a newer signing certificate than the APK which defines a
+        // signature permission is able to get that permission, even if the newer APK does not
+        // grant the permission capability to the older signing certificate.
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permcli-companion.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testInstallV3NoRotationSigPerm() throws Exception {
+        // make sure that an APK, which wants to use a signature permission defined by an APK, which
+        // has not granted that capability to older signing certificates, can still install
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-permcli-companion.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testInstallV3SigPermDoubleDefNewerSucceeds() throws Exception {
+        // make sure that if an app defines a signature permission already defined by another app,
+        // it successfully installs if the other app's signing cert is in its past signing certs and
+        // the signature permission capability is granted
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk");
+    }
+
+    public void testInstallV3SigPermDoubleDefOlderSucceeds() throws Exception {
+        // make sure that if an app defines a signature permission already defined by another app,
+        // it successfully installs if it is in the other app's past signing certs and the signature
+        // permission capability is granted
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+    }
+
+    public void testInstallV3SigPermDoubleDefNewerNoCapFails() throws Exception {
+        // make sure that if an app defines a signature permission already defined by another app,
+        // it fails to install if the other app's signing cert is in its past signing certs but the
+        // signature permission capability is not granted
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+        assertInstallFails(
+                "v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
+    }
+
+    public void testInstallV3SigPermDoubleDefOlderNoCapFails() throws Exception {
+        // make sure that if an app defines a signature permission already defined by another app,
+        // it fails to install if it is in the other app's past signing certs but the signature
+        // permission capability is not granted
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
+        assertInstallFails("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
+    }
+
+    public void testInstallV3SigPermDoubleDefSameNoCapSucceeds() throws Exception {
+        // make sure that if an app defines a signature permission already defined by another app,
+        // it installs successfully when signed by the same certificate, even if the original app
+        // does not grant signature capabilities to its past certs
+        assertInstallSucceeds(
+                "v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-permdef.apk");
+    }
+
+    public void testInstallV3KeyRotationGetSignatures() throws Exception {
+        // tests that a PackageInfo w/GET_SIGNATURES flag returns the older cert
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testGetSignaturesShowsOld");
+    }
+
+    public void testInstallV3KeyRotationGetSigningCertificates() throws Exception {
+        // tests that a PackageInfo w/GET_SIGNING_CERTIFICATES flag returns the old and new certs
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testGetSigningCertificatesShowsAll");
+    }
+
+    public void testInstallV3KeyRotationHasSigningCertificate() throws Exception {
+        // tests that hasSigningCertificate() recognizes past and current signing certs
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testHasSigningCertificate");
+    }
+
+    public void testInstallV3KeyRotationHasSigningCertificateSha256() throws Exception {
+        // tests that hasSigningCertificate() recognizes past and current signing certs by sha256
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testHasSigningCertificateSha256");
+    }
+
+    public void testInstallV3KeyRotationHasSigningCertificateByUid() throws Exception {
+        // tests that hasSigningCertificate() recognizes past and current signing certs by uid
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testHasSigningCertificateByUid");
+    }
+
+    public void testInstallV3KeyRotationHasSigningCertificateByUidSha256() throws Exception {
+        // tests that hasSigningCertificate() recognizes past and current signing certs by uid
+        // and sha256
+        assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testHasSigningCertificateByUidSha256");
+    }
+
     private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
         String installResult = installPackageFromResource(apkFilenameInResources);
         if (installResult != null) {
@@ -575,6 +802,13 @@
         }
     }
 
+    private void installDeviceTestPkg() throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        File apk = buildHelper.getTestFile(DEVICE_TESTS_APK);
+        String result = getDevice().installPackage(apk, true);
+        assertNull("failed to install " + DEVICE_TESTS_APK + ", Reason: " + result, result);
+    }
+
     private String installPackageFromResource(String apkFilenameInResources, boolean ephemeral)
             throws IOException, DeviceNotAvailableException {
         // ITestDevice.installPackage API requires the APK to be install to be a File. We thus
@@ -618,4 +852,18 @@
     private String uninstallPackage() throws DeviceNotAvailableException {
         return getDevice().uninstallPackage(TEST_PKG);
     }
+
+    private String uninstallCompanionPackage() throws DeviceNotAvailableException {
+        return getDevice().uninstallPackage(COMPANION_TEST_PKG);
+    }
+
+    private String uninstallDeviceTestPackage() throws DeviceNotAvailableException {
+        return getDevice().uninstallPackage(DEVICE_TESTS_PKG);
+    }
+
+    private void uninstallPackages() throws DeviceNotAvailableException {
+        uninstallPackage();
+        uninstallCompanionPackage();
+        uninstallDeviceTestPackage();
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
index 889b20b..96992bf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
@@ -58,4 +58,12 @@
     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");
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 9474ba8..acec1fb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -34,7 +34,7 @@
     static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
 
     static final String PKG = "com.android.cts.splitapp";
-    static final String CLASS = ".SplitAppTest";
+    static final String CLASS = PKG + ".SplitAppTest";
 
     static final String APK = "CtsSplitApp.apk";
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 6bf71cf..38f7347 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -16,9 +16,13 @@
 
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import junit.framework.AssertionFailedError;
 
@@ -27,13 +31,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
 
 /**
  * Tests that exercise various storage APIs.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class StorageHostTest extends CompatibilityHostTestBase {
+public class StorageHostTest extends BaseHostJUnit4Test {
     private static final String PKG_STATS = "com.android.cts.storagestatsapp";
     private static final String PKG_A = "com.android.cts.storageapp_a";
     private static final String PKG_B = "com.android.cts.storageapp_b";
@@ -69,8 +73,8 @@
     }
 
     @Test
-    public void testVerifyQuota() throws Exception {
-        Utils.runDeviceTests(getDevice(), PKG_STATS, CLASS_STATS, "testVerifyQuota");
+    public void testVerify() throws Exception {
+        Utils.runDeviceTests(getDevice(), PKG_STATS, CLASS_STATS, "testVerify");
     }
 
     @Test
@@ -239,7 +243,22 @@
 
     public void runDeviceTests(String packageName, String testClassName, String testMethodName,
             int userId) throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, null,
-                20L, TimeUnit.MINUTES);
+        if (!runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, 20 * 60 * 1000L)) {
+            TestRunResult res = getLastDeviceRunResults();
+            if (res != null) {
+                StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
+                for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    res.getTestResults().entrySet()) {
+                    if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                        errorBuilder.append(resultEntry.getKey().toString());
+                        errorBuilder.append(":\n");
+                        errorBuilder.append(resultEntry.getValue().getStackTrace());
+                    }
+                }
+                throw new AssertionError(errorBuilder.toString());
+            } else {
+                throw new AssertionFailedError("Error when running device tests.");
+            }
+        }
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
index 67ee091..c63720f 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
@@ -17,13 +17,13 @@
 package android.appsecurity.cts;
 
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 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 java.util.Arrays;
 import java.util.Map;
@@ -32,26 +32,6 @@
 public class Utils {
     public static final int USER_SYSTEM = 0;
 
-    public static void runDeviceTests(ITestDevice device, String packageName)
-            throws DeviceNotAvailableException {
-        runDeviceTests(device, packageName, null, null, USER_SYSTEM, null);
-    }
-
-    public static void runDeviceTests(ITestDevice device, String packageName, int userId)
-            throws DeviceNotAvailableException {
-        runDeviceTests(device, packageName, null, null, userId, null);
-    }
-
-    public static void runDeviceTests(ITestDevice device, String packageName, String testClassName)
-            throws DeviceNotAvailableException {
-        runDeviceTests(device, packageName, testClassName, null, USER_SYSTEM, null);
-    }
-
-    public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
-            int userId) throws DeviceNotAvailableException {
-        runDeviceTests(device, packageName, testClassName, null, userId, null);
-    }
-
     public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
         runDeviceTests(device, packageName, testClassName, testMethodName, USER_SYSTEM, null);
@@ -113,7 +93,7 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                 result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
new file mode 100644
index 0000000..bfb88f0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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 \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialLegacy
+LOCAL_SDK_VERSION := current
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml
new file mode 100644
index 0000000..6976c56
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.os.cts">
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
+
+    <application/>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.os.cts" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java
new file mode 100644
index 0000000..6dd97c3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.os.cts;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.os.Build;
+import org.junit.Test;
+
+/**
+ * Test that legacy apps can access the device serial without the phone permission.
+ */
+public class AccessSerialLegacyTest {
+    @Test
+    public void testAccessSerialNoPermissionNeeded() throws Exception {
+        // Build.SERIAL should provide the device serial for legacy apps.
+        // We don't know the serial but know that it should not be the dummy
+        // value returned to unauthorized callers, so make sure not that value
+        assertTrue("Build.SERIAL must be visible to legacy apps",
+                !Build.UNKNOWN.equals(Build.SERIAL));
+
+        // We don't have the READ_PHONE_STATE permission, so this should throw
+        try {
+            Build.getSerial();
+            fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
new file mode 100644
index 0000000..4bdb346
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
@@ -0,0 +1,37 @@
+#
+# 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 := \
+    compatibility-device-util \
+    android-support-test \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialModern
+LOCAL_SDK_VERSION := current
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml
new file mode 100644
index 0000000..2eb5777
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.os.cts">
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28" />
+
+    <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.os.cts" />
+
+</manifest>
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
new file mode 100644
index 0000000..df8ed97
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.os.cts;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Test that legacy apps can access the device serial without the phone permission.
+ */
+public class AccessSerialModernTest {
+    @Test
+    public void testAccessSerialPermissionNeeded() throws Exception {
+        // Build.SERIAL should not provide the device serial for modern apps.
+        // We don't know the serial but know that it should be the dummy
+        // value returned to unauthorized callers, so make sure that value
+        assertTrue("Build.SERIAL must not work for modern apps",
+                Build.UNKNOWN.equals(Build.SERIAL));
+
+        // We don't have the read phone state permission, so this should throw
+        try {
+            Build.getSerial();
+            fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+
+        // Now grant ourselves READ_PHONE_STATE
+        grantReadPhoneStatePermission();
+
+        // Build.SERIAL should not provide the device serial for modern apps.
+        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
+        try {
+            assertTrue("Build.getSerial() must work for apps holding READ_PHONE_STATE",
+                    !Build.UNKNOWN.equals(Build.getSerial()));
+        } catch (SecurityException e) {
+            fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+        }
+    }
+
+    private void grantReadPhoneStatePermission() throws IOException {
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "pm grant " + InstrumentationRegistry.getContext().getPackageName()
+                + " " + Manifest.permission.READ_PHONE_STATE);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
index 2e6911d..615492c 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
@@ -21,7 +21,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := CtsAppAccessData
 
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
index ffc104e..6a846fc 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
@@ -22,6 +22,8 @@
     created by com.android.cts.appwithdata.
     -->
 
+    <uses-permission android:name="android.permission.INTERNET"/>
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
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 0867968..7a64583 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,17 +16,25 @@
 
 package com.android.cts.appaccessdata;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.test.AndroidTestCase;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
 
 /**
  * Test that another app's private data cannot be accessed, while its public data can.
@@ -51,6 +59,10 @@
      */
     private static final String PUBLIC_FILE_NAME = "public_file.txt";
 
+    private static final String QTAGUID_STATS_FILE = "/proc/net/xt_qtaguid/stats";
+
+    private static final Uri PRIVATE_TARGET = Uri.parse("content://com.android.cts.appwithdata/");
+
     /**
      * Tests that another app's private data cannot be accessed. It includes file
      * and detailed traffic stats.
@@ -68,7 +80,6 @@
         } catch (FileNotFoundException | SecurityException e) {
             // expected
         }
-        accessPrivateTrafficStats();
     }
 
     private ApplicationInfo getApplicationInfo(String packageName) {
@@ -97,7 +108,15 @@
         }
     }
 
-    private void accessPrivateTrafficStats() throws IOException {
+    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
+        // will ensure they cannot read other apps stats.
+        assertFalse("untrusted app should not be able to read qtaguid profile",
+            new File(QTAGUID_STATS_FILE).canRead());
+    }
+
+    public void testAccessPrivateTrafficStats() {
         int otherAppUid = -1;
         try {
             otherAppUid = getContext()
@@ -106,19 +125,53 @@
         } catch (NameNotFoundException e) {
             fail("Was not able to find other app");
         }
+
+        final int UNSUPPORTED = -1;
+        assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(otherAppUid));
+        assertEquals(UNSUPPORTED, TrafficStats.getUidRxPackets(otherAppUid));
+        assertEquals(UNSUPPORTED, TrafficStats.getUidTxBytes(otherAppUid));
+        assertEquals(UNSUPPORTED, TrafficStats.getUidTxPackets(otherAppUid));
+    }
+
+    public void testTrafficStatsStatsUidSelf() throws Exception {
+        final int uid = android.os.Process.myUid();
+        final long rxb = TrafficStats.getUidRxBytes(uid);
+        final long rxp = TrafficStats.getUidRxPackets(uid);
+        final long txb = TrafficStats.getUidTxBytes(uid);
+        final long txp = TrafficStats.getUidTxPackets(uid);
+
+        // Start remote server
+        final int port = getContext().getContentResolver().call(PRIVATE_TARGET, "start", null, null)
+                .getInt("port");
+
+        // Try talking to them, but shift blame
         try {
-            BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
-            String line;
-            while ((line = qtaguidReader.readLine()) != null) {
-                String tokens[] = line.split(" ");
-                if (tokens.length > 3 && tokens[3].equals(String.valueOf(otherAppUid))) {
-                    // CreatePrivateDataTest:testCreatePrivateData ensures we can access our own stats data
-                    fail("Other apps detailed traffic stats leaked");
-                }
-            }
-            qtaguidReader.close();
-        } catch (FileNotFoundException e) {
-            fail("Was not able to access qtaguid/stats: " + e);
+            final Socket socket = new Socket();
+            socket.setTcpNoDelay(true);
+
+            Bundle extras = new Bundle();
+            extras.putParcelable("fd", ParcelFileDescriptor.fromSocket(socket));
+            getContext().getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
+
+            socket.connect(new InetSocketAddress("localhost", port));
+
+            socket.getOutputStream().write(42);
+            socket.getOutputStream().flush();
+            final int val = socket.getInputStream().read();
+            assertEquals(42, val);
+            socket.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            getContext().getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
         }
+
+        SystemClock.sleep(1000);
+
+        // Since we shifted blame, our stats shouldn't have changed
+        assertEquals(rxb, TrafficStats.getUidRxBytes(uid));
+        assertEquals(rxp, TrafficStats.getUidRxPackets(uid));
+        assertEquals(txb, TrafficStats.getUidTxBytes(uid));
+        assertEquals(txp, TrafficStats.getUidTxPackets(uid));
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
index 3d11a83..c719665 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
@@ -21,7 +21,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := CtsAppWithData
 
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index 598ebe7..2accec1 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -23,8 +23,14 @@
     -->
 
     <uses-permission android:name="android.permission.INTERNET" />
+
     <application>
         <uses-library android:name="android.test.runner" />
+
+        <provider
+            android:name="com.android.cts.appwithdata.MyProvider"
+            android:authorities="com.android.cts.appwithdata"
+            android:exported="true" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
index f3787e8..279d878 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
@@ -89,7 +89,6 @@
 
         writeToPreferences();
         writeToDatabase();
-        createTrafficStatsWithTags();
     }
 
     private void accessPublicData() throws IOException {
@@ -169,76 +168,6 @@
         }
     }
 
-    private void accessOwnTrafficStats() throws IOException {
-        final int ownAppUid = getContext().getApplicationInfo().uid;
-
-        boolean foundOwnDetailedStats = false;
-        try {
-            BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
-            String line;
-            while ((line = qtaguidReader.readLine()) != null) {
-                String tokens[] = line.split(" ");
-                if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
-                    if (!tokens[2].equals("0x0")) {
-                      foundOwnDetailedStats = true;
-                    }
-                }
-            }
-            qtaguidReader.close();
-        } catch (FileNotFoundException e) {
-            fail("Was not able to access qtaguid/stats: " + e);
-        }
-        assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats);
-    }
-
-    private void createTrafficStatsWithTags() throws IOException {
-
-        // Transfer 1MB of data across an explicitly localhost socket.
-        final int byteCount = 1024;
-        final int packetCount = 1024;
-
-        final ServerSocket server = new ServerSocket(0);
-        new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") {
-            @Override
-            public void run() {
-                try {
-                    Socket socket = new Socket("localhost", server.getLocalPort());
-                    // Make sure that each write()+flush() turns into a packet:
-                    // disable Nagle.
-                    socket.setTcpNoDelay(true);
-                    OutputStream out = socket.getOutputStream();
-                    byte[] buf = new byte[byteCount];
-                    for (int i = 0; i < packetCount; i++) {
-                        TrafficStats.setThreadStatsTag(i % 10);
-                        TrafficStats.tagSocket(socket);
-                        out.write(buf);
-                        out.flush();
-                    }
-                    out.close();
-                    socket.close();
-                } catch (IOException e) {
-                  assertTrue("io exception" + e, false);
-                }
-            }
-        }.start();
-
-        try {
-            Socket socket = server.accept();
-            InputStream in = socket.getInputStream();
-            byte[] buf = new byte[byteCount];
-            int read = 0;
-            while (read < byteCount * packetCount) {
-                int n = in.read(buf);
-                assertTrue("Unexpected EOF", n > 0);
-                read += n;
-            }
-        } finally {
-            server.close();
-        }
-
-        accessOwnTrafficStats();
-    }
-
     static class TestDatabaseOpenHelper extends SQLiteOpenHelper {
 
         static final String _ID = "_id";
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java
new file mode 100644
index 0000000..135c27c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appwithdata;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class MyProvider extends ContentProvider {
+    private static final String TAG = "CTS";
+
+    @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();
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        Log.v(TAG, "call() " + method);
+        switch (method) {
+            case "start": {
+                server = new EchoServer();
+                server.start();
+                extras = new Bundle();
+                extras.putInt("port", server.server.getLocalPort());
+                return extras;
+            }
+            case "stop": {
+                server.halt();
+                return null;
+            }
+            case "tag": {
+                Log.v(TAG, "My UID is " + android.os.Process.myUid());
+                TrafficStats.setThreadStatsUidSelf();
+                try {
+                    final ParcelFileDescriptor pfd = extras.getParcelable("fd");
+                    TrafficStats.tagFileDescriptor(pfd.getFileDescriptor());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                } finally {
+                    TrafficStats.clearThreadStatsUid();
+                }
+                return null;
+            }
+            default: {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    private EchoServer server;
+
+    private static class EchoServer extends Thread {
+        final ServerSocket server;
+        volatile boolean halted = false;
+
+        public EchoServer() {
+            try {
+                server = new ServerSocket(0);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void halt() {
+            halted = true;
+            try {
+                server.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void run() {
+            while (!halted) {
+                try {
+                    final Socket socket = server.accept();
+                    socket.setTcpNoDelay(true);
+                    final int val = socket.getInputStream().read();
+                    socket.getOutputStream().write(val);
+                    socket.getOutputStream().flush();
+                    socket.close();
+                } catch (IOException e) {
+                    Log.w(TAG, e);
+                }
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
index 0a55834..ad50a57 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
@@ -22,6 +22,8 @@
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsDocumentClient
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index 68e554d..7038685 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -60,6 +60,10 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        // Wake up the device and dismiss the keyguard before the test starts.
+        executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        executeShellCommand("wm dismiss-keyguard");
+
         Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
 
         // Disable IME's to avoid virtual keyboards from showing up. Occasionally IME draws some UI
@@ -140,6 +144,14 @@
         return true;
     }
 
+    protected boolean supportedHardwareForScopedDirectoryAccess() {
+        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        if (pm.hasSystemFeature("android.hardware.type.watch")) {
+            return false;
+        }
+        return true;
+    }
+
     protected void assertActivityFailed() {
         final Result result = mActivity.getResult();
         assertEquals(REQUEST_CODE, result.requestCode);
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 dec8769..207e3a6 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
@@ -37,8 +37,11 @@
 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;
@@ -77,7 +80,7 @@
     }
 
     public void testInvalidPath() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             openExternalDirectoryInvalidPath(volume, "");
@@ -89,7 +92,7 @@
     }
 
     public void testUserRejects() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             // Tests user clicking DENY button, for all valid directories.
@@ -114,7 +117,7 @@
     }
 
     public void testUserAccepts() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             userAcceptsTest(volume, DIRECTORY_PICTURES);
@@ -125,7 +128,7 @@
     }
 
     public void testUserAcceptsNewDirectory() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         // TODO: figure out a better way to remove the directory.
         final String command = "rm -rf /sdcard/" + DIRECTORY_PICTURES;
@@ -137,7 +140,7 @@
     }
 
     public void testNotAskedAgain() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
@@ -157,7 +160,7 @@
     }
 
     public void testNotAskedAgainOnRoot() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             if (volume.isPrimary()) continue;
@@ -185,7 +188,7 @@
     }
 
     public void testDeniesOnceButAllowsAskingAgain() throws Exception {
-        if (!supportedHardware())return;
+        if (!supportedHardwareForScopedDirectoryAccess())return;
 
         final String[] dirs = { DIRECTORY_DCIM, DIRECTORY_ROOT };
         for (StorageVolume volume : getVolumes()) {
@@ -210,7 +213,7 @@
     }
 
     public void testDeniesOnceForAll() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         final String[] dirs = {DIRECTORY_PICTURES, DIRECTORY_ROOT};
         for (StorageVolume volume : getVolumes()) {
@@ -246,13 +249,13 @@
     }
 
     public void testRemovePackageStep1UserDenies() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         deniesOnceForAllTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
     }
 
     public void testRemovePackageStep2UserAcceptsDoNotClear() throws Exception {
-        if (!supportedHardware()) return;
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         userAcceptsTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
     }
@@ -285,6 +288,17 @@
         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...
@@ -317,6 +331,94 @@
         return grantedUri;
     }
 
+    public void testResetDoNotAskAgain() throws Exception {
+        if (!supportedHardwareForScopedDirectoryAccess()) return;
+
+        final StorageVolume volume = getPrimaryVolume();
+        final String dir = DIRECTORY_PICTURES;
+
+        // 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
+        final List<UiObject2> toggles = mDevice.findObjects(By.res("android:id/switch_widget"));
+        assertEquals("should have just one toggle: " + toggles, 1, toggles.size());
+        final UiObject2 toggle = toggles.get(0);
+        assertFalse("toggle for '" + dir + "' should not be checked", toggle.isChecked());
+        toggle.click();
+        assertTrue("toggle for '" + dir + "' should be checked", toggle.isChecked());
+
+        // Close app screen.
+        mDevice.pressBack();
+        // 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();
+        final String dir = DIRECTORY_PICTURES;
+
+        // First, grants it
+        userAcceptsTest(volume, DIRECTORY_PICTURES);
+
+        // 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
+        final List<UiObject2> toggles = mDevice.findObjects(By.res("android:id/switch_widget"));
+        assertEquals("should have just one toggle: " + toggles, 1, toggles.size());
+        final UiObject2 toggle = toggles.get(0);
+        assertTrue("toggle for '" + dir + "' should be checked", toggle.isChecked());
+        toggle.click();
+        assertFalse("toggle for '" + dir + "' should not be checked", toggle.isChecked());
+
+        // Close app screen.
+        mDevice.pressBack();
+        // Close main screen.
+        mDevice.pressBack();
+
+        // Then tries again - should be denied.
+        sendOpenExternalDirectoryIntent(volume, dir);
+        assertActivityFailed();
+    }
+
+    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 '"
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
index 7609e33..42aa074 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
@@ -20,7 +20,12 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
index 894eff1..6e140d7 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.documentprovider">
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <provider android:name=".MyDocumentsProvider"
                 android:authorities="com.android.cts.documentprovider"
                 android:exported="true"
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
index a4a9436..9734112 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
@@ -22,6 +22,8 @@
 LOCAL_SDK_VERSION := current
 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, src)
 
 LOCAL_PACKAGE_NAME := CtsEncryptionApp
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 37744fa..af75334 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
@@ -27,6 +27,7 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -205,6 +206,14 @@
         assertQuery(1, MATCH_DIRECT_BOOT_AWARE);
         assertQuery(1, MATCH_DIRECT_BOOT_UNAWARE);
         assertQuery(2, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
+
+        if (Environment.isExternalStorageEmulated()) {
+            assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
+
+            final File expected = null;
+            assertEquals(expected, mCe.getExternalCacheDir());
+            assertEquals(expected, mDe.getExternalCacheDir());
+        }
     }
 
     public void assertUnlocked() throws Exception {
@@ -239,6 +248,15 @@
         assertQuery(1, MATCH_DIRECT_BOOT_AWARE);
         assertQuery(1, MATCH_DIRECT_BOOT_UNAWARE);
         assertQuery(2, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
+
+        if (Environment.isExternalStorageEmulated()) {
+            assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+
+            final File expected = new File(
+                    "/sdcard/Android/data/com.android.cts.encryptionapp/cache");
+            assertCanonicalEquals(expected, mCe.getExternalCacheDir());
+            assertCanonicalEquals(expected, mDe.getExternalCacheDir());
+        }
     }
 
     private void assertQuery(int count, int flags) throws Exception {
@@ -257,6 +275,10 @@
         assertGet(visible, true, flags);
     }
 
+    private void assertCanonicalEquals(File expected, File actual) throws Exception {
+        assertEquals(expected.getCanonicalFile(), actual.getCanonicalFile());
+    }
+
     private ComponentName buildName(String prefix, String type) {
         return new ComponentName(TEST_PKG, TEST_PKG + "." + prefix + type);
     }
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
index 446c190..dab340b 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
@@ -17,12 +17,11 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_JAVA_LIBRARIES := \
     cts-aia-util \
     android-support-test \
-    legacy-android-test \
 	ctsdeviceutillegacy \
 	ctstestrunner
 
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index 6028ae5..8f39344 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -21,8 +21,15 @@
         android:minSdkVersion="25" />
 
     <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application
         android:label="@string/app_name">
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
index 656be27..e7e49e3 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
@@ -16,17 +16,17 @@
 
 package com.android.cts.ephemeralapp1;
 
+import static android.media.AudioFormat.CHANNEL_IN_MONO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.MediaRecorder.AudioSource.MIC;
 import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertFalse;
+import static org.hamcrest.CoreMatchers.nullValue;
 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.Manifest;
-import android.annotation.Nullable;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.ActivityNotFoundException;
@@ -41,14 +41,29 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.media.AudioRecord;
+import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestThread;
 import com.android.cts.util.TestResult;
 
 import org.junit.After;
@@ -85,6 +100,7 @@
             "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT";
 
     private BroadcastReceiver mReceiver;
+    private PhoneStateListener mPhoneStateListener;
     private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>();
 
     @Before
@@ -320,7 +336,8 @@
     public void testStartNormal() throws Exception {
         // start the normal activity
         try {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             InstrumentationRegistry
                     .getContext().startActivity(startNormalIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -330,7 +347,8 @@
 
         // start the normal activity; directed package
         try {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startNormalIntent.setPackage("com.android.cts.normalapp");
             InstrumentationRegistry
                     .getContext().startActivity(startNormalIntent, null /*options*/);
@@ -341,7 +359,8 @@
 
         // start the normal activity; directed component
         try {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startNormalIntent.setComponent(new ComponentName(
                     "com.android.cts.normalapp", "com.android.cts.normalapp.NormalActivity"));
             InstrumentationRegistry
@@ -355,7 +374,8 @@
 //       the normal app and chrome; for which there is no easy solution.
 //        // start the normal activity; using VIEW/BROWSABLE
 //        {
-//            final Intent startViewIntent = new Intent(Intent.ACTION_VIEW);
+//            final Intent startViewIntent = new Intent(Intent.ACTION_VIEW)
+//                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 //            startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
 //            startViewIntent.setData(Uri.parse("https://cts.google.com/normal"));
 //            InstrumentationRegistry.getContext().startActivity(startViewIntent, null /*options*/);
@@ -423,7 +443,8 @@
     @Test
     public void testStartExposed01() throws Exception {
         // start the explicitly exposed activity
-        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED);
+        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         InstrumentationRegistry
                 .getContext().startActivity(startExposedIntent, null /*options*/);
         final TestResult testResult = getResult();
@@ -442,7 +463,8 @@
     @Test
     public void testStartExposed02() throws Exception {
         // start the explicitly exposed activity; directed package
-        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED);
+        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startExposedIntent.setPackage("com.android.cts.normalapp");
         InstrumentationRegistry
                 .getContext().startActivity(startExposedIntent, null /*options*/);
@@ -462,7 +484,8 @@
     @Test
     public void testStartExposed03() throws Exception {
         // start the explicitly exposed activity; directed component
-        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED);
+        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startExposedIntent.setComponent(new ComponentName(
                 "com.android.cts.normalapp", "com.android.cts.normalapp.ExposedActivity"));
         InstrumentationRegistry
@@ -484,7 +507,8 @@
     public void testStartExposed04() throws Exception {
         // start the implicitly exposed activity; directed package
         try {
-            final Intent startExposedIntent = new Intent(Intent.ACTION_VIEW);
+            final Intent startExposedIntent = new Intent(Intent.ACTION_VIEW)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startExposedIntent.setPackage("com.android.cts.implicitapp");
             startExposedIntent.addCategory(Intent.CATEGORY_BROWSABLE);
             startExposedIntent.setData(Uri.parse("https://cts.google.com/implicit"));
@@ -498,7 +522,8 @@
     public void testStartExposed05() throws Exception {
         // start the implicitly exposed activity; directed component
         try {
-            final Intent startExposedIntent = new Intent(Intent.ACTION_VIEW);
+            final Intent startExposedIntent = new Intent(Intent.ACTION_VIEW)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startExposedIntent.setComponent(new ComponentName(
                     "com.android.cts.implicitapp",
                     "com.android.cts.implicitapp.ImplicitActivity"));
@@ -513,7 +538,8 @@
     @Test
     public void testStartExposed06() throws Exception {
         // start the exposed service; directed package
-        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED);
+        final Intent startExposedIntent = new Intent(ACTION_START_EXPOSED)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startExposedIntent.setPackage("com.android.cts.normalapp");
         InstrumentationRegistry.getContext().startForegroundService(startExposedIntent);
         final TestResult testResult = getResult();
@@ -642,7 +668,8 @@
     public void testStartEphemeral() throws Exception {
         // start the ephemeral activity
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             InstrumentationRegistry
                     .getContext().startActivity(startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -658,7 +685,8 @@
 
         // start the ephemeral activity; directed package
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
             InstrumentationRegistry
                     .getContext().startActivity(startEphemeralIntent, null /*options*/);
@@ -675,7 +703,8 @@
 
         // start the ephemeral activity; directed component
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setComponent(
                     new ComponentName("com.android.cts.ephemeralapp1",
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
@@ -694,7 +723,8 @@
 
         // start a private ephemeral activity
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             InstrumentationRegistry
                     .getContext().startActivity(startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -710,7 +740,8 @@
 
         // start a private ephemeral activity; directed package
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
             InstrumentationRegistry
                     .getContext().startActivity(startEphemeralIntent, null /*options*/);
@@ -727,7 +758,8 @@
 
         // start a private ephemeral activity; directed component
         {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_PRIVATE)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setComponent(
                     new ComponentName("com.android.cts.ephemeralapp1",
                             "com.android.cts.ephemeralapp1.EphemeralActivity2"));
@@ -746,7 +778,8 @@
 
         // start a private ephemeral activity; directed component
         {
-            final Intent startEphemeralIntent = new Intent();
+            final Intent startEphemeralIntent = new Intent()
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setComponent(
                     new ComponentName("com.android.cts.ephemeralapp1",
                             "com.android.cts.ephemeralapp1.EphemeralActivity3"));
@@ -959,6 +992,28 @@
     }
 
     @Test
+    public void testGetInstaller01() throws Exception {
+        // test we can read our own installer
+        final String installerPackageName = InstrumentationRegistry.getContext().getPackageManager()
+                .getInstallerPackageName("com.android.cts.ephemeralapp1");
+        assertThat(installerPackageName, is("com.android.cts.normalapp"));
+    }
+    @Test
+    public void testGetInstaller02() throws Exception {
+        // test we can read someone else's installer if they're exposed to instant applications
+        final String installerPackageName = InstrumentationRegistry.getContext().getPackageManager()
+                .getInstallerPackageName("com.android.cts.normalapp");
+        assertThat(installerPackageName, is("com.android.cts.normalapp"));
+    }
+    @Test
+    public void testGetInstaller03() throws Exception {
+        // test we can't read installer if they're not exposed to instant applications
+        final String installerPackageName = InstrumentationRegistry.getContext().getPackageManager()
+                .getInstallerPackageName("com.android.cts.unexposedapp");
+        assertThat(installerPackageName, is(nullValue()));
+    }
+
+    @Test
     public void testStartForegroundService() throws Exception {
         final Context context = InstrumentationRegistry.getContext();
         final Intent intent = new Intent(context, SomeService.class);
@@ -994,6 +1049,137 @@
         latch2.await(5, TimeUnit.SECONDS);
     }
 
+    @Test
+    public void testRecordAudioPermission() throws Throwable {
+        final AudioRecord record =
+                new AudioRecord(MIC, 8000, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, 4096);
+        try {
+            assertThat("audio record not initialized",
+                    record.getState(), is(AudioRecord.STATE_INITIALIZED));
+        } finally {
+            record.release();
+        }
+    }
+
+    @Test
+    public void testReadPhoneNumbersPermission() throws Throwable {
+        final Context context = InstrumentationRegistry.getContext();
+        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        try {
+            final TelephonyManager telephonyManager =
+                    (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            final String nmbr = telephonyManager.getLine1Number();
+        } catch (SecurityException e) {
+            fail("Permission not granted");
+        }
+    }
+
+    @Test
+    public void testAccessCoarseLocationPermission() throws Throwable {
+        final Context context = InstrumentationRegistry.getContext();
+        final ConnectivityManager mCm =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            return;
+        }
+
+        final TelephonyManager mTelephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        // getCellLocation should never return null, but that is allowed if the cell network type
+        // is LTE (since there is no LteCellLocation class)
+        if (mTelephonyManager.getNetworkType() != TelephonyManager.NETWORK_TYPE_LTE) {
+            assertThat(mTelephonyManager.getCellLocation(), is(notNullValue()));
+        }
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestThread t = new TestThread(() -> {
+            Looper.prepare();
+            mPhoneStateListener = new PhoneStateListener() {
+                @Override
+                public void onCellLocationChanged(CellLocation location) {
+                    latch.countDown();
+                }
+            };
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+            Looper.loop();
+        });
+        t.start();
+
+        try {
+            CellLocation.requestLocationUpdate();
+            assertThat(latch.await(1000, TimeUnit.MILLISECONDS), is(true));
+        } finally {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        }
+        t.checkException();
+    }
+
+    @Test
+    public void testCameraPermission() throws Throwable {
+        final Context context = InstrumentationRegistry.getContext();
+        final CameraManager manager =
+                (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        final String[] cameraIds = manager.getCameraIdList();
+        if (cameraIds.length == 0) {
+            return;
+        }
+        final CountDownLatch latch = new CountDownLatch(1);
+        final HandlerThread backgroundThread = new HandlerThread("camera_bg");
+        backgroundThread.start();
+        final CameraDevice.StateCallback callback = new CameraDevice.StateCallback() {
+            @Override
+            public void onOpened(CameraDevice camera) {
+                latch.countDown();
+                camera.close();
+            }
+            @Override
+            public void onDisconnected(CameraDevice camera) {
+                camera.close();
+            }
+            @Override
+            public void onError(CameraDevice camera, int error) {
+                camera.close();
+            }
+        };
+        manager.openCamera(cameraIds[0], callback, new Handler(backgroundThread.getLooper()));
+        assertThat(latch.await(1000, TimeUnit.MILLISECONDS), is(true));
+    }
+
+    @Test
+    public void testInternetPermission() throws Throwable {
+        final ConnectivityManager manager = (ConnectivityManager) InstrumentationRegistry.getContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        manager.reportNetworkConnectivity(null, false);
+    }
+
+    @Test
+    public void testVibratePermission() throws Throwable {
+        final Vibrator vibrator = (Vibrator) InstrumentationRegistry.getContext()
+                .getSystemService(Context.VIBRATOR_SERVICE);
+        final VibrationEffect effect =
+                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+        vibrator.vibrate(effect);
+    }
+
+    @Test
+    public void testWakeLockPermission() throws Throwable {
+        WakeLock wakeLock = null;
+        try {
+            final PowerManager powerManager = (PowerManager) InstrumentationRegistry.getContext()
+                    .getSystemService(Context.POWER_SERVICE);
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "test");
+            wakeLock.acquire();
+        }
+        finally {
+            if (wakeLock != null &&  wakeLock.isHeld()) {
+                wakeLock.release();
+            }
+        }
+    }
+
     private TestResult getResult() {
         final TestResult result;
         try {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity.java
index bc29af9..270df87 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity.java
@@ -37,6 +37,7 @@
         TestResult.getBuilder()
                 .setPackageName("com.android.cts.ephemeralapp1")
                 .setComponentName("EphemeralActivity")
+                .setIntent(getIntent())
                 .setStatus("PASS")
                 .build()
                 .broadcast(this);
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity2.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity2.java
index 952b2f66..a0a170d 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity2.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity2.java
@@ -35,6 +35,7 @@
         TestResult.getBuilder()
                 .setPackageName("com.android.cts.ephemeralapp1")
                 .setComponentName("EphemeralActivity2")
+                .setIntent(getIntent())
                 .setStatus("PASS")
                 .build()
                 .broadcast(this);
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity3.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity3.java
index 28734dd..1267577 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity3.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralActivity3.java
@@ -35,6 +35,7 @@
         TestResult.getBuilder()
                 .setPackageName("com.android.cts.ephemeralapp1")
                 .setComponentName("EphemeralActivity3")
+                .setIntent(getIntent())
                 .setStatus("PASS")
                 .build()
                 .broadcast(this);
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralService.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralService.java
index d649994..86dedc6 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralService.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/EphemeralService.java
@@ -41,6 +41,7 @@
                 .setPackageName("com.android.cts.ephemeralapp1")
                 .setComponentName("EphemeralService")
                 .setMethodName("onBind")
+                .setIntent(intent)
                 .setStatus("PASS")
                 .build()
                 .broadcast(this);
@@ -53,6 +54,7 @@
                 .setPackageName("com.android.cts.ephemeralapp1")
                 .setComponentName("EphemeralService")
                 .setMethodName("onStartCommand")
+                .setIntent(intent)
                 .setStatus("PASS")
                 .build()
                 .broadcast(this);
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
index dc7fcc8..d2c3667 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
@@ -20,8 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_JAVA_LIBRARIES := \
     cts-aia-util \
-    android-support-test \
-    legacy-android-test
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
index 4c27dba..54ab0d7 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
@@ -19,6 +19,7 @@
     <uses-sdk
         android:minSdkVersion="24" />
 
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <application
         android:label="@string/app_name">
         <uses-library android:name="android.test.runner" />
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 cd5ac0e..60ba5ad 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
@@ -16,6 +16,7 @@
 
 package com.android.cts.normalapp;
 
+import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
@@ -25,16 +26,16 @@
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
+import android.provider.Settings.Secure;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
 
 import com.android.cts.util.TestResult;
 
@@ -293,7 +294,8 @@
     public void testStartNormal() throws Exception {
         // start the normal activity
         {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
             final TestResult testResult = getResult();
             assertThat(testResult.getPackageName(),
@@ -306,7 +308,8 @@
 
         // start the normal activity; directed package
         {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startNormalIntent.setPackage("com.android.cts.normalapp");
             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -320,7 +323,8 @@
 
         // start the normal activity; directed component
         {
-            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY);
+            final Intent startNormalIntent = new Intent(ACTION_START_NORMAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startNormalIntent.setComponent(new ComponentName(
                     "com.android.cts.normalapp", "com.android.cts.normalapp.NormalActivity"));
             InstrumentationRegistry.getContext().startActivity(startNormalIntent, null /*options*/);
@@ -354,9 +358,10 @@
 
     @Test
     public void testStartEphemeral() throws Exception {
-        // start the ephemeral activity
+        // start the ephemeral activity; no EXTERNAL flag
         try {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -364,9 +369,22 @@
         } catch (ActivityNotFoundException expected) {
         }
 
-        // start the ephemeral activity; directed package
+        // start the ephemeral activity; EXTERNAL flag
+        {
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
+            InstrumentationRegistry.getContext().startActivity(
+                    startEphemeralIntent, null /*options*/);
+            final TestResult testResult = getResult();
+            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
+        }
+
+
+        // start the ephemeral activity; directed package, no EXTERNAL flag
         try {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
@@ -375,9 +393,37 @@
         } catch (ActivityNotFoundException expected) {
         }
 
+        // start the ephemeral activity; directed package, includes EXTERNAL flag
+        {
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
+            startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
+            InstrumentationRegistry.getContext().startActivity(
+                    startEphemeralIntent, null /*options*/);
+            final TestResult testResult = getResult();
+            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
+        }
+
         // start the ephemeral activity; directed component
         try {
-            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY);
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startEphemeralIntent.setComponent(
+                    new ComponentName("com.android.cts.ephemeralapp1",
+                            "com.android.cts.ephemeralapp1.EphemeralActivity"));
+            InstrumentationRegistry.getContext().startActivity(
+                    startEphemeralIntent, null /*options*/);
+            final TestResult testResult = getResult();
+            fail();
+        } catch (ActivityNotFoundException expected) {
+        }
+
+
+        // start the ephemeral activity; directed component, includes EXTERNAL flag
+        try {
+            final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
             startEphemeralIntent.setComponent(
                     new ComponentName("com.android.cts.ephemeralapp1",
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
@@ -390,7 +436,8 @@
 
         // start the ephemeral activity; using VIEW/BROWSABLE
         {
-            final Intent startViewIntent = new Intent(Intent.ACTION_VIEW);
+            final Intent startViewIntent = new Intent(Intent.ACTION_VIEW)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
             startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral"));
             InstrumentationRegistry.getContext().startActivity(
@@ -398,13 +445,51 @@
             final TestResult testResult = getResult();
             assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
             assertThat("EphemeralActivity", is(testResult.getComponentName()));
+            assertThat(Intent.ACTION_VIEW, is(testResult.getIntent().getAction()));
+            assertThat(testResult.getIntent().getCategories(), hasItems(Intent.CATEGORY_BROWSABLE));
+            assertThat("https://cts.google.com/ephemeral",
+                    is(testResult.getIntent().getData().toString()));
+        }
+
+        final ContentResolver contentResolver =
+                InstrumentationRegistry.getContext().getContentResolver();
+        final int originalSetting = Secure.getInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 1);
+        Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 0);
+        try {
+            // start the ephemeral activity; using VIEW/BROWSABLE with setting disabled
+            try {
+                final Intent startViewIntent = new Intent(Intent.ACTION_VIEW)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
+                startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral"));
+                InstrumentationRegistry.getContext().startActivity(
+                        startViewIntent, null /*options*/);
+                final TestResult testResult = getResult();
+                fail();
+            } catch (TestResultNotFoundException expected) {
+                // we shouldn't resolve to our instant app
+            }
+
+            // start the ephemeral activity; EXTERNAL flag with setting disabled
+            {
+                final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
+                InstrumentationRegistry.getContext().startActivity(
+                        startEphemeralIntent, null /*options*/);
+                final TestResult testResult = getResult();
+                assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+                assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
+            }
+
+        } finally {
+            Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, originalSetting);
         }
 
         // connect to the instant app provider
         {
             final String provider = "content://com.android.cts.ephemeralapp1.provider/table";
-            final Cursor testCursor = InstrumentationRegistry
-                    .getContext().getContentResolver().query(
+            final Cursor testCursor = contentResolver.query(
                             Uri.parse(provider),
                             null /*projection*/,
                             null /*selection*/,
@@ -422,11 +507,18 @@
             throw new RuntimeException(e);
         }
         if (result == null) {
-            throw new IllegalStateException("Activity didn't receive a Result in 5 seconds");
+            throw new TestResultNotFoundException(
+                    "Activity didn't receive a Result in 5 seconds");
         }
         return result;
     }
 
+    private static class TestResultNotFoundException extends IllegalStateException {
+        public TestResultNotFoundException(String description) {
+            super(description);
+        }
+    }
+
     private static class ActivityBroadcastReceiver extends BroadcastReceiver {
         private final SynchronousQueue<TestResult> mQueue;
         public ActivityBroadcastReceiver(SynchronousQueue<TestResult> queue) {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
index 1f52857..28560d3 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
@@ -20,8 +20,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_JAVA_LIBRARIES := \
     cts-aia-util \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
index d7774c1..64f02cb 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
@@ -19,8 +19,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/src/com/android/cts/util/TestResult.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/src/com/android/cts/util/TestResult.java
index 4dacec6..0f52e80 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/src/com/android/cts/util/TestResult.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/src/com/android/cts/util/TestResult.java
@@ -20,9 +20,6 @@
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Parcelable.Creator;
-
-import java.util.ArrayList;
 
 public class TestResult implements Parcelable {
     public static final String EXTRA_TEST_RESULT =
@@ -35,6 +32,7 @@
     private final String mMethodName;
     private final String mStatus;
     private final String mException;
+    private final Intent mIntent;
     private final boolean mInstantAppPackageInfoExposed;
 
     public String getPackageName() {
@@ -61,6 +59,10 @@
         return mInstantAppPackageInfoExposed;
     }
 
+    public Intent getIntent() {
+        return mIntent;
+    }
+
     public static Builder getBuilder() {
         return new Builder();
     }
@@ -74,12 +76,14 @@
     }
 
     private TestResult(String packageName, String componentName, String methodName,
-            String status, String exception, boolean ephemeralPackageInfoExposed) {
+            String status, String exception, Intent intent,
+            boolean ephemeralPackageInfoExposed) {
         mPackageName = packageName;
         mComponentName = componentName;
         mMethodName = methodName;
         mStatus = status;
         mException = exception;
+        mIntent = intent;
         mInstantAppPackageInfoExposed = ephemeralPackageInfoExposed;
     }
 
@@ -95,6 +99,7 @@
         dest.writeString(mMethodName);
         dest.writeString(mStatus);
         dest.writeString(mException);
+        dest.writeParcelable(mIntent, 0 /* flags */);
         dest.writeInt(mInstantAppPackageInfoExposed ? 1 : 0);
     }
 
@@ -113,6 +118,7 @@
         mMethodName = source.readString();
         mStatus = source.readString();
         mException = source.readString();
+        mIntent = source.readParcelable(Object.class.getClassLoader());
         mInstantAppPackageInfoExposed = source.readInt() != 0;
     }
 
@@ -122,6 +128,7 @@
         private String methodName;
         private String status;
         private String exception;
+        private Intent intent;
         private boolean instantAppPackageInfoExposed;
 
         private Builder() {
@@ -150,9 +157,13 @@
             instantAppPackageInfoExposed = _instantAppPackageInfoExposed;
             return this;
         }
+        public Builder setIntent(Intent _intent) {
+            intent = _intent;
+            return this;
+        }
         public TestResult build() {
             return new TestResult(packageName, componentName, methodName,
-                    status, exception, instantAppPackageInfoExposed);
+                    status, exception, intent, instantAppPackageInfoExposed);
         }
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
index 47d468e..2fcbc20 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_PACKAGE_NAME := CtsExternalStorageApp
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
index a48abbf..b99597b 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
@@ -21,7 +21,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := CtsInstrumentationAppDiffCert
 
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk
new file mode 100644
index 0000000..6047ca5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk
@@ -0,0 +1,21 @@
+#
+# 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)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk
new file mode 100644
index 0000000..db83bb0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk
@@ -0,0 +1,37 @@
+# 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_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000000000ffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
new file mode 100644
index 0000000..2a63cd7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.majorversion"
+        android:versionCodeMajor="0x00000000" android:versionCode="0x0000ffff">
+
+    <application/>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..4fe9fed
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * 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.majorversion;
+
+public class VersionConstants {
+    public static final long PACKAGE_VERSION = 0x000000000000ffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk
new file mode 100644
index 0000000..dd32f59
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk
@@ -0,0 +1,37 @@
+# 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_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion00000000ffffffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
new file mode 100644
index 0000000..934deec
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.majorversion"
+        android:versionCodeMajor="0x00000000" android:versionCode="0xffffffff">
+
+    <application/>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..4c9a62b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * 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.majorversion;
+
+public class VersionConstants {
+    public static final long PACKAGE_VERSION = 0x00000000ffffffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk
new file mode 100644
index 0000000..d534b7b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk
@@ -0,0 +1,37 @@
+# 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_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000ff00000000
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
new file mode 100644
index 0000000..c7b9dd0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.majorversion"
+        android:versionCodeMajor="0x000000ff" android:versionCode="0x00000000">
+
+    <application/>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..6a10139
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * 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.majorversion;
+
+public class VersionConstants {
+    public static final long PACKAGE_VERSION = 0x000000ff00000000L;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk
new file mode 100644
index 0000000..72c9914
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk
@@ -0,0 +1,37 @@
+# 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_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000ffffffffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
new file mode 100644
index 0000000..91d4e39
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.majorversion"
+        android:versionCodeMajor="0x000000ff" android:versionCode="0xffffffff">
+
+    <application/>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..dead996
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * 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.majorversion;
+
+public class VersionConstants {
+    public static final long PACKAGE_VERSION = 0x000000ffffffffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java
new file mode 100644
index 0000000..e69e14b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.majorversion;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+public class VersionTest {
+    @Test
+    public void testCheckVersion() throws Exception {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        PackageInfo pi = pm.getPackageInfo(InstrumentationRegistry.getContext().getPackageName(),
+                0);
+        assertEquals("com.android.cts.majorversion",
+                InstrumentationRegistry.getContext().getPackageName());
+        assertEquals(VersionConstants.PACKAGE_VERSION, pi.getLongVersionCode());
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
index 0744834..e08ac6f 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk
new file mode 100644
index 0000000..67fe9f4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/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_STATIC_JAVA_LIBRARIES := android-support-test
+# 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_SDK_VERSION := 23
+LOCAL_PACKAGE_NAME := CtsOrderedActivityApp
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
new file mode 100644
index 0000000..383f000
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.orderedactivity"
+        android:versionCode="10"
+        android:versionName="1.0">
+    <application android:label="@string/app_name">
+        <!-- Activities used for queries -->
+        <activity android:name=".OrderActivity2">
+            <intent-filter
+                    android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts/package" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".OrderActivity1">
+            <intent-filter
+                    android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:path="/cts/packageresolution" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".OrderActivityDefault">
+            <intent-filter>
+                <!-- default order -->
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".OrderActivity3">
+            <intent-filter
+                    android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts" />
+            </intent-filter>
+        </activity>
+
+        <!-- Services used for queries -->
+        <service android:name=".OrderServiceDefault">
+            <intent-filter>
+                <!-- default order -->
+              <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com" />
+            </intent-filter>
+        </service>
+        <service android:name=".OrderService2">
+            <intent-filter
+                    android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts/package" />
+            </intent-filter>
+        </service>
+        <service android:name=".OrderService3">
+            <intent-filter
+                    android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts" />
+            </intent-filter>
+        </service>
+        <service android:name=".OrderService1">
+            <intent-filter
+                    android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:path="/cts/packageresolution" />
+            </intent-filter>
+        </service>
+
+        <!-- Broadcast receivers used for queries -->
+        <receiver android:name=".OrderReceiver3">
+            <intent-filter
+                    android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".OrderReceiverDefault">
+            <intent-filter>
+                <!-- default order -->
+              <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".OrderReceiver1">
+            <intent-filter
+                    android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:path="/cts/packageresolution" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".OrderReceiver2">
+            <intent-filter
+                    android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED" />
+                <data android:scheme="https"
+                      android:host="www.google.com"
+                      android:pathPrefix="/cts/package" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.appsecurity.cts.orderedactivity" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/res/values/strings.xml
new file mode 100644
index 0000000..70d5a95
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Tiny App for CTS</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/src/android/appsecurity/cts/orderedactivity/PackageResolutionTest.java b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/src/android/appsecurity/cts/orderedactivity/PackageResolutionTest.java
new file mode 100644
index 0000000..e7ff989
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/src/android/appsecurity/cts/orderedactivity/PackageResolutionTest.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.appsecurity.cts.orderedactivity;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageResolutionTest {
+    @Test
+    public void queryActivityOrdered() throws Exception {
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        final Intent intent = new Intent("android.cts.intent.action.ORDERED");
+        intent.setData(Uri.parse("https://www.google.com/cts/packageresolution"));
+        final List<ResolveInfo> resolve = pm.queryIntentActivities(intent, 0 /*flags*/);
+
+        assertNotNull(resolve);
+        assertEquals(4, resolve.size());
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderActivity3",
+                resolve.get(0).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderActivity2",
+                resolve.get(1).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderActivity1",
+                resolve.get(2).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderActivityDefault",
+                resolve.get(3).activityInfo.name);
+    }
+
+    @Test
+    public void queryServiceOrdered() throws Exception {
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        final Intent intent = new Intent("android.cts.intent.action.ORDERED");
+        intent.setData(Uri.parse("https://www.google.com/cts/packageresolution"));
+        final List<ResolveInfo> resolve = pm.queryIntentServices(intent, 0 /*flags*/);
+        
+        assertNotNull(resolve);
+        assertEquals(4, resolve.size());
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderService3",
+                resolve.get(0).serviceInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderService2",
+                resolve.get(1).serviceInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderService1",
+                resolve.get(2).serviceInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderServiceDefault",
+                resolve.get(3).serviceInfo.name);
+    }
+
+    @Test
+    public void queryReceiverOrdered() throws Exception {
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        final Intent intent = new Intent("android.cts.intent.action.ORDERED");
+        intent.setData(Uri.parse("https://www.google.com/cts/packageresolution"));
+        final List<ResolveInfo> resolve = pm.queryBroadcastReceivers(intent, 0 /*flags*/);
+        
+        assertNotNull(resolve);
+        assertEquals(4, resolve.size());
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderReceiver3",
+                resolve.get(0).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderReceiver2",
+                resolve.get(1).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderReceiver1",
+                resolve.get(2).activityInfo.name);
+        assertEquals("android.appsecurity.cts.orderedactivity.OrderReceiverDefault",
+                resolve.get(3).activityInfo.name);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
index ad7a640..a46f214 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -31,10 +31,9 @@
     <permission android:name="com.android.cts.permissionNormal" />
 
     <application>
-        <receiver android:name="GrantUriPermission" android:exported="true">
-        </receiver>
-        <receiver android:name="SetInstallerPackage" android:exported="true">
-        </receiver>
+        <provider android:name="UtilsProvider"
+                android:authorities="com.android.cts.permissiondeclareapp"
+                android:exported="true" />
 
         <!-- Need a way for another app to try to access the permission. So create a content
         provider which is enforced by the permission -->
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
deleted file mode 100644
index 46e1dff..0000000
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.permissiondeclareapp;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriPermission;
-import android.net.Uri;
-import android.util.Log;
-
-import java.util.List;
-
-public class GrantUriPermission extends BroadcastReceiver {
-    public static final String ACTION_GRANT_URI = "grantUri";
-    public static final String ACTION_REVOKE_URI = "revokeUri";
-    public static final String ACTION_START_ACTIVITY = "startActivity";
-    public static final String ACTION_START_SERVICE = "startService";
-    public static final String ACTION_VERIFY_OUTGOING_PERSISTED = "verifyOutgoingPersisted";
-
-    public static final String EXTRA_PACKAGE_NAME = "packageName";
-    public static final String EXTRA_INTENT = Intent.EXTRA_INTENT;
-    public static final String EXTRA_URI = "uri";
-    public static final String EXTRA_MODE = "mode";
-
-    public static final int SUCCESS = 101;
-    public static final int FAILURE = 100;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        try {
-            final String action = intent.getAction();
-            if (ACTION_GRANT_URI.equals(action)) {
-                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
-                context.grantUriPermission(intent.getStringExtra(EXTRA_PACKAGE_NAME), uri,
-                        intent.getIntExtra(EXTRA_MODE, 0));
-
-            } else if (ACTION_REVOKE_URI.equals(action)) {
-                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
-                context.revokeUriPermission(uri, intent.getIntExtra(EXTRA_MODE, 0));
-
-            } else if (ACTION_START_ACTIVITY.equals(action)) {
-                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
-                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                context.startActivity(newIntent);
-
-            } else if (ACTION_START_SERVICE.equals(action)) {
-                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
-                context.startService(newIntent);
-
-            } else if (ACTION_VERIFY_OUTGOING_PERSISTED.equals(action)) {
-                verifyOutgoingPersisted(context, intent);
-            }
-
-            if (isOrderedBroadcast()) {
-                setResultCode(SUCCESS);
-            }
-        } catch (SecurityException e) {
-            Log.i("GrantUriPermission", "Security exception", e);
-            if (isOrderedBroadcast()) {
-                setResultCode(FAILURE);
-            }
-        }
-    }
-
-    private void verifyOutgoingPersisted(Context context, Intent intent) {
-        final Uri uri = intent.getParcelableExtra(EXTRA_URI);
-        final List<UriPermission> perms = context.getContentResolver()
-                .getOutgoingPersistedUriPermissions();
-        if (uri != null) {
-            // Should have a single persisted perm
-            if (perms.size() != 1) {
-                throw new SecurityException("Missing grant");
-            }
-            final UriPermission perm = perms.get(0);
-            if (!perm.getUri().equals(uri)) {
-                throw new SecurityException(
-                        "Expected " + uri + " but found " + perm.getUri());
-            }
-        } else {
-            // Should have zero persisted perms
-            if (perms.size() != 0) {
-                throw new SecurityException("Unexpected grant");
-            }
-        }
-    }
-}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SetInstallerPackage.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SetInstallerPackage.java
deleted file mode 100644
index ea06d8c..0000000
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SetInstallerPackage.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.permissiondeclareapp;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class SetInstallerPackage extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String targetPackage = intent.getStringExtra("target");
-        String installerPackage = intent.getStringExtra("installer");
-        try {
-            context.getPackageManager().setInstallerPackageName(targetPackage, installerPackage);
-            if (isOrderedBroadcast()) {
-                setResultCode(101);
-            }
-        } catch (SecurityException e) {
-            Log.i("SetInstallerPackage", "Security exception", e);
-            if (isOrderedBroadcast()) {
-                setResultCode(100);
-            }
-        }
-    }
-}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
new file mode 100644
index 0000000..ca23d8f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
@@ -0,0 +1,149 @@
+/*
+ * 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.cts.permissiondeclareapp;
+
+import android.content.ClipboardManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+
+import java.util.List;
+
+public class UtilsProvider extends ContentProvider {
+    public static final Uri URI = Uri.parse("content://com.android.cts.permissiondeclareapp/");
+
+    public static final String ACTION_GRANT_URI = "grantUri";
+    public static final String ACTION_REVOKE_URI = "revokeUri";
+    public static final String ACTION_START_ACTIVITY = "startActivity";
+    public static final String ACTION_START_SERVICE = "startService";
+    public static final String ACTION_VERIFY_OUTGOING_PERSISTED = "verifyOutgoingPersisted";
+    public static final String ACTION_SET_PRIMARY_CLIP = "setPrimaryClip";
+    public static final String ACTION_CLEAR_PRIMARY_CLIP = "clearPrimaryClip";
+    public static final String ACTION_SET_INSTALLER_PACKAGE_NAME = "setInstallerPackageName";
+
+    public static final String EXTRA_PACKAGE_NAME = "packageName";
+    public static final String EXTRA_INSTALLER_PACKAGE_NAME = "installerPackageName";
+    public static final String EXTRA_INTENT = Intent.EXTRA_INTENT;
+    public static final String EXTRA_URI = "uri";
+    public static final String EXTRA_MODE = "mode";
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        final Context context = getContext();
+        final Intent intent = extras.getParcelable(Intent.EXTRA_INTENT);
+        final String action = intent.getAction();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (ACTION_GRANT_URI.equals(action)) {
+                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+                context.grantUriPermission(intent.getStringExtra(EXTRA_PACKAGE_NAME), uri,
+                        intent.getIntExtra(EXTRA_MODE, 0));
+
+            } else if (ACTION_REVOKE_URI.equals(action)) {
+                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+                context.revokeUriPermission(uri, intent.getIntExtra(EXTRA_MODE, 0));
+
+            } else if (ACTION_START_ACTIVITY.equals(action)) {
+                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(newIntent);
+
+            } else if (ACTION_START_SERVICE.equals(action)) {
+                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
+                context.startService(newIntent);
+
+            } else if (ACTION_VERIFY_OUTGOING_PERSISTED.equals(action)) {
+                verifyOutgoingPersisted(context, intent);
+
+            } else if (ACTION_SET_PRIMARY_CLIP.equals(action)) {
+                context.getSystemService(ClipboardManager.class)
+                        .setPrimaryClip(intent.getClipData());
+
+            } else if (ACTION_CLEAR_PRIMARY_CLIP.equals(action)) {
+                context.getSystemService(ClipboardManager.class).clearPrimaryClip();
+
+            } else if (ACTION_SET_INSTALLER_PACKAGE_NAME.equals(action)) {
+                context.getPackageManager().setInstallerPackageName(
+                        intent.getStringExtra(EXTRA_PACKAGE_NAME),
+                        intent.getStringExtra(EXTRA_INSTALLER_PACKAGE_NAME));
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return null;
+    }
+
+    private void verifyOutgoingPersisted(Context context, Intent intent) {
+        final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+        final List<UriPermission> perms = context.getContentResolver()
+                .getOutgoingPersistedUriPermissions();
+        if (uri != null) {
+            // Should have a single persisted perm
+            if (perms.size() != 1) {
+                throw new SecurityException("Missing grant");
+            }
+            final UriPermission perm = perms.get(0);
+            if (!perm.getUri().equals(uri)) {
+                throw new SecurityException(
+                        "Expected " + uri + " but found " + perm.getUri());
+            }
+        } else {
+            // Should have zero persisted perms
+            if (perms.size() != 0) {
+                throw new SecurityException("Unexpected grant");
+            }
+        }
+    }
+
+    @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/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
index 7fa2148..a8d659b 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
@@ -25,8 +25,6 @@
     compatibility-device-util \
     ctstestrunner \
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsPermissionPolicyTest25
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
index 17a1716..d998a77 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
@@ -24,6 +24,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 LOCAL_PROGUARD_ENABLED := disabled
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
index 4f828ad..51699dd 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index 9341949..a0a8080 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -19,12 +19,14 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsSplitApp
+LOCAL_SDK_VERSION := current
 LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 v7 fr de
 
 # Tag this module as a cts test artifact
@@ -48,7 +50,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -75,7 +79,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -101,7 +107,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index d9dceb8..e9e3625 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,7 +15,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp">
+    package="com.android.cts.splitapp">
+    <!-- TODO(b/73365611) Remove targetSdkVersion once EncryptionApp tests
+         are fixed to no longer access SplitApp's data by path. -->
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
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 7a693e8..4cfd7d0 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
@@ -322,6 +322,7 @@
         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().startActivity(i);
         assertTrue(cv.block(2000L));
         getContext().unregisterReceiver(r);
@@ -347,6 +348,7 @@
         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().startActivity(i);
         assertTrue(cv.block(2000L));
         getContext().unregisterReceiver(r);
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 9d0dec6..23e4c9f 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
@@ -16,6 +16,8 @@
 
 package com.android.cts.storageapp;
 
+import static android.os.storage.StorageManager.UUID_DEFAULT;
+
 import static com.android.cts.storageapp.Utils.CACHE_ALL;
 import static com.android.cts.storageapp.Utils.CACHE_EXT;
 import static com.android.cts.storageapp.Utils.CACHE_INT;
@@ -26,7 +28,6 @@
 import static com.android.cts.storageapp.Utils.assertMostlyEquals;
 import static com.android.cts.storageapp.Utils.getSizeManual;
 import static com.android.cts.storageapp.Utils.makeUniqueFile;
-import static com.android.cts.storageapp.Utils.shouldHaveQuota;
 import static com.android.cts.storageapp.Utils.useSpace;
 
 import android.app.usage.StorageStats;
@@ -39,7 +40,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.system.Os;
 import android.test.InstrumentationTestCase;
 
 import java.io.File;
@@ -59,17 +59,10 @@
     }
 
     public void testFullDisk() throws Exception {
-        if (shouldHaveQuota(Os.uname())) {
+        final StorageStatsManager stats = getContext()
+                .getSystemService(StorageStatsManager.class);
+        if (stats.isReservedSupported(UUID_DEFAULT)) {
             final File dataDir = getContext().getDataDir();
-
-            // Pre-flight to see if we have enough disk space to test with
-            final long total = dataDir.getTotalSpace();
-            final long free = dataDir.getFreeSpace();
-            final long required = ((total * 9) / 10) + MB_IN_BYTES;
-            if (free < required) {
-                fail("Skipping full disk test; only found " + free + " free out of " + total);
-            }
-
             Hoarder.doBlocks(dataDir, true);
         } else {
             fail("Skipping full disk test due to missing quota support");
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 6908ad8..08dc7ad 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
@@ -169,30 +169,6 @@
         return success;
     }
 
-    public static boolean shouldHaveQuota(StructUtsname uname) throws Exception {
-        try (BufferedReader br = new BufferedReader(new FileReader("/proc/mounts"))) {
-            String line;
-            while ((line = br.readLine()) != null) {
-                final String[] fields = line.split(" ");
-                final String target = fields[1];
-                final String format = fields[2];
-
-                if (target.equals("/data") && !format.equals("ext4")) {
-                    Log.d(TAG, "Assuming no quota support because /data is " + format);
-                    return false;
-                }
-            }
-        }
-
-        final Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)").matcher(uname.release);
-        if (!matcher.find()) {
-            throw new IllegalStateException("Failed to parse version: " + uname.release);
-        }
-        final int major = Integer.parseInt(matcher.group(1));
-        final int minor = Integer.parseInt(matcher.group(2));
-        return (major > 3 || (major == 3 && minor >= 18));
-    }
-
     public static void logCommand(String... cmd) throws Exception {
         final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
 
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
index f3d7d35..ebbc892 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
@@ -17,8 +17,10 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../StorageApp/src/)
 
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
index 5f85459..3a5462e 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
@@ -17,8 +17,10 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../StorageApp/src/)
 
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
index b5c30fb..16d78d7 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := test_current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator legacy-android-test
+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) \
 	$(call all-java-files-under, ../StorageApp/src)
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index 9b69057..7b1abec 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -30,7 +30,6 @@
 import static com.android.cts.storageapp.Utils.getSizeManual;
 import static com.android.cts.storageapp.Utils.logCommand;
 import static com.android.cts.storageapp.Utils.makeUniqueFile;
-import static com.android.cts.storageapp.Utils.shouldHaveQuota;
 import static com.android.cts.storageapp.Utils.useFallocate;
 import static com.android.cts.storageapp.Utils.useSpace;
 import static com.android.cts.storageapp.Utils.useWrite;
@@ -46,13 +45,12 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.support.test.uiautomator.UiDevice;
-import android.system.Os;
-import android.system.StructUtsname;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 import android.util.MutableLong;
@@ -76,19 +74,22 @@
     }
 
     /**
-     * Require that quota support be fully enabled on kernel 3.18 or newer. This
-     * test verifies that both kernel options and the fstab 'quota' option are
-     * enabled.
+     * Require that quota support be fully enabled on devices that first ship
+     * with P. This test verifies that both kernel options and the fstab 'quota'
+     * option are enabled.
      */
-    public void testVerifyQuota() throws Exception {
-        final StructUtsname uname = Os.uname();
-        if (shouldHaveQuota(uname)) {
+    public void testVerify() throws Exception {
+        if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.P) {
             final StorageStatsManager stats = getContext()
                     .getSystemService(StorageStatsManager.class);
-            assertTrue("You're running kernel 3.18 or newer (" + uname.release + ") which "
-                    + "means that CONFIG_QUOTA, CONFIG_QFMT_V2, CONFIG_QUOTACTL and the "
-                    + "'quota' fstab option on /data are required",
+            assertTrue("Devices that first ship with P or newer must enable quotas to "
+                    + "support StorageStatsManager APIs. You may need to enable the "
+                    + "CONFIG_QUOTA, CONFIG_QFMT_V2, CONFIG_QUOTACTL kernel options "
+                    + "and add the 'quota' fstab option on /data.",
                     stats.isQuotaSupported(UUID_DEFAULT));
+            assertTrue("Devices that first ship with P or newer must enable resgid to "
+                    + "preserve system stability in the face of abusive apps.",
+                    stats.isReservedSupported(UUID_DEFAULT));
         }
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
index d050de2..7a62f09 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
@@ -25,7 +25,7 @@
     ctstestrunner \
     ub-uiautomator
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java \
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
index f91f401..58ee1c7 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
@@ -25,7 +25,7 @@
     ctstestrunner \
     ub-uiautomator
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
index 71eadaa..9d25826 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
@@ -66,6 +66,8 @@
     <uses-permission android:name="android.permission.BODY_SENSORS"/>
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
     </application>
 
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 d0ca617..3d1fa10 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.usepermission;
 
 import static junit.framework.Assert.assertEquals;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
@@ -47,16 +46,13 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ScrollView;
 import android.widget.Switch;
-
-import junit.framework.Assert;
-
-import org.junit.Before;
-import org.junit.runner.RunWith;
-
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public abstract class BasePermissionsTest {
@@ -69,67 +65,6 @@
     private static final String LOG_TAG = "BasePermissionsTest";
 
     private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
-    static {
-        // Contacts
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_CONTACTS,
-                "@android:string/permgrouplab_contacts");
-        sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CONTACTS,
-                "@android:string/permgrouplab_contacts");
-        // Calendar
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALENDAR,
-                "@android:string/permgrouplab_calendar");
-        sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALENDAR,
-                "@android:string/permgrouplab_calendar");
-        // SMS
-        sPermissionToLabelResNameMap.put(Manifest.permission.SEND_SMS,
-                "@android:string/permgrouplab_sms");
-        sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_SMS,
-                "@android:string/permgrouplab_sms");
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_SMS,
-                "@android:string/permgrouplab_sms");
-        sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_WAP_PUSH,
-                "@android:string/permgrouplab_sms");
-        sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_MMS,
-                "@android:string/permgrouplab_sms");
-        sPermissionToLabelResNameMap.put("android.permission.READ_CELL_BROADCASTS",
-                "@android:string/permgrouplab_sms");
-        // Storage
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_EXTERNAL_STORAGE,
-                "@android:string/permgrouplab_storage");
-        sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                "@android:string/permgrouplab_storage");
-        // Location
-        sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_FINE_LOCATION,
-                "@android:string/permgrouplab_location");
-        sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_COARSE_LOCATION,
-                "@android:string/permgrouplab_location");
-        // Phone
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_PHONE_STATE,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.CALL_PHONE,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put("android.permission.ACCESS_IMS_CALL_SERVICE",
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALL_LOG,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALL_LOG,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.ADD_VOICEMAIL,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.USE_SIP,
-                "@android:string/permgrouplab_phone");
-        sPermissionToLabelResNameMap.put(Manifest.permission.PROCESS_OUTGOING_CALLS,
-                "@android:string/permgrouplab_phone");
-        // Microphone
-        sPermissionToLabelResNameMap.put(Manifest.permission.RECORD_AUDIO,
-                "@android:string/permgrouplab_microphone");
-        // Camera
-        sPermissionToLabelResNameMap.put(Manifest.permission.CAMERA,
-                "@android:string/permgrouplab_camera");
-        // Body sensors
-        sPermissionToLabelResNameMap.put(Manifest.permission.BODY_SENSORS,
-                "@android:string/permgrouplab_sensors");
-    }
 
     private Context mContext;
     private Resources mPlatformResources;
@@ -168,6 +103,143 @@
         return activity;
     }
 
+    private void initPermissionToLabelMap(boolean permissionReviewMode) {
+        if (!permissionReviewMode) {
+            // Contacts
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CONTACTS, "@android:string/permgrouplab_contacts");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CONTACTS, "@android:string/permgrouplab_contacts");
+            // Calendar
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar");
+            // SMS
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.SEND_SMS, "@android:string/permgrouplab_sms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_SMS, "@android:string/permgrouplab_sms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_SMS, "@android:string/permgrouplab_sms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permgrouplab_sms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_MMS, "@android:string/permgrouplab_sms");
+            sPermissionToLabelResNameMap.put(
+                    "android.permission.READ_CELL_BROADCASTS", "@android:string/permgrouplab_sms");
+            // Storage
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_EXTERNAL_STORAGE,
+                    "@android:string/permgrouplab_storage");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                    "@android:string/permgrouplab_storage");
+            // Location
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ACCESS_FINE_LOCATION,
+                    "@android:string/permgrouplab_location");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ACCESS_COARSE_LOCATION,
+                    "@android:string/permgrouplab_location");
+            // Phone
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_PHONE_STATE, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.CALL_PHONE, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    "android.permission.ACCESS_IMS_CALL_SERVICE",
+                    "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CALL_LOG, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CALL_LOG, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ADD_VOICEMAIL, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.USE_SIP, "@android:string/permgrouplab_phone");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.PROCESS_OUTGOING_CALLS,
+                    "@android:string/permgrouplab_phone");
+            // Microphone
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone");
+            // Camera
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.CAMERA, "@android:string/permgrouplab_camera");
+            // Body sensors
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors");
+        } else {
+            // Contacts
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CONTACTS, "@android:string/permlab_readContacts");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CONTACTS, "@android:string/permlab_writeContacts");
+            // Calendar
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar");
+            // SMS
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.SEND_SMS, "@android:string/permlab_sendSms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_SMS, "@android:string/permlab_receiveSms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_SMS, "@android:string/permlab_readSms");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permlab_receiveWapPush");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECEIVE_MMS, "@android:string/permlab_receiveMms");
+            sPermissionToLabelResNameMap.put(
+                    "android.permission.READ_CELL_BROADCASTS",
+                    "@android:string/permlab_readCellBroadcasts");
+            // Storage
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_EXTERNAL_STORAGE,
+                    "@android:string/permgrouplab_storage");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                    "@android:string/permgrouplab_storage");
+            // Location
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ACCESS_FINE_LOCATION,
+                    "@android:string/permgrouplab_location");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ACCESS_COARSE_LOCATION,
+                    "@android:string/permgrouplab_location");
+            // Phone
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_PHONE_STATE, "@android:string/permlab_readPhoneState");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.CALL_PHONE, "@android:string/permlab_callPhone");
+            sPermissionToLabelResNameMap.put(
+                    "android.permission.ACCESS_IMS_CALL_SERVICE",
+                    "@android:string/permlab_accessImsCallService");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.READ_CALL_LOG, "@android:string/permlab_readCallLog");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.WRITE_CALL_LOG, "@android:string/permlab_writeCallLog");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.ADD_VOICEMAIL, "@android:string/permlab_addVoicemail");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.USE_SIP, "@android:string/permlab_use_sip");
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.PROCESS_OUTGOING_CALLS,
+                    "@android:string/permlab_processOutgoingCalls");
+            // Microphone
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone");
+            // Camera
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.CAMERA, "@android:string/permgrouplab_camera");
+            // Body sensors
+            sPermissionToLabelResNameMap.put(
+                    Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors");
+        }
+    }
+
     @Before
     public void beforeTest() {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -178,7 +250,9 @@
             /* cannot happen */
         }
 
-        mWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+        PackageManager packageManager = mContext.getPackageManager();
+        mWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
+        initPermissionToLabelMap(packageManager.isPermissionReviewModeEnabled());
 
         UiObject2 button = getUiDevice().findObject(By.text("Close"));
         if (button != null) {
@@ -278,6 +352,7 @@
         // Open the app details settings
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setData(Uri.parse("package:" + mContext.getPackageName()));
         startActivity(intent);
 
@@ -358,6 +433,7 @@
             try {
                 getInstrumentation().getContext().startActivity(intent);
             } catch (Exception e) {
+                Log.e(LOG_TAG, "Cannot start activity: " + intent, e);
                 fail("Cannot start activity: " + intent);
             }
         }, (AccessibilityEvent event) -> event.getEventType()
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 518b233..01f9c7d 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
@@ -17,6 +17,8 @@
 package com.android.cts.usepermission;
 
 import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
@@ -453,7 +455,7 @@
                 Manifest.permission.WRITE_CALENDAR
         };
 
-        // Request the permission and do nothing
+        // Request the permission and allow it
         BasePermissionActivity.Result result = requestPermissions(permissions,
                 REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
             try {
@@ -466,9 +468,24 @@
             }
         });
 
-        // Expect the permission is not granted
+        // Expect the permission are reported as granted
         assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
                 permissions, new boolean[] {true, true});
+
+        // The permissions are granted
+        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_CONTACTS));
+        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_CALENDAR));
+
+        // In API < N_MR1 all permissions of a group are granted. I.e. the grant was "expanded"
+        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.READ_CALENDAR));
+
+        // Even the contacts group was expanded, the read-calendar permission is not in the
+        // manifest, hence not granted.
+        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.READ_CONTACTS));
     }
 
     @Test
@@ -537,6 +554,76 @@
         assertAllPermissionsGrantState(PackageManager.PERMISSION_GRANTED);
     }
 
+    @Test
+    public void testNullPermissionRequest() throws Exception {
+        String[] permissions = new String[] {null};
+
+        // Go through normal grant flow
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                REQUEST_CODE_PERMISSIONS,
+                BasePermissionActivity.class,
+                () -> { /* empty */ });
+
+        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
+                permissions, new boolean[] {false});
+    }
+
+    @Test
+    public void testNullAndRealPermission() throws Exception {
+        // Make sure we don't have the permissions
+        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_CONTACTS));
+        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
+
+        String[] permissions = new String[] {
+                null,
+                Manifest.permission.WRITE_CONTACTS,
+                null,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE
+        };
+
+        // Request the permission and allow it
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
+                    try {
+                        clickAllowButton();
+                        getUiDevice().waitForIdle();
+                        clickAllowButton();
+                        getUiDevice().waitForIdle();
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        // Expect the permission are reported as granted
+        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
+                permissions, new boolean[] {false, true, false, true});
+
+        // The permissions are granted
+        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_CONTACTS));
+        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
+    }
+
+    @Test
+    public void testInvalidPermission() throws Exception {
+        String[] permissions = new String[] {
+                getInstrumentation().getContext().getPackageName() + ".abadname"
+        };
+
+        // Request the permission and allow it
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                REQUEST_CODE_PERMISSIONS,
+                BasePermissionActivity.class,
+                () -> { /* empty */ });
+
+        // Expect the permissions is not granted
+        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
+                permissions, new boolean[] {false});
+    }
+
     private void assertAllPermissionsRevoked() {
         assertAllPermissionsGrantState(PackageManager.PERMISSION_DENIED);
     }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
index cccdd7f..8528752 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
@@ -25,6 +25,8 @@
     ctstestrunner \
     ub-uiautomator
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, ../UsePermissionApp23/src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
 LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
index acaeeb0..c6a6316 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
@@ -67,6 +67,8 @@
     <uses-permission android:name="android.permission.BODY_SENSORS"/>
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
     </application>
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
index 845e43d..9458db3 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
     </application>
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
index cac6790..57a58ab 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
     </application>
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
index 498e8ca..93d8ddd 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
@@ -19,10 +19,12 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-    ../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
+    ../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
 
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionDiffCert
 
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 cf16307..80a1578 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -16,21 +16,33 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
-import android.content.BroadcastReceiver;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_CLEAR_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INTENT;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_MODE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_URI;
+
 import android.content.ClipData;
-import android.content.ComponentName;
+import android.content.ClipboardManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 import android.content.UriPermission;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.SystemClock;
+import android.os.Bundle;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import com.android.cts.permissiondeclareapp.GrantUriPermission;
+import com.android.cts.permissiondeclareapp.UtilsProvider;
 
 import java.io.IOException;
 import java.util.List;
@@ -42,9 +54,6 @@
  * Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
  */
 public class AccessPermissionWithDiffSigTest extends AndroidTestCase {
-    private static final ComponentName GRANT_URI_PERM_COMP
-            = new ComponentName("com.android.cts.permissiondeclareapp",
-                    "com.android.cts.permissiondeclareapp.GrantUriPermission");
     private static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
     private static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
     private static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
@@ -58,6 +67,20 @@
     private static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
     private static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
 
+    private static final Uri[] GRANTABLE = new Uri[] {
+            Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
+            Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
+            Uri.withAppendedPath(PERM_URI_PATH, "foo"),
+    };
+
+    private static final Uri[] NOT_GRANTABLE = new Uri[] {
+            Uri.withAppendedPath(PERM_URI, "foo"),
+            Uri.withAppendedPath(PRIV_URI, "foo"),
+            Uri.withAppendedPath(PERM_URI_PATH_RESTRICTING, "foo"),
+            CalendarContract.CONTENT_URI,
+            ContactsContract.AUTHORITY_URI,
+    };
+
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
@@ -83,6 +106,10 @@
         }
     }
 
+    private void assertReadingClipNotAllowed(ClipData clip) {
+        assertReadingClipNotAllowed(clip, null);
+    }
+
     private void assertReadingClipNotAllowed(ClipData clip, String msg) {
         for (int i=0; i<clip.getItemCount(); i++) {
             ClipData.Item item = clip.getItemAt(i);
@@ -173,6 +200,10 @@
         }
     }
 
+    private void assertWritingClipNotAllowed(ClipData clip) {
+        assertWritingClipNotAllowed(clip, null);
+    }
+
     private void assertWritingClipNotAllowed(ClipData clip, String msg) {
         for (int i=0; i<clip.getItemCount(); i++) {
             ClipData.Item item = clip.getItemAt(i);
@@ -425,76 +456,6 @@
                 Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
     }
 
-    private static class GrantResultReceiver extends BroadcastReceiver {
-        boolean mHaveResult = false;
-        boolean mGoodResult = false;
-        boolean mSucceeded = false;
-        static final int TIMEOUT_MS = 30000;
-        
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (this) {
-                mHaveResult = true;
-                switch (getResultCode()) {
-                    case GrantUriPermission.FAILURE:
-                        mGoodResult = true;
-                        mSucceeded = false;
-                        break;
-                    case GrantUriPermission.SUCCESS:
-                        mGoodResult = true;
-                        mSucceeded = true;
-                        break;
-                    default:
-                        mGoodResult = false;
-                        break;
-                }
-                notifyAll();
-            }
-        }
-        
-        void assertSuccess(String failureMessage) {
-            synchronized (this) {
-                final long startTime = SystemClock.uptimeMillis();
-                while (!mHaveResult) {
-                    try {
-                        wait(TIMEOUT_MS);
-                    } catch (InterruptedException e) {
-                    }
-                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
-                        throw new RuntimeException("Timeout");
-                    }
-                }
-                if (!mGoodResult) {
-                    fail("Broadcast receiver did not return good result");
-                }
-                if (!mSucceeded) {
-                    fail(failureMessage);
-                }
-            }
-        }
-        
-        void assertFailure(String failureMessage) {
-            synchronized (this) {
-                final long startTime = SystemClock.uptimeMillis();
-                while (!mHaveResult) {
-                    try {
-                        wait(TIMEOUT_MS);
-                    } catch (InterruptedException e) {
-                    }
-                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
-                        throw new RuntimeException("Timeout");
-                    }
-                }
-                if (!mGoodResult) {
-                    fail("Broadcast receiver did not return good result");
-                }
-                if (mSucceeded) {
-                    fail(failureMessage);
-                }
-            }
-        }
-    }
-
     private void grantUriPermissionFail(Uri uri, int mode, boolean service) {
         Uri grantDataUri = Uri.withAppendedPath(uri, "data");
         Intent grantIntent = new Intent();
@@ -503,26 +464,26 @@
         grantIntent.setClass(getContext(),
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         Intent intent = new Intent();
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
-                : GrantUriPermission.ACTION_START_ACTIVITY);
-        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
-        GrantResultReceiver receiver = new GrantResultReceiver();
-        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
-        receiver.assertFailure("Able to grant URI permission to " + grantDataUri + " when should not");
+        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+        intent.putExtra(EXTRA_INTENT, grantIntent);
+        try {
+            call(intent);
+            fail("Able to grant URI permission to " + grantDataUri + " when should not");
+        } catch (Exception expected) {
+        }
 
         grantIntent = makeClipIntent(uri, mode);
         grantIntent.setClass(getContext(),
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         intent = new Intent();
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
-                : GrantUriPermission.ACTION_START_ACTIVITY);
-        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
-        receiver = new GrantResultReceiver();
-        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
-        receiver.assertFailure("Able to grant URI permission to " + grantIntent.getClipData()
-                + " when should not");
+        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+        intent.putExtra(EXTRA_INTENT, grantIntent);
+        try {
+            call(intent);
+            fail("Able to grant URI permission to " + grantIntent.getClipData()
+                    + " when should not");
+        } catch (Exception expected) {
+        }
     }
 
     private void doTestGrantUriPermissionFail(Uri uri) {
@@ -574,6 +535,12 @@
         doTestGrantUriPermissionFail(Uri.withAppendedPath(PRIV_URI_GRANTING, "invalid"));
     }
 
+    private void call(Intent intent) {
+        final Bundle extras = new Bundle();
+        extras.putParcelable(Intent.EXTRA_INTENT, intent);
+        getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
+    }
+
     private void grantClipUriPermission(ClipData clip, int mode, boolean service) {
         Intent grantIntent = new Intent();
         if (clip.getItemCount() == 1) {
@@ -592,33 +559,39 @@
         grantIntent.setClass(getContext(),
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         Intent intent = new Intent();
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
-                : GrantUriPermission.ACTION_START_ACTIVITY);
-        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
-        getContext().sendBroadcast(intent);
+        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+        intent.putExtra(EXTRA_INTENT, grantIntent);
+        call(intent);
     }
 
     private void grantClipUriPermissionViaContext(Uri uri, int mode) {
         Intent intent = new Intent();
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(GrantUriPermission.ACTION_GRANT_URI);
-        intent.putExtra(GrantUriPermission.EXTRA_PACKAGE_NAME, getContext().getPackageName());
-        intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
-        intent.putExtra(GrantUriPermission.EXTRA_MODE, mode);
-        getContext().sendBroadcast(intent);
+        intent.setAction(ACTION_GRANT_URI);
+        intent.putExtra(EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        intent.putExtra(EXTRA_URI, uri);
+        intent.putExtra(EXTRA_MODE, mode);
+        call(intent);
     }
 
     private void revokeClipUriPermissionViaContext(Uri uri, int mode) {
         Intent intent = new Intent();
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(GrantUriPermission.ACTION_REVOKE_URI);
-        intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
-        intent.putExtra(GrantUriPermission.EXTRA_MODE, mode);
-        getContext().sendBroadcast(intent);
+        intent.setAction(ACTION_REVOKE_URI);
+        intent.putExtra(EXTRA_URI, uri);
+        intent.putExtra(EXTRA_MODE, mode);
+        call(intent);
+    }
+
+    private void setPrimaryClip(ClipData clip) {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_SET_PRIMARY_CLIP);
+        intent.setClipData(clip);
+        call(intent);
+    }
+
+    private void clearPrimaryClip() {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_CLEAR_PRIMARY_CLIP);
+        call(intent);
     }
 
     private void assertReadingClipAllowed(ClipData clip) {
@@ -1371,12 +1344,9 @@
 
         // And assert remote
         Intent intent = new Intent();
-        intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.setAction(GrantUriPermission.ACTION_VERIFY_OUTGOING_PERSISTED);
-        intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
-        GrantResultReceiver receiver = new GrantResultReceiver();
-        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
-        receiver.assertSuccess("unexpected outgoing persisted Uri status");
+        intent.setAction(ACTION_VERIFY_OUTGOING_PERSISTED);
+        intent.putExtra(EXTRA_URI, uri);
+        call(intent);
     }
 
     /**
@@ -1500,7 +1470,6 @@
         grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
         grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        SystemClock.sleep(2000);
 
         long before = System.currentTimeMillis();
         resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -1517,7 +1486,6 @@
 
         // Revoke anyone with write under meow
         revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        SystemClock.sleep(2000);
 
         // This should have nuked persisted permission at lower level, but it
         // shoulnd't have touched our prefix read.
@@ -1531,7 +1499,6 @@
 
         // Revoking read at top of tree should nuke everything else
         revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        SystemClock.sleep(2000);
         assertReadingClipNotAllowed(clip, "reading should have failed");
         assertReadingClipNotAllowed(clipMeow, "reading should have failed");
         assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
@@ -1569,7 +1536,6 @@
         grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
         grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        SystemClock.sleep(2000);
 
         long before = System.currentTimeMillis();
         resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -1607,4 +1573,56 @@
         assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
         assertNoPersistedUriPermission();
     }
+
+    public void testClipboardWithPermission() throws Exception {
+        for (Uri target : GRANTABLE) {
+            final ClipData clip = makeSingleClipData(target);
+
+            // Normally we can't see the underlying clip data
+            assertReadingClipNotAllowed(clip);
+            assertWritingClipNotAllowed(clip);
+
+            // But if someone puts it on the clipboard, we can read it
+            setPrimaryClip(clip);
+            final ClipData clipFromClipboard = getContext().getSystemService(ClipboardManager.class)
+                    .getPrimaryClip();
+            assertClipDataEquals(clip, clipFromClipboard);
+            assertReadingClipAllowed(clipFromClipboard);
+            assertWritingClipNotAllowed(clipFromClipboard);
+
+            // And if clipboard is cleared, we lose access
+            clearPrimaryClip();
+            assertReadingClipNotAllowed(clipFromClipboard);
+            assertWritingClipNotAllowed(clipFromClipboard);
+        }
+    }
+
+    public void testClipboardWithoutPermission() throws Exception {
+        for (Uri target : NOT_GRANTABLE) {
+            final ClipData clip = makeSingleClipData(target);
+
+            // Can't see it directly
+            assertReadingClipNotAllowed(clip);
+            assertWritingClipNotAllowed(clip);
+
+            // Can't put on clipboard if we don't own it
+            try {
+                setPrimaryClip(clip);
+                fail("Unexpected ability to put protected data " + clip + " on clipboard!");
+            } catch (Exception expected) {
+            }
+        }
+    }
+
+    private static void assertClipDataEquals(ClipData expected, ClipData actual) {
+        assertEquals(expected.getItemCount(), actual.getItemCount());
+        for (int i = 0; i < expected.getItemCount(); i++) {
+            final ClipData.Item expectedItem = expected.getItemAt(i);
+            final ClipData.Item actualItem = actual.getItemAt(i);
+            assertEquals(expectedItem.getText(), actualItem.getText());
+            assertEquals(expectedItem.getHtmlText(), actualItem.getHtmlText());
+            assertEquals(expectedItem.getIntent(), actualItem.getIntent());
+            assertEquals(expectedItem.getUri(), actualItem.getUri());
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
index 989e24b..f3e5002 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
@@ -16,15 +16,16 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_INSTALLER_PACKAGE_NAME;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INSTALLER_PACKAGE_NAME;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
+
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.SystemClock;
+import android.os.Bundle;
 import android.test.AndroidTestCase;
-import android.util.Log;
+
+import com.android.cts.permissiondeclareapp.UtilsProvider;
 
 /**
  * Tests that one application can and can not modify the installer package
@@ -33,85 +34,19 @@
  * Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
  */
 public class ModifyInstallerPackageTest extends AndroidTestCase {
-    static final ComponentName SET_INSTALLER_PACKAGE_COMP
-            = new ComponentName("com.android.cts.permissiondeclareapp",
-                    "com.android.cts.permissiondeclareapp.SetInstallerPackage");
     static final String OTHER_PACKAGE = "com.android.cts.permissiondeclareapp";
     static final String MY_PACKAGE = "com.android.cts.usespermissiondiffcertapp";
 
-    static class SetInstallerPackageReceiver extends BroadcastReceiver {
-        boolean mHaveResult = false;
-        boolean mGoodResult = false;
-        boolean mSucceeded = false;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (this) {
-                mHaveResult = true;
-                switch (getResultCode()) {
-                    case 100:
-                        mGoodResult = true;
-                        mSucceeded = false;
-                        break;
-                    case 101:
-                        mGoodResult = true;
-                        mSucceeded = true;
-                        break;
-                    default:
-                        mGoodResult = false;
-                        break;
-                }
-                notifyAll();
-            }
-        }
-
-        void assertSuccess(String failureMessage) {
-            synchronized (this) {
-                final long startTime = SystemClock.uptimeMillis();
-                while (!mHaveResult) {
-                    try {
-                        wait(5000);
-                    } catch (InterruptedException e) {
-                    }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
-                        throw new RuntimeException("Timeout");
-                    }
-                }
-                if (!mGoodResult) {
-                    fail("Broadcast receiver did not return good result");
-                }
-                if (!mSucceeded) {
-                    fail(failureMessage);
-                }
-            }
-        }
-
-        void assertFailure(String failureMessage) {
-            synchronized (this) {
-                final long startTime = SystemClock.uptimeMillis();
-                while (!mHaveResult) {
-                    try {
-                        wait(5000);
-                    } catch (InterruptedException e) {
-                    }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
-                        throw new RuntimeException("Timeout");
-                    }
-                }
-                if (!mGoodResult) {
-                    fail("Broadcast receiver did not return good result");
-                }
-                if (mSucceeded) {
-                    fail(failureMessage);
-                }
-            }
-        }
-    }
-
     PackageManager getPackageManager() {
         return getContext().getPackageManager();
     }
 
+    private void call(Intent intent) {
+        final Bundle extras = new Bundle();
+        extras.putParcelable(Intent.EXTRA_INTENT, intent);
+        getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
+    }
+
     /**
      * Test that we can set the installer package name.
      */
@@ -182,12 +117,10 @@
 
         // Have the other package set the installer, under its cert.
         Intent intent = new Intent();
-        intent.setComponent(SET_INSTALLER_PACKAGE_COMP);
-        intent.putExtra("target", OTHER_PACKAGE);
-        intent.putExtra("installer", OTHER_PACKAGE);
-        SetInstallerPackageReceiver receiver = new SetInstallerPackageReceiver();
-        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
-        receiver.assertSuccess("Failure initializing with other installer");
+        intent.setAction(ACTION_SET_INSTALLER_PACKAGE_NAME);
+        intent.putExtra(EXTRA_PACKAGE_NAME, OTHER_PACKAGE);
+        intent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, OTHER_PACKAGE);
+        call(intent);
 
         assertEquals(OTHER_PACKAGE, getPackageManager().getInstallerPackageName(OTHER_PACKAGE));
 
@@ -201,11 +134,10 @@
         assertEquals(OTHER_PACKAGE, getPackageManager().getInstallerPackageName(OTHER_PACKAGE));
 
         // Now clear the installer
-        intent.putExtra("target", OTHER_PACKAGE);
-        intent.putExtra("installer", (String)null);
-        receiver = new SetInstallerPackageReceiver();
-        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
-        receiver.assertSuccess("Failure clearing other installer");
+        intent.setAction(ACTION_SET_INSTALLER_PACKAGE_NAME);
+        intent.putExtra(EXTRA_PACKAGE_NAME, OTHER_PACKAGE);
+        intent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, (String)null);
+        call(intent);
 
         assertEquals(null, getPackageManager().getInstallerPackageName(OTHER_PACKAGE));
     }
diff --git a/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk b/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
index 2d67d62..df12f82 100644
--- a/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
@@ -22,6 +22,8 @@
 LOCAL_SDK_VERSION := current
 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, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
 
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.mk b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.mk
new file mode 100644
index 0000000..235a955
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/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_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+LOCAL_PACKAGE_NAME := CtsV3SigningSchemeRotationTest
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/AndroidManifest.xml
new file mode 100644
index 0000000..677757f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.v3rotationtests">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:targetPackage="android.appsecurity.cts.v3rotationtests"
+        android:name="android.support.test.runner.AndroidJUnitRunner" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
new file mode 100644
index 0000000..e13dfad
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.v3rotationtests;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.test.AndroidTestCase;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.lang.Override;
+
+/**
+ * On-device tests for APK Signature Scheme v3 based signing certificate rotation
+ */
+public class V3RotationTest extends AndroidTestCase {
+
+    private static final String PKG = "android.appsecurity.cts.tinyapp";
+    private static final String COMPANION_PKG = "android.appsecurity.cts.tinyapp_companion";
+    private static final String PERMISSION_NAME = "android.appsecurity.cts.tinyapp.perm";
+
+    private static final String FIRST_CERT_HEX =
+            "308203773082025fa0030201020204357f7a97300d06092a864886f70d01010b0500306c310b3009060355"
+            + "040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e20566965"
+            + "773110300e060355040a1307416e64726f69643110300e060355040b1307556e6b6e6f776e3114301206"
+            + "03550403130b477265656e2044726f6964301e170d3138303133313138303331345a170d343530363138"
+            + "3138303331345a306c310b3009060355040613025553310b300906035504081302434131163014060355"
+            + "0407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355"
+            + "040b1307556e6b6e6f776e311430120603550403130b477265656e2044726f696430820122300d06092a"
+            + "864886f70d01010105000382010f003082010a0282010100bce0517978db6e1eb0255b4704c10fca31b8"
+            + "d8b7465d1512a36712f425009143d418743cbde9c9f7df219907fff376e44298462fb14f5dd7d0edd46e"
+            + "d764f93e82b277146f100616c4d93e97279aefdcf449a5ff000eb72445816039c7afdcd9340f39fc5f0b"
+            + "b4d0e6c4d67a0ec876bf28007cf709667250142385248af89c5fa87b8ef9cc1270577b1721c235bdde97"
+            + "87c04536706947193d38d244c8c6590e54c90b3843506b3e19c5f2c22c38a1a2893c749ad4ddce67b86f"
+            + "f5dcd7dd0d63265fa50036f4bf31f28f2b4c067fb58c95e09ab6c1686c257c3730616659b352b966ed66"
+            + "d93952c60563c9d863aa37c097f646d55c825b450f511eca39198cd70203010001a321301f301d060355"
+            + "1d0e04160414831074e3173e51fbd2ac8ed8c6681f61eca81318300d06092a864886f70d01010b050003"
+            + "820101001fa412618659f40395e38698cd8b7ca9425f63e231e39a6c8e2909af951bbdc5e6307813e25e"
+            + "31502578a56b6e6f08442a400de19449f1c7f87357e55191fd7f30b8735e1298d4c668b9cde563e143d4"
+            + "72bf8005443d8ee386f955136ad0aa16dda7f5b977dc0dee858f954aff2ae4d7473259fb1b1acc1d6161"
+            + "49b12659371ae0298222974be10817156279c9dd8f6cae5265bf15b63fa9f084e789d06b3a9b86d82749"
+            + "c95793acded24b321a31cc20fd12774c7b207325df50c2efcb78a5cf9749f62aafd86d303254880a4476"
+            + "a67801452e82d1e6c91aeb97ca837a91bcefd8324fca491de4ef648d80c6d74f4b66533684040ddad550"
+            + "0ff140edcdacf0a4";
+
+    private static final String SECOND_CERT_HEX =
+            "3082037b30820263a00302010202044c4a644a300d06092a864886f70d01010b0500306e310b3009060355"
+            + "040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e20566965"
+            + "773110300e060355040a1307416e64726f69643110300e060355040b1307556e6b6e6f776e3116301406"
+            + "03550403130d477265656e65722044726f6964301e170d3138303133313138303533385a170d34353036"
+            + "31383138303533385a306e310b3009060355040613025553310b30090603550408130243413116301406"
+            + "03550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06"
+            + "0355040b1307556e6b6e6f776e311630140603550403130d477265656e65722044726f69643082012230"
+            + "0d06092a864886f70d01010105000382010f003082010a0282010100a4f3741edf04e32e7047aaadc2ce"
+            + "de0164a2847149aa7fad054dab59e70c1078dd2d5946cec64809f79551395478b08f3e0286d452fa39b9"
+            + "e6804e965a44210a61bd5cfe3c8ac0ec86017c0e91260248daa1fd7a11b1b1b519216a40d02db49e754a"
+            + "6380357c45ef1531996e4c37c13e2f507f3a9296eb52235248d0172efe4ed7bac537fe2435f03d66c5e3"
+            + "e5cbf60d77f3088bc22718ded09009d6414106d1301d1a5abf71aa5dc6469bb73b29b2cc9c09b9c42e8f"
+            + "8e81e7fc9b24ad1cb0c3e2e2832d0570bdeb87a3ff8f49aae05b03164fb3c225745b70134c6b7aaf081f"
+            + "514058776bd28a0c0a152bf45b3eddc7f2c4aaed4eace0cab67ef4650213303f0203010001a321301f30"
+            + "1d0603551d0e04160414f0c3dacf02d9583200d4d386a40341aee82dd859300d06092a864886f70d0101"
+            + "0b050003820101003a63a93be986740158574239310e987cdcdc86f735867789c2b56f8ffa7ce901a9f2"
+            + "2b0c374d345aa996a37c7474824dbf72186d1eedaa6aae0574fd1996f501def32e62703ee4010a7058df"
+            + "f0d608df1176bdbe93ebd3910172fc146307d0961a57d95806eeabc1e54b81273d9449f0f68511288377"
+            + "c82f87a032d1379260365ef83c93f1bf4c0af959b426794c558c0357fe61271ed0fb054e916e0bab8600"
+            + "3bcc16ce53f7d2c8d05b83072115b0fb2c671c2266813f1a407ca911c47f27665debfa63bc4e4071730a"
+            + "a477acbe6eecc8d575511b2b77a3551f040ccf21792f74cd95c84e3ff87ad03851db81d164c836830e31"
+            + "8208a52dcdc77e376f73b96d";
+
+    public void testHasPerm() throws Exception {
+        PackageManager pm = getContext().getPackageManager();
+        assertTrue(PERMISSION_NAME + " not granted to " + COMPANION_PKG,
+                pm.checkPermission(PERMISSION_NAME, COMPANION_PKG)
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    public void testHasNoPerm() throws Exception {
+        PackageManager pm = getContext().getPackageManager();
+        assertFalse(PERMISSION_NAME + " granted to " + COMPANION_PKG,
+                pm.checkPermission(PERMISSION_NAME, COMPANION_PKG)
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    public void testGetSignaturesShowsOld() throws Exception {
+        // to prevent breakage due to signing certificate rotation, we report the oldest signing
+        // certificate in the now-deprecated PackageInfo signatures field.  Make sure that when
+        // rotating, it still displays the older cert.
+        PackageManager pm = getContext().getPackageManager();
+        PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNATURES);
+        assertNotNull("Failed to get signatures in PackageInfo of " + PKG, pi.signatures);
+        assertEquals("PackageInfo for " + PKG + "contains multiple entries",
+                1, pi.signatures.length);
+        assertEquals("signature mismatch for " + PKG + ", expected old signing certificate",
+                pi.signatures[0].toCharsString(), FIRST_CERT_HEX);
+    }
+
+    public void testGetSigningCertificatesShowsAll() throws Exception {
+        // make sure our shiny new GET_SIGNATURES replacement does its job.  It should return all of
+        // the certificates in an app's provided history, including its current one
+        PackageManager pm = getContext().getPackageManager();
+        PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
+        assertNotNull("Failed to get signatures in PackageInfo of " + PKG,
+                pi.signingCertificateHistory);
+        int numSigningSets = pi.signingCertificateHistory.length;
+        assertEquals("PackageInfo for " + PKG + "contains the wrong number of signing certificat "
+                + " rotations.  Expected 2 (corresponding to one rotation). Found: "
+                + numSigningSets, 2, pi.signingCertificateHistory.length);
+
+        // make sure all entries have at most one signing certificate.  Also check to see if we
+        // match the certs for which we're looking
+        boolean matchedFirst = false;
+        boolean matchedSecond = false;
+        for (int i = 0; i < numSigningSets; i ++) {
+            assertEquals("Multiple signing certificates found in signing certificate history for "
+                    + PKG, 1, pi.signingCertificateHistory[i].length);
+            String reportedCert = pi.signingCertificateHistory[i][0].toCharsString();
+            if (FIRST_CERT_HEX.equals(reportedCert)) {
+                matchedFirst = true;
+            } else if (SECOND_CERT_HEX.equals(reportedCert)) {
+                matchedSecond = true;
+            }
+        }
+        assertTrue("Old signing certificate not found for " + PKG + " expected "
+                + FIRST_CERT_HEX, matchedFirst);
+        assertTrue("Current signing certificate not found for " + PKG + " expected "
+                + SECOND_CERT_HEX, matchedSecond);
+    }
+
+    public void testHasSigningCertificate() throws Exception {
+        // make sure that hasSigningCertificate() reports that both certificates in the signing
+        // history are present
+        PackageManager pm = getContext().getPackageManager();
+        byte[] firstCertBytes = fromHexToByteArray(FIRST_CERT_HEX);
+        assertTrue("Old signing certificate not found for " + PKG,
+               pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+        byte[] secondCertBytes = fromHexToByteArray(SECOND_CERT_HEX);
+        assertTrue("Current signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+    }
+
+    public void testHasSigningCertificateSha256() throws Exception {
+        // make sure that hasSigningCertificate() reports that both certificates in the signing
+        // history are present
+        PackageManager pm = getContext().getPackageManager();
+        byte[] firstCertBytes = computeSha256DigestBytes(fromHexToByteArray(FIRST_CERT_HEX));
+
+        assertTrue("Old signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_SHA256));
+        byte[] secondCertBytes = computeSha256DigestBytes(fromHexToByteArray(SECOND_CERT_HEX));
+        assertTrue("Current signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
+    }
+
+    public void testHasSigningCertificateByUid() throws Exception {
+        // make sure that hasSigningCertificate() reports that both certificates in the signing
+        // history are present
+        PackageManager pm = getContext().getPackageManager();
+        int uid = pm.getPackageUid(PKG, 0);
+        byte[] firstCertBytes = fromHexToByteArray(FIRST_CERT_HEX);
+        assertTrue("Old signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(uid, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+        byte[] secondCertBytes = fromHexToByteArray(SECOND_CERT_HEX);
+        assertTrue("Current signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+    }
+
+    public void testHasSigningCertificateByUidSha256() throws Exception {
+        // make sure that hasSigningCertificate() reports that both certificates in the signing
+        // history are present
+        PackageManager pm = getContext().getPackageManager();
+        int uid = pm.getPackageUid(PKG, 0);
+        byte[] firstCertBytes = computeSha256DigestBytes(fromHexToByteArray(FIRST_CERT_HEX));
+
+        assertTrue("Old signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(uid, firstCertBytes, PackageManager.CERT_INPUT_SHA256));
+        byte[] secondCertBytes = computeSha256DigestBytes(fromHexToByteArray(SECOND_CERT_HEX));
+        assertTrue("Current signing certificate not found for " + PKG,
+                pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
+    }
+
+    private  static byte[] fromHexToByteArray(String str) {
+        if (str == null || str.length() == 0 || str.length() % 2 != 0) {
+            return null;
+        }
+
+        final char[] chars = str.toCharArray();
+        final int charLength = chars.length;
+        final byte[] bytes = new byte[charLength / 2];
+
+        for (int i = 0; i < bytes.length; i++) {
+            bytes[i] =
+                    (byte)(((getIndex(chars[i * 2]) << 4) & 0xF0)
+                            | (getIndex(chars[i * 2 + 1]) & 0x0F));
+        }
+        return bytes;
+    }
+
+    // copy of ByteStringUtils - lowercase version (to match inputs)
+    private static int getIndex(char c) {
+        final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
+        for (int i = 0; i < HEX_ARRAY.length; i++) {
+            if (HEX_ARRAY[i] == c) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static byte[] computeSha256DigestBytes(byte[] data) throws NoSuchAlgorithmException {
+        MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
+        messageDigest.update(data);
+        return messageDigest.digest();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
index 8574d63..b1e84cb 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -20,8 +20,9 @@
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
-	compatibility-device-util \
-	legacy-android-test
+	compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
index 907ae36..619f1ce 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
@@ -22,7 +22,8 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 LOCAL_PACKAGE_NAME := CtsKeySetTestApp
 LOCAL_DEX_PREOPT := false
 
diff --git a/hostsidetests/atrace/Android.mk b/hostsidetests/atrace/Android.mk
index eb6d14d..0c84134 100644
--- a/hostsidetests/atrace/Android.mk
+++ b/hostsidetests/atrace/Android.mk
@@ -28,6 +28,9 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+# Enforce public / test api only
+LOCAL_SDK_VERSION := test_current
+
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/atrace/AndroidTest.xml b/hostsidetests/atrace/AndroidTest.xml
index 8262232..ecaa17a 100644
--- a/hostsidetests/atrace/AndroidTest.xml
+++ b/hostsidetests/atrace/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS atrace host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsAtraceHostTestCases.jar" />
diff --git a/hostsidetests/backup/Android.mk b/hostsidetests/backup/Android.mk
index 1faec59..fd5b3c6 100644
--- a/hostsidetests/backup/Android.mk
+++ b/hostsidetests/backup/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_MODULE_TAGS := tests
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 LOCAL_MODULE := CtsBackupHostTestCases
 
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 314a92e..868e20f 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/backup/DeviceOwnerApp/Android.mk b/hostsidetests/backup/DeviceOwnerApp/Android.mk
new file mode 100644
index 0000000..a050041
--- /dev/null
+++ b/hostsidetests/backup/DeviceOwnerApp/Android.mk
@@ -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.
+
+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
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := arcts cts
+
+LOCAL_PACKAGE_NAME := CtsBackupDeviceOwnerApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/hostsidetests/backup/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/backup/DeviceOwnerApp/AndroidManifest.xml
new file mode 100644
index 0000000..f9e6f93
--- /dev/null
+++ b/hostsidetests/backup/DeviceOwnerApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.deviceownerapp" >
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <receiver
+            android:name="android.cts.backup.deviceownerapp.BackupDeviceAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.cts.backup.deviceownerapp"
+                     android:label="Device Owner CTS tests for backups" />
+</manifest>
diff --git a/hostsidetests/backup/DeviceOwnerApp/res/xml/device_admin.xml b/hostsidetests/backup/DeviceOwnerApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..0848717
--- /dev/null
+++ b/hostsidetests/backup/DeviceOwnerApp/res/xml/device_admin.xml
@@ -0,0 +1,4 @@
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+    </uses-policies>
+</device-admin>
\ No newline at end of file
diff --git a/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceAdminReceiver.java b/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceAdminReceiver.java
new file mode 100644
index 0000000..9075337
--- /dev/null
+++ b/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceAdminReceiver.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.cts.backup.deviceownerapp;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class BackupDeviceAdminReceiver extends DeviceAdminReceiver {
+
+    public static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, BackupDeviceAdminReceiver.class);
+    }
+
+}
diff --git a/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceOwnerTest.java b/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceOwnerTest.java
new file mode 100644
index 0000000..486c917
--- /dev/null
+++ b/hostsidetests/backup/DeviceOwnerApp/src/android/cts/backup/deviceownerapp/BackupDeviceOwnerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.deviceownerapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ComponentName;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BackupDeviceOwnerTest {
+
+    private static final String LOCAL_TRANSPORT =
+            "android/com.android.internal.backup.LocalTransport";
+
+    private DevicePolicyManager mDevicePolicyManager;
+    private ComponentName mLocalBackupTransportComponent;
+
+    @Before
+    public void setup() {
+        mDevicePolicyManager = getTargetContext().getSystemService(DevicePolicyManager.class);
+        mLocalBackupTransportComponent = ComponentName.unflattenFromString(LOCAL_TRANSPORT);
+        assertDeviceOwner();
+    }
+
+    @Test
+    public void testBackupServiceDisabled() {
+        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+    }
+
+    @Test
+    public void testEnableBackupService() {
+        // Set backup service enabled.
+        mDevicePolicyManager.setBackupServiceEnabled(getWho(), true);
+
+        // Verify that backup service is enabled.
+        assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+    }
+
+    @Test
+    public void testSetMandatoryBackupTransport() {
+        // Make backups with the local transport mandatory.
+        mDevicePolicyManager.setMandatoryBackupTransport(getWho(), mLocalBackupTransportComponent);
+
+        // Verify backup service is enabled.
+        assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+        // Verify the mandatory backup transport is set to the local backup transport as expected.
+        assertEquals(
+                mLocalBackupTransportComponent, mDevicePolicyManager.getMandatoryBackupTransport());
+    }
+
+    @Test
+    public void testClearMandatoryBackupTransport() {
+        // Clear the mandatory backup transport.
+        mDevicePolicyManager.setMandatoryBackupTransport(getWho(), null);
+
+        // Verify the mandatory backup transport is not set any more.
+        assertNull(mDevicePolicyManager.getMandatoryBackupTransport());
+    }
+
+    private void assertDeviceOwner() {
+        assertNotNull(mDevicePolicyManager);
+        assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
+        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(getTargetContext().getPackageName()));
+        assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
+    }
+
+    private ComponentName getWho() {
+        return BackupDeviceAdminReceiver.getComponentName(getTargetContext());
+    }
+}
\ No newline at end of file
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 76f3de2..807f5ae 100644
--- a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java
+++ b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java
@@ -140,7 +140,8 @@
         mSharedPrefTestSuccess = false;
         mSharedPrefActivityIntent = new Intent()
                 .setClassName(SHARED_PREFERENCES_RESTORE_PACKAGE_NAME,
-                        SHARED_PREFERENCES_RESTORE_ACTIVITY_NAME);
+                        SHARED_PREFERENCES_RESTORE_ACTIVITY_NAME)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.registerReceiver(mSharedPrefencesReceiver, new IntentFilter(RESULT_ACTION));
     }
 
diff --git a/hostsidetests/backup/SuccessNotificationApp/Android.mk b/hostsidetests/backup/SuccessNotificationApp/Android.mk
new file mode 100644
index 0000000..501cf14
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/Android.mk
@@ -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.
+
+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
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupSuccessNotificationApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
new file mode 100644
index 0000000..b7f8c2c
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/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.cts.backup.successnotificationapp">
+
+    <application>
+        <receiver android:name=".SuccessNotificationReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BACKUP_FINISHED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.successnotificationapp" />
+</manifest>
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java
new file mode 100644
index 0000000..2122151
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.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.successnotificationapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class SuccessNotificationReceiver extends BroadcastReceiver {
+    private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+    private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!BACKUP_FINISHED_ACTION.equals(intent.getAction())) {
+            return;
+        }
+        final String packageName = intent.getStringExtra(BACKUP_FINISHED_PACKAGE_EXTRA);
+        if (packageName == null || packageName.isEmpty()) {
+            return;
+        }
+        context.getSharedPreferences(SuccessNotificationTest.PREFS_FILE, Context.MODE_PRIVATE)
+                .edit().putBoolean(packageName, true).commit();
+    }
+}
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
new file mode 100644
index 0000000..3fb8ef2
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.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.cts.backup.successnotificationapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SuccessNotificationTest {
+    protected static final String PREFS_FILE = "android.cts.backup.successnotificationapp.PREFS";
+    private static final String KEY_VALUE_RESTORE_APP_PACKAGE =
+            "android.cts.backup.keyvaluerestoreapp";
+    private static final String FULLBACKUPONLY_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+    @Test
+    public void clearBackupSuccessNotificationsReceived() {
+        getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE).edit().clear()
+                .commit();
+    }
+
+    @Test
+    public void verifyBackupSuccessNotificationReceivedForKeyValueApp() {
+        assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+                .getBoolean(KEY_VALUE_RESTORE_APP_PACKAGE, false));
+    }
+
+    @Test
+    public void verifyBackupSuccessNotificationReceivedForFullBackupApp() {
+        assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+                .getBoolean(FULLBACKUPONLY_APP_PACKAGE, false));
+    }
+}
diff --git a/hostsidetests/backup/fullbackupapp/Android.mk b/hostsidetests/backup/fullbackupapp/Android.mk
index 96f9032..40a90d6 100644
--- a/hostsidetests/backup/fullbackupapp/Android.mk
+++ b/hostsidetests/backup/fullbackupapp/Android.mk
@@ -30,7 +30,7 @@
 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_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 LOCAL_PACKAGE_NAME := CtsFullbackupApp
 
diff --git a/hostsidetests/backup/includeexcludeapp/Android.mk b/hostsidetests/backup/includeexcludeapp/Android.mk
index a6d258e..2043a14 100644
--- a/hostsidetests/backup/includeexcludeapp/Android.mk
+++ b/hostsidetests/backup/includeexcludeapp/Android.mk
@@ -30,7 +30,7 @@
 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_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 LOCAL_PACKAGE_NAME := CtsIncludeExcludeApp
 
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
index 4f46936..73373d3 100644
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
@@ -16,7 +16,7 @@
 
 package android.cts.backup;
 
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -62,6 +62,7 @@
     private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
 
     @After
+    @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupDeviceOwnerHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupDeviceOwnerHostSideTest.java
new file mode 100644
index 0000000..5d85a3e
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupDeviceOwnerHostSideTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+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;
+
+import java.io.FileNotFoundException;
+import java.lang.Thread;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Test for checking the interaction between backup policies which can be set by a device owner and
+ * the backup subsystem.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class BackupDeviceOwnerHostSideTest extends BaseBackupHostSideTest {
+
+    private static final long MAX_TRANSPORTS_WAIT_MS = 5000;
+    private static final long LIST_TRANSPORTS_RETRY_MS = 200;
+    private static final String DEVICE_OWNER_APK = "CtsBackupDeviceOwnerApp.apk";
+    private static final String DEVICE_OWNER_PKG = "android.cts.backup.deviceownerapp";
+    private static final String DEVICE_OWNER_TEST_CLASS = ".BackupDeviceOwnerTest";
+    private static final String DEVICE_OWNER_TEST_NAME = DEVICE_OWNER_PKG + DEVICE_OWNER_TEST_CLASS;
+    private static final String ADMIN_RECEIVER_TEST_CLASS =
+            DEVICE_OWNER_PKG + ".BackupDeviceAdminReceiver";
+    private static final String DEVICE_OWNER_COMPONENT = DEVICE_OWNER_PKG + "/"
+            + ADMIN_RECEIVER_TEST_CLASS;
+
+    private boolean mIsDeviceManagementSupported;
+    private int mPrimaryUserId;
+    private boolean mBackupsInitiallyEnabled;
+    private String mOriginalTransport;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue("android.software.backup feature is not supported on this device",
+                mIsBackupSupported);
+        mIsDeviceManagementSupported = hasDeviceFeature("android.software.device_admin");
+        assumeTrue("android.software.device_admin feature is not supported on this device",
+                mIsDeviceManagementSupported);
+        mBackupsInitiallyEnabled = isBackupEnabled();
+        if (mBackupsInitiallyEnabled) {
+            mOriginalTransport = getCurrentTransport();
+        }
+        mPrimaryUserId = getDevice().getPrimaryUserId();
+        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        if (!getDevice().setDeviceOwner(DEVICE_OWNER_COMPONENT, mPrimaryUserId)) {
+            getDevice().removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId);
+            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+            fail("Failed to set device owner");
+        }
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            // Clear the policy.
+            checkDeviceTest("testClearMandatoryBackupTransport");
+            // Re-enable backup service in case something went wrong during the test.
+            checkDeviceTest("testEnableBackupService");
+            // Re-enable backups if they were originally enabled.
+            enableBackup(mBackupsInitiallyEnabled);
+            // Restore originally selected transport.
+            if (mOriginalTransport != null) {
+                setBackupTransport(mOriginalTransport);
+            }
+        } finally {
+            getDevice().removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId);
+            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+            super.tearDown();
+        }
+    }
+
+    @Test
+    public void testMandatoryBackupTransport()  throws Exception {
+        // The backup service is initially disabled after the installation of the device owner.
+        checkDeviceTest("testBackupServiceDisabled");
+
+        // Try to set the mandatory backup transport to the local transport and verify it is
+        // set.
+        checkDeviceTest("testSetMandatoryBackupTransport");
+
+        // Verify that backups have been enabled.
+        assertTrue("Backups should be enabled after setting the mandatory backup transport",
+                isBackupEnabled());
+
+        // Get the transports; eventually wait for the backup subsystem to initialize.
+        String[] transports = waitForTransportsAndReturnTheList();
+
+        // Verify that the local transport is selected.
+        assertEquals(LOCAL_TRANSPORT, getCurrentTransport());
+
+        // Verify that switching to other than local transport, which is locked by the policy,
+        // is not possible.
+        for (String transport : transports) {
+            setBackupTransport(transport);
+            assertEquals(LOCAL_TRANSPORT, getCurrentTransport());
+        }
+
+        // Clear the mandatory backup transport policy.
+        checkDeviceTest("testClearMandatoryBackupTransport");
+
+        // Verify it is possible to switch the transports again.
+        for (String transport : transports) {
+            setBackupTransport(transport);
+            assertEquals(transport, getCurrentTransport());
+        }
+    }
+
+    @Test
+    public void testMandatoryBackupTransport_withComponents()  throws Exception {
+        // The backup service is initially disabled after the installation of the device owner.
+        checkDeviceTest("testBackupServiceDisabled");
+
+        // Try to set the mandatory backup transport to the local transport and verify it is
+        // set.
+        checkDeviceTest("testSetMandatoryBackupTransport");
+
+        // Verify that backups have been enabled.
+        assertTrue("Backups should be enabled after setting the mandatory backup transport",
+                isBackupEnabled());
+
+        // Get the transports; eventually wait for the backup subsystem to initialize.
+        String[] transportComponents = waitForTransportComponentsAndReturnTheList();
+
+        // Verify that the local transport is selected.
+        assertEquals(LOCAL_TRANSPORT, getCurrentTransport());
+
+        // Verify that switching to other than local transport, which is locked by the policy,
+        // is not possible.
+        for (String transportComponent : transportComponents) {
+            setBackupTransportComponent(transportComponent);
+            assertEquals(LOCAL_TRANSPORT, getCurrentTransport());
+        }
+
+        // Clear the mandatory backup transport policy.
+        checkDeviceTest("testClearMandatoryBackupTransport");
+
+        // Verify it is possible to switch the transports again.
+        for (String transportComponent : transportComponents) {
+            assertTrue(setBackupTransportComponent(transportComponent));
+        }
+    }
+
+    private void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
+            DeviceNotAvailableException {
+        installAppAsUser(appFileName, true, userId);
+    }
+
+    private void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        LogUtil.CLog.d("Installing app " + appFileName + " for user " + userId);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        String result = getDevice().installPackageForUser(
+                buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t");
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    private void checkDeviceTest(String methodName) throws Exception {
+        checkDeviceTest(DEVICE_OWNER_PKG, DEVICE_OWNER_TEST_NAME, methodName);
+    }
+
+    private void enableBackup(boolean enable) throws DeviceNotAvailableException {
+        getDevice().executeShellCommand("bmgr enable " + enable);
+    }
+
+    private String[] waitForTransportsAndReturnTheList()
+            throws DeviceNotAvailableException, InterruptedException {
+        long startTime = System.currentTimeMillis();
+        String output = "";
+        do {
+            output = getDevice().executeShellCommand("bmgr list transports");
+            if (!"No transports available.".equals(output.trim())) {
+                break;
+            } else {
+                output = "";
+            }
+            Thread.sleep(LIST_TRANSPORTS_RETRY_MS);
+        } while (System.currentTimeMillis() - startTime < MAX_TRANSPORTS_WAIT_MS);
+        output = output.replace("*", "");
+        return output.trim().split("\\s+");
+    }
+
+    private String[] waitForTransportComponentsAndReturnTheList()
+            throws DeviceNotAvailableException, InterruptedException {
+        long startTime = System.currentTimeMillis();
+        String[] transportComponents;
+        do {
+            String output = getDevice().executeShellCommand("bmgr list transports -c");
+            transportComponents = output.trim().split("\\s+");
+            if (transportComponents.length > 0 && !transportComponents[0].isEmpty()) {
+                break;
+            }
+            Thread.sleep(LIST_TRANSPORTS_RETRY_MS);
+        } while (System.currentTimeMillis() - startTime < MAX_TRANSPORTS_WAIT_MS);
+        return transportComponents;
+    }
+
+    private void setBackupTransport(String transport) throws DeviceNotAvailableException {
+        getDevice().executeShellCommand("bmgr transport " + transport);
+    }
+
+    private boolean setBackupTransportComponent(String transport)
+            throws DeviceNotAvailableException {
+        String output = getDevice().executeShellCommand("bmgr transport -c " + transport);
+        return output.contains("Success.");
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
index acdb423..f682a61 100644
--- a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
@@ -16,15 +16,13 @@
 
 package android.cts.backup;
 
-import static junit.framework.Assert.assertTrue;
-
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
 
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.Before;
@@ -38,18 +36,18 @@
  * Base class for CTS backup/restore hostside tests
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public abstract class BaseBackupHostSideTest extends CompatibilityHostTestBase {
+public abstract class BaseBackupHostSideTest extends BaseHostJUnit4Test {
     protected boolean mIsBackupSupported;
 
     /** Value of PackageManager.FEATURE_BACKUP */
     private static final String FEATURE_BACKUP = "android.software.backup";
 
-    private static final String LOCAL_TRANSPORT =
+    protected static final String LOCAL_TRANSPORT =
             "android/com.android.internal.backup.LocalTransport";
 
     @Before
     public void setUp() throws DeviceNotAvailableException, Exception {
-        mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
+        mIsBackupSupported = getDevice().hasFeature("feature:" + FEATURE_BACKUP);
         if (!mIsBackupSupported) {
             CLog.i("android.software.backup feature is not supported on this device");
             return;
@@ -71,7 +69,7 @@
      * Execute shell command "bmgr backupnow <packageName>" and return output from this command.
      */
     protected String backupNow(String packageName) throws DeviceNotAvailableException {
-        return mDevice.executeShellCommand("bmgr backupnow " + packageName);
+        return getDevice().executeShellCommand("bmgr backupnow " + packageName);
     }
 
     /**
@@ -96,14 +94,14 @@
      * Execute shell command "bmgr restore <packageName>" and return output from this command.
      */
     protected String restore(String packageName) throws DeviceNotAvailableException {
-        return mDevice.executeShellCommand("bmgr restore " + packageName);
+        return getDevice().executeShellCommand("bmgr restore " + packageName);
     }
 
     /**
      * Attempts to clear the device log.
      */
     protected void clearLogcat() throws DeviceNotAvailableException {
-        mDevice.executeAdbCommand("logcat", "-c");
+        getDevice().executeAdbCommand("logcat", "-c");
     }
 
     /**
@@ -174,7 +172,7 @@
 
     protected void startActivityInPackageAndWait(String packageName, String className)
             throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(String.format(
+        getDevice().executeShellCommand(String.format(
                 "am start -W -a android.intent.action.MAIN -n %s/%s.%s", packageName,
                 packageName,
                 className));
@@ -187,19 +185,19 @@
       */
     protected void clearBackupDataInLocalTransport(String packageName)
             throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
+        getDevice().executeShellCommand(String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
     }
 
     /**
      * Clears package data
      */
     protected void clearPackageData(String packageName) throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(String.format("pm clear %s", packageName));
+        getDevice().executeShellCommand(String.format("pm clear %s", packageName));
     }
 
-    private boolean isBackupEnabled() throws DeviceNotAvailableException {
+    protected boolean isBackupEnabled() throws DeviceNotAvailableException {
         boolean isEnabled;
-        String output = mDevice.executeShellCommand("bmgr enabled");
+        String output = getDevice().executeShellCommand("bmgr enabled");
         Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
         Matcher matcher = pattern.matcher(output.trim());
         if (matcher.find()) {
@@ -210,8 +208,8 @@
         return isEnabled;
     }
 
-    private String getCurrentTransport() throws DeviceNotAvailableException {
-        String output = mDevice.executeShellCommand("bmgr list transports");
+    protected String getCurrentTransport() throws DeviceNotAvailableException {
+        String output = getDevice().executeShellCommand("bmgr list transports");
         Pattern pattern = Pattern.compile("\\* (.*)");
         Matcher matcher = pattern.matcher(output);
         if (matcher.find()) {
@@ -220,4 +218,5 @@
             throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
         }
     }
+
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
index 89cfe40..6589993 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
@@ -16,7 +16,7 @@
 
 package android.cts.backup;
 
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -76,6 +76,7 @@
 
 
     @After
+    @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
diff --git a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
index a508d47..004e1cf 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
@@ -16,8 +16,6 @@
 
 package android.cts.backup;
 
-import static org.junit.Assert.assertTrue;
-
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.log.LogUtil.CLog;
diff --git a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
index a1f4927..5ce86d9 100644
--- a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
@@ -16,7 +16,7 @@
 
 package android.cts.backup;
 
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -27,8 +27,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.FileNotFoundException;
-
 /**
  * Test for checking that key/value backup and restore works correctly.
  * It interacts with the app that saves values in different shared preferences and files.
@@ -60,6 +58,7 @@
 
 
     @Before
+    @Override
     public void setUp() throws Exception {
         super.setUp();
         installPackage(KEY_VALUE_RESTORE_APP_APK);
@@ -70,6 +69,7 @@
     }
 
     @After
+    @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
index 7c1f37b..a62eab8 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
@@ -16,23 +16,17 @@
 
 package android.cts.backup;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertNull;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.FileNotFoundException;
-
 /**
  * Test checking that restoreAnyVersion manifest flag is respected by backup manager.
  *
@@ -62,6 +56,7 @@
             "CtsBackupRestoreAnyVersionNoRestoreApp.apk";
 
     @After
+    @Override
     public void tearDown() throws Exception {
         super.tearDown();
 
@@ -157,21 +152,21 @@
     }
 
     private void installRestoreAnyVersionApp()
-            throws DeviceNotAvailableException, FileNotFoundException {
+            throws DeviceNotAvailableException, TargetSetupError {
         installPackage(RESTORE_ANY_VERSION_APP_APK, "-d", "-r");
 
         checkRestoreAnyVersionDeviceTest("checkAppVersionIsOld");
     }
 
     private void installNoRestoreAnyVersionApp()
-            throws DeviceNotAvailableException, FileNotFoundException {
+            throws DeviceNotAvailableException, TargetSetupError {
         installPackage(NO_RESTORE_ANY_VERSION_APK, "-d", "-r");
 
         checkRestoreAnyVersionDeviceTest("checkAppVersionIsOld");
     }
 
     private void installNewVersionApp()
-            throws DeviceNotAvailableException, FileNotFoundException {
+            throws DeviceNotAvailableException, TargetSetupError {
         installPackage(RESTORE_ANY_VERSION_UPDATE_APK, "-d", "-r");
 
         checkRestoreAnyVersionDeviceTest("checkAppVersionIsNew");
diff --git a/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
new file mode 100644
index 0000000..414e3bc
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for checking that an observer app is notified by a broadcast Intent whenever a backup
+ * succeeds.
+ *
+ * NB: The tests use "bmgr backupnow" for backup, which works on N+ devices.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SuccessNotificationHostSideTest extends BaseBackupHostSideTest {
+
+    /** The name of the package that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_APP_PACKAGE =
+            "android.cts.backup.keyvaluerestoreapp";
+
+    /** The name of the package that a full backup will be run for */
+    private static final String FULL_BACKUP_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+    /** The name of the package that observes backup results. */
+    private static final String SUCCESS_NOTIFICATION_APP_PACKAGE =
+            "android.cts.backup.successnotificationapp";
+
+    /** The name of the device side test class in the APK that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_DEVICE_TEST_NAME =
+            KEY_VALUE_BACKUP_APP_PACKAGE + ".KeyValueBackupRestoreTest";
+
+    /** The name of the device side test class in the APK that a full backup will be run for */
+    private static final String FULL_BACKUP_DEVICE_TEST_CLASS_NAME =
+            FULL_BACKUP_APP_PACKAGE + ".FullBackupOnlyTest";
+
+    /** The name of the device side test class in the APK that observes backup results */
+    private static final String SUCCESS_NOTIFICATION_DEVICE_TEST_NAME =
+            SUCCESS_NOTIFICATION_APP_PACKAGE + ".SuccessNotificationTest";
+
+    /** The name of the APK that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_APP_APK = "CtsKeyValueBackupRestoreApp.apk";
+
+    /** The name of the APK that a full backup will be run for */
+    private static final String FULL_BACKUP_APP_APK = "FullBackupOnlyFalseWithAgentApp.apk";
+
+    /** The name of the APK that observes backup results */
+    private static final String SUCCESS_NOTIFICATION_APP_APK =
+            "CtsBackupSuccessNotificationApp.apk";
+
+    /** Secure setting that holds the backup manager configuration as key-value pairs */
+    private static final String BACKUP_MANAGER_CONSTANTS_PREF = "backup_manager_constants";
+
+    /** Key for specifying the apps that the backup manager should notify of successful backups */
+    private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS =
+            "backup_finished_notification_receivers";
+
+    /** The original backup manager configuration */
+    private String mOriginalBackupManagerConstants = null;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        installPackage(KEY_VALUE_BACKUP_APP_APK);
+        installPackage(FULL_BACKUP_APP_APK);
+
+        installPackage(SUCCESS_NOTIFICATION_APP_APK);
+        checkDeviceTest("clearBackupSuccessNotificationsReceived");
+        addBackupFinishedNotificationReceiver();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        restoreBackupFinishedNotificationReceivers();
+        assertNull(uninstallPackage(SUCCESS_NOTIFICATION_APP_PACKAGE));
+
+        // Clear backup data and uninstall the package (in that order!)
+        clearBackupDataInLocalTransport(KEY_VALUE_BACKUP_APP_PACKAGE);
+        assertNull(uninstallPackage(KEY_VALUE_BACKUP_APP_PACKAGE));
+
+        clearBackupDataInLocalTransport(FULL_BACKUP_APP_PACKAGE);
+        assertNull(uninstallPackage(FULL_BACKUP_APP_PACKAGE));
+    }
+
+    /**
+     * Test that the observer app is notified when a key/value backup succeeds.
+     *
+     * Test logic:
+     *   1. Change a test app's data, trigger a key/value backup and wait for it to complete.
+     *   2. Verify that the observer app was informed about the backup.
+     */
+    @Test
+    public void testSuccessNotificationForKeyValueBackup() throws Exception {
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        checkDeviceTest(KEY_VALUE_BACKUP_APP_PACKAGE, KEY_VALUE_BACKUP_DEVICE_TEST_NAME,
+                "saveSharedPreferencesAndNotifyBackupManager");
+        backupNowAndAssertSuccess(KEY_VALUE_BACKUP_APP_PACKAGE);
+
+        checkDeviceTest("verifyBackupSuccessNotificationReceivedForKeyValueApp");
+    }
+
+    /**
+     * Test that the observer app is notified when a full backup succeeds.
+     *
+     * Test logic:
+     *   1. Change a test app's data, trigger a full backup and wait for it to complete.
+     *   2. Verify that the observer app was informed about the backup.
+     */
+    @Test
+    public void testSuccessNotificationForFullBackup() throws Exception {
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        checkDeviceTest(FULL_BACKUP_APP_PACKAGE, FULL_BACKUP_DEVICE_TEST_CLASS_NAME, "createFiles");
+        backupNowAndAssertSuccess(FULL_BACKUP_APP_PACKAGE);
+
+        checkDeviceTest("verifyBackupSuccessNotificationReceivedForFullBackupApp");
+    }
+
+    /**
+     * Instructs the backup manager to notify the observer app whenever a backup succeeds. The old
+     * backup manager configuration is stored in a member variable and can be restored by calling
+     * {@link restoreBackupFinishedNotificationReceivers}.
+     */
+    private void addBackupFinishedNotificationReceiver()
+            throws DeviceNotAvailableException {
+        mOriginalBackupManagerConstants = getDevice().executeShellCommand(String.format(
+                "settings get secure %s", BACKUP_MANAGER_CONSTANTS_PREF)).trim();
+        if ("null".equals(mOriginalBackupManagerConstants)) {
+            mOriginalBackupManagerConstants = null;
+        }
+        String backupManagerConstants = null;
+
+        if (mOriginalBackupManagerConstants == null || mOriginalBackupManagerConstants.isEmpty()) {
+            backupManagerConstants =
+                    BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE;
+        } else {
+            final List<String> keyValuePairs =
+                    new ArrayList<>(Arrays.asList(mOriginalBackupManagerConstants.split(",")));
+            boolean present = false;
+            for (int i = 0; i < keyValuePairs.size(); ++i) {
+                final String keyValuePair = keyValuePairs.get(i);
+                final String[] fields = keyValuePair.split("=");
+                final String key = fields[0].trim();
+                if (BACKUP_FINISHED_NOTIFICATION_RECEIVERS.equals(key)) {
+                    if (fields.length == 1 || fields[1].trim().isEmpty()) {
+                        keyValuePairs.set(i, key + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+                    } else {
+                        final String[] values = fields[1].split(":");
+                        for (int j = 0; j < values.length; ++j) {
+                            if (SUCCESS_NOTIFICATION_APP_PACKAGE.equals(values[j].trim())) {
+                                present = true;
+                                break;
+                            }
+                        }
+                        if (!present) {
+                            keyValuePairs.set(i,
+                                    keyValuePair + ":" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+                        }
+                    }
+                    present = true;
+                    break;
+                }
+            }
+            if (!present) {
+                keyValuePairs.add(BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" +
+                        SUCCESS_NOTIFICATION_APP_PACKAGE);
+            }
+            backupManagerConstants = String.join(",", keyValuePairs);
+        }
+        setBackupManagerConstants(backupManagerConstants);
+    }
+
+    /**
+     * Restores the backup manager configuration stored by a previous call to
+     * {@link addBackupFinishedNotificationReceiver}.
+     */
+    private void restoreBackupFinishedNotificationReceivers() throws DeviceNotAvailableException {
+        setBackupManagerConstants(mOriginalBackupManagerConstants);
+    }
+
+    private void setBackupManagerConstants(String backupManagerConstants)
+            throws DeviceNotAvailableException {
+        getDevice().executeShellCommand(String.format("settings put secure %s %s",
+                BACKUP_MANAGER_CONSTANTS_PREF, backupManagerConstants));
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTest(SUCCESS_NOTIFICATION_APP_PACKAGE, SUCCESS_NOTIFICATION_DEVICE_TEST_NAME,
+                methodName);
+    }
+}
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
index 6d1be7d..5cac548 100644
--- a/hostsidetests/bootstats/AndroidTest.xml
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS Boot Stats host tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="sysui" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsBootStatsTestCases.jar" />
diff --git a/hostsidetests/compilation/AndroidTest.xml b/hostsidetests/compilation/AndroidTest.xml
index 9cc9066..63d53e9 100644
--- a/hostsidetests/compilation/AndroidTest.xml
+++ b/hostsidetests/compilation/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Compilation Test">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsCompilationTestCases.jar" />
diff --git a/hostsidetests/compilation/assets/primary.prof.txt b/hostsidetests/compilation/assets/primary.prof.txt
index 0a8bded..fd55262 100644
--- a/hostsidetests/compilation/assets/primary.prof.txt
+++ b/hostsidetests/compilation/assets/primary.prof.txt
@@ -1,103 +1,103 @@
 Landroid/cts/compilation/CompilationTargetActivity;
-Landroid/cts/compilation/CompilationTargetActivity;->m0()I
-Landroid/cts/compilation/CompilationTargetActivity;->m1()I
-Landroid/cts/compilation/CompilationTargetActivity;->m2()I
-Landroid/cts/compilation/CompilationTargetActivity;->m3()I
-Landroid/cts/compilation/CompilationTargetActivity;->m4()I
-Landroid/cts/compilation/CompilationTargetActivity;->m5()I
-Landroid/cts/compilation/CompilationTargetActivity;->m6()I
-Landroid/cts/compilation/CompilationTargetActivity;->m7()I
-Landroid/cts/compilation/CompilationTargetActivity;->m8()I
-Landroid/cts/compilation/CompilationTargetActivity;->m9()I
-Landroid/cts/compilation/CompilationTargetActivity;->m10()I
-Landroid/cts/compilation/CompilationTargetActivity;->m11()I
-Landroid/cts/compilation/CompilationTargetActivity;->m12()I
-Landroid/cts/compilation/CompilationTargetActivity;->m13()I
-Landroid/cts/compilation/CompilationTargetActivity;->m14()I
-Landroid/cts/compilation/CompilationTargetActivity;->m15()I
-Landroid/cts/compilation/CompilationTargetActivity;->m16()I
-Landroid/cts/compilation/CompilationTargetActivity;->m17()I
-Landroid/cts/compilation/CompilationTargetActivity;->m18()I
-Landroid/cts/compilation/CompilationTargetActivity;->m19()I
-Landroid/cts/compilation/CompilationTargetActivity;->m20()I
-Landroid/cts/compilation/CompilationTargetActivity;->m21()I
-Landroid/cts/compilation/CompilationTargetActivity;->m22()I
-Landroid/cts/compilation/CompilationTargetActivity;->m23()I
-Landroid/cts/compilation/CompilationTargetActivity;->m24()I
-Landroid/cts/compilation/CompilationTargetActivity;->m25()I
-Landroid/cts/compilation/CompilationTargetActivity;->m26()I
-Landroid/cts/compilation/CompilationTargetActivity;->m27()I
-Landroid/cts/compilation/CompilationTargetActivity;->m28()I
-Landroid/cts/compilation/CompilationTargetActivity;->m29()I
-Landroid/cts/compilation/CompilationTargetActivity;->m30()I
-Landroid/cts/compilation/CompilationTargetActivity;->m31()I
-Landroid/cts/compilation/CompilationTargetActivity;->m32()I
-Landroid/cts/compilation/CompilationTargetActivity;->m33()I
-Landroid/cts/compilation/CompilationTargetActivity;->m34()I
-Landroid/cts/compilation/CompilationTargetActivity;->m35()I
-Landroid/cts/compilation/CompilationTargetActivity;->m36()I
-Landroid/cts/compilation/CompilationTargetActivity;->m37()I
-Landroid/cts/compilation/CompilationTargetActivity;->m38()I
-Landroid/cts/compilation/CompilationTargetActivity;->m39()I
-Landroid/cts/compilation/CompilationTargetActivity;->m40()I
-Landroid/cts/compilation/CompilationTargetActivity;->m41()I
-Landroid/cts/compilation/CompilationTargetActivity;->m42()I
-Landroid/cts/compilation/CompilationTargetActivity;->m43()I
-Landroid/cts/compilation/CompilationTargetActivity;->m44()I
-Landroid/cts/compilation/CompilationTargetActivity;->m45()I
-Landroid/cts/compilation/CompilationTargetActivity;->m46()I
-Landroid/cts/compilation/CompilationTargetActivity;->m47()I
-Landroid/cts/compilation/CompilationTargetActivity;->m48()I
-Landroid/cts/compilation/CompilationTargetActivity;->m49()I
-Landroid/cts/compilation/CompilationTargetActivity;->m50()I
-Landroid/cts/compilation/CompilationTargetActivity;->m51()I
-Landroid/cts/compilation/CompilationTargetActivity;->m52()I
-Landroid/cts/compilation/CompilationTargetActivity;->m53()I
-Landroid/cts/compilation/CompilationTargetActivity;->m54()I
-Landroid/cts/compilation/CompilationTargetActivity;->m55()I
-Landroid/cts/compilation/CompilationTargetActivity;->m56()I
-Landroid/cts/compilation/CompilationTargetActivity;->m57()I
-Landroid/cts/compilation/CompilationTargetActivity;->m58()I
-Landroid/cts/compilation/CompilationTargetActivity;->m59()I
-Landroid/cts/compilation/CompilationTargetActivity;->m60()I
-Landroid/cts/compilation/CompilationTargetActivity;->m61()I
-Landroid/cts/compilation/CompilationTargetActivity;->m62()I
-Landroid/cts/compilation/CompilationTargetActivity;->m63()I
-Landroid/cts/compilation/CompilationTargetActivity;->m64()I
-Landroid/cts/compilation/CompilationTargetActivity;->m65()I
-Landroid/cts/compilation/CompilationTargetActivity;->m66()I
-Landroid/cts/compilation/CompilationTargetActivity;->m67()I
-Landroid/cts/compilation/CompilationTargetActivity;->m68()I
-Landroid/cts/compilation/CompilationTargetActivity;->m69()I
-Landroid/cts/compilation/CompilationTargetActivity;->m70()I
-Landroid/cts/compilation/CompilationTargetActivity;->m71()I
-Landroid/cts/compilation/CompilationTargetActivity;->m72()I
-Landroid/cts/compilation/CompilationTargetActivity;->m73()I
-Landroid/cts/compilation/CompilationTargetActivity;->m74()I
-Landroid/cts/compilation/CompilationTargetActivity;->m75()I
-Landroid/cts/compilation/CompilationTargetActivity;->m76()I
-Landroid/cts/compilation/CompilationTargetActivity;->m77()I
-Landroid/cts/compilation/CompilationTargetActivity;->m78()I
-Landroid/cts/compilation/CompilationTargetActivity;->m79()I
-Landroid/cts/compilation/CompilationTargetActivity;->m80()I
-Landroid/cts/compilation/CompilationTargetActivity;->m81()I
-Landroid/cts/compilation/CompilationTargetActivity;->m82()I
-Landroid/cts/compilation/CompilationTargetActivity;->m83()I
-Landroid/cts/compilation/CompilationTargetActivity;->m84()I
-Landroid/cts/compilation/CompilationTargetActivity;->m85()I
-Landroid/cts/compilation/CompilationTargetActivity;->m86()I
-Landroid/cts/compilation/CompilationTargetActivity;->m87()I
-Landroid/cts/compilation/CompilationTargetActivity;->m88()I
-Landroid/cts/compilation/CompilationTargetActivity;->m89()I
-Landroid/cts/compilation/CompilationTargetActivity;->m90()I
-Landroid/cts/compilation/CompilationTargetActivity;->m91()I
-Landroid/cts/compilation/CompilationTargetActivity;->m92()I
-Landroid/cts/compilation/CompilationTargetActivity;->m93()I
-Landroid/cts/compilation/CompilationTargetActivity;->m94()I
-Landroid/cts/compilation/CompilationTargetActivity;->m95()I
-Landroid/cts/compilation/CompilationTargetActivity;->m96()I
-Landroid/cts/compilation/CompilationTargetActivity;->m97()I
-Landroid/cts/compilation/CompilationTargetActivity;->m98()I
-Landroid/cts/compilation/CompilationTargetActivity;->m99()I
-Landroid/cts/compilation/CompilationTargetActivity;->m100()I
-Landroid/cts/compilation/CompilationTargetActivity;->m101()I
\ No newline at end of file
+SHLandroid/cts/compilation/CompilationTargetActivity;->m0()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m1()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m2()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m3()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m4()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m5()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m6()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m7()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m8()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m9()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m10()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m11()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m12()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m13()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m14()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m15()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m16()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m17()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m18()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m19()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m20()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m21()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m22()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m23()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m24()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m25()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m26()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m27()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m28()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m29()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m30()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m31()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m32()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m33()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m34()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m35()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m36()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m37()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m38()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m39()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m40()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m41()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m42()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m43()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m44()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m45()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m46()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m47()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m48()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m49()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m50()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m51()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m52()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m53()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m54()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m55()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m56()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m57()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m58()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m59()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m60()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m61()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m62()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m63()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m64()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m65()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m66()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m67()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m68()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m69()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m70()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m71()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m72()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m73()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m74()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m75()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m76()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m77()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m78()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m79()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m80()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m81()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m82()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m83()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m84()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m85()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m86()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m87()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m88()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m89()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m90()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m91()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m92()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m93()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m94()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m95()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m96()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m97()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m98()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m99()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m100()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m101()I
\ No newline at end of file
diff --git a/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java b/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
index 1f5d669..591dcb4 100644
--- a/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
+++ b/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
@@ -274,19 +274,15 @@
         executePush(apkFile.getAbsolutePath(), targetPathApk, targetDir);
         assertTrue("Failed to push APK from ", doesFileExist(targetPathApk));
         // Run profman to create the real profile on device.
-        try {
-            String pathSpec = executeSuShellAdbCommand(1, "pm", "path", APPLICATION_PACKAGE)[0];
-            pathSpec = pathSpec.replace("package:", "");
-            assertTrue("Failed find APK " + pathSpec, doesFileExist(pathSpec));
-            executeSuShellAdbCommand(
+        String pathSpec = executeSuShellAdbCommand(1, "pm", "path", APPLICATION_PACKAGE)[0];
+        pathSpec = pathSpec.replace("package:", "");
+        assertTrue("Failed find APK " + pathSpec, doesFileExist(pathSpec));
+        executeSuShellAdbCommand(
                 "profman",
                 "--create-profile-from=" + targetPathTemp,
                 "--apk=" + pathSpec,
                 "--dex-location=" + pathSpec,
                 "--reference-profile-file=" + targetPath);
-        } catch (Exception e) {
-            assertEquals("", e.toString());
-        }
         executeSuShellAdbCommand(0, "chown", owner, targetPath);
         // Verify that the file was written successfully
         assertTrue("failed to create profile file", doesFileExist(targetPath));
diff --git a/hostsidetests/content/AndroidTest.xml b/hostsidetests/content/AndroidTest.xml
index accf2a5..badbda7 100644
--- a/hostsidetests/content/AndroidTest.xml
+++ b/hostsidetests/content/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSyncContentHostTestCases.jar" />
diff --git a/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java b/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java
deleted file mode 100644
index f6f9d10..0000000
--- a/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java
+++ /dev/null
@@ -1,108 +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.content.cts;
-
-import android.appsecurity.cts.Utils;
-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;
-
-/**
- * Set of tests that verify behavior of the content framework.
- */
-public class SyncAdapterAccountAccessHostTest extends DeviceTestCase
-        implements IAbiReceiver, IBuildReceiver {
-    private static final String ACCOUNT_ACCESS_TESTS_OTHER_CERT_APK =
-            "CtsSyncAccountAccessOtherCertTestCases.apk";
-    private static final String ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG =
-            "com.android.cts.content";
-
-    private static final String ACCOUNT_ACCESS_TESTS_SAME_CERT_APK =
-            "CtsSyncAccountAccessSameCertTestCases.apk";
-    private static final String ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG =
-            "com.android.cts.content";
-
-    private static final String STUBS_APK =
-            "CtsSyncAccountAccessStubs.apk";
-    private static final String STUBS_PKG =
-            "com.android.cts.stub";
-
-    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();
-        getDevice().uninstallPackage(STUBS_PKG);
-
-        assertNull(getDevice().installPackage(mBuildHelper
-                .getTestFile(STUBS_APK), false, false));
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        getDevice().uninstallPackage(STUBS_PKG);
-    }
-
-    public void testSameCertAuthenticatorCanSeeAccount() throws Exception {
-        getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG);
-
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
-                ACCOUNT_ACCESS_TESTS_SAME_CERT_APK), false, false));
-        try {
-            runDeviceTests(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG,
-                    "com.android.cts.content.CtsSyncAccountAccessSameCertTestCases",
-                    "testAccountAccess_sameCertAsAuthenticatorCanSeeAccount");
-        } finally {
-            getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG);
-        }
-    }
-
-    public void testOtherCertAuthenticatorCanSeeAccount() throws Exception {
-        getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG);
-
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
-                ACCOUNT_ACCESS_TESTS_OTHER_CERT_APK), false, false));
-        try {
-            runDeviceTests(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG,
-                    "com.android.cts.content.CtsSyncAccountAccessOtherCertTestCases",
-                    "testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount");
-        } finally {
-            getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG);
-        }
-    }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk
deleted file mode 100644
index 1586258..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk
+++ /dev/null
@@ -1,46 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    ctstestrunner \
-    ub-uiautomator \
-    compatibility-device-util
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-  ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java \
-  ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java \
-  ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java \
-  ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
-
-LOCAL_PACKAGE_NAME := CtsSyncAccountAccessOtherCertTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
deleted file mode 100644
index 398a75b..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.content">
-
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-
-        <activity android:name=".StubActivity"/>
-
-        <service android:name=".SyncService">
-            <intent-filter>
-                <action android:name="android.content.SyncAdapter"/>
-            </intent-filter>
-            <meta-data android:name="android.content.SyncAdapter"
-                   android:resource="@xml/syncadapter" />
-        </service>
-
-    </application>
-
-    <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.content" />
-
-</manifest>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
deleted file mode 100644
index 030690a..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SyncRequest;
-import android.content.SyncResult;
-import android.content.cts.FlakyTestRule;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-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.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-/**
- * Tests whether a sync adapter can access accounts.
- */
-@RunWith(AndroidJUnit4.class)
-public class CtsSyncAccountAccessOtherCertTestCases {
-    private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
-    private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec
-
-    private static final String PERMISSION_REQUESTED = "Permission Requested";
-    public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
-
-    @Rule
-    public final TestRule mFlakyTestRule = new FlakyTestRule(3);
-
-    @Before
-    public void setUp() throws Exception {
-        allowSyncAdapterRunInBackgroundAndDataInBackground();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        disallowSyncAdapterRunInBackgroundAndDataInBackground();
-    }
-
-    @Test
-    public void testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount() throws Exception {
-        if (!hasDataConnection() || !hasNotificationSupport() || isRunningInVR()) {
-            return;
-        }
-
-        Intent intent = new Intent(getContext(), StubActivity.class);
-        Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
-        AccountManager accountManager = getContext().getSystemService(AccountManager.class);
-        Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
-                null, null).getResult();
-
-        Account addedAccount = new Account(
-                result.getString(AccountManager.KEY_ACCOUNT_NAME),
-                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-
-        waitForSyncManagerAccountChangeUpdate();
-
-        try {
-            CountDownLatch latch = new CountDownLatch(1);
-
-            SyncAdapter.setOnPerformSyncDelegate((Account account, Bundle extras,
-                    String authority, ContentProviderClient provider, SyncResult syncResult)
-                    -> latch.countDown());
-
-            Bundle extras = new Bundle();
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
-            extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
-            SyncRequest request = new SyncRequest.Builder()
-                    .setSyncAdapter(null, "com.android.cts.stub.provider")
-                    .syncOnce()
-                    .setExtras(extras)
-                    .setExpedited(true)
-                    .setManual(true)
-                    .build();
-            ContentResolver.requestSync(request);
-
-            assertFalse(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-
-            UiDevice uiDevice = getUiDevice();
-            if (isWatch()) {
-                UiObject2 notification = findPermissionNotificationInStream(uiDevice);
-                notification.click();
-            } else {
-                uiDevice.openNotification();
-                uiDevice.wait(Until.hasObject(By.text(PERMISSION_REQUESTED)),
-                        UI_TIMEOUT_MILLIS);
-
-                uiDevice.findObject(By.text(PERMISSION_REQUESTED)).click();
-            }
-
-            uiDevice.wait(Until.hasObject(By.text("ALLOW")),
-                    UI_TIMEOUT_MILLIS);
-
-            uiDevice.findObject(By.text("ALLOW")).click();
-
-            ContentResolver.requestSync(request);
-
-            assertTrue(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } finally {
-            // Ask the differently signed authenticator to drop all accounts
-            accountManager.getAuthToken(addedAccount, TOKEN_TYPE_REMOVE_ACCOUNTS,
-                    null, false, null, null);
-            activity.finish();
-        }
-    }
-
-    private UiObject2 findPermissionNotificationInStream(UiDevice uiDevice) {
-        uiDevice.pressHome();
-        swipeUp(uiDevice);
-        if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
-          return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
-        }
-        for (int i = 0; i < 100; i++) {
-          if (!swipeUp(uiDevice)) {
-            // We have reached the end of the stream and not found the target.
-            break;
-          }
-          if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
-            return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
-          }
-        }
-        return null;
-    }
-
-    private boolean swipeUp(UiDevice uiDevice) {
-        int width = uiDevice.getDisplayWidth();
-        int height = uiDevice.getDisplayHeight();
-        return uiDevice.swipe(
-            width / 2 /* startX */,
-            height - 1 /* startY */,
-            width / 2 /* endX */,
-            1 /* endY */,
-            50 /* numberOfSteps */);
-    }
-
-    private boolean isWatch() {
-        return (getContext().getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
-    }
-
-    private Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getContext();
-    }
-
-    private UiDevice getUiDevice() {
-        return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-    }
-
-    private void waitForSyncManagerAccountChangeUpdate() {
-        // Wait for the sync manager to be notified for the new account.
-        // Unfortunately, there is no way to detect this event, sigh...
-        SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
-    }
-
-    private boolean hasDataConnection() {
-        ConnectivityManager connectivityManager = getContext().getSystemService(
-                ConnectivityManager.class);
-        NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
-        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
-    }
-
-    private boolean hasNotificationSupport() {
-        final PackageManager manager = getContext().getPackageManager();
-        return !manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-                && !manager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
-    }
-
-    private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
-        // Allow us to run in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd deviceidle whitelist +" + getContext().getPackageName());
-        // Allow us to use data in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
-    }
-
-    private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
-        // Allow us to run in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd deviceidle whitelist -" + getContext().getPackageName());
-        // Allow us to use data in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
-    }
-
-    private boolean isRunningInVR() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        return ((context.getResources().getConfiguration().uiMode &
-                 Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_VR_HEADSET);
-    }
-
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
deleted file mode 100644
index 4e0a034..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
+++ /dev/null
@@ -1,40 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    ctstestrunner \
-    ub-uiautomator \
-    compatibility-device-util
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSyncAccountAccessSameCertTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml
deleted file mode 100644
index 2ecd27d..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.content">
-
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-
-        <activity android:name=".StubActivity"/>
-
-        <service android:name=".SyncService">
-            <intent-filter>
-                <action android:name="android.content.SyncAdapter"/>
-            </intent-filter>
-            <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/syncadapter" />
-        </service>
-
-    </application>
-
-    <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.content" />
-
-</manifest>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml
deleted file mode 100644
index f55a19a..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml
+++ /dev/null
@@ -1,25 +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.
--->
-
-<sync-adapter
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:contentAuthority="com.android.cts.stub.provider"
-    android:accountType="com.stub"
-    android:userVisible="false"
-    android:supportsUploading="false"
-    android:allowParallelSyncs="false"
-    android:isAlwaysSyncable="true">
-</sync-adapter>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java
deleted file mode 100644
index bfdd072..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import static junit.framework.Assert.assertTrue;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SyncRequest;
-import android.content.SyncResult;
-import android.content.cts.FlakyTestRule;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-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.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-/**
- * Tests whether a sync adapter can access accounts.
- */
-@RunWith(AndroidJUnit4.class)
-public class CtsSyncAccountAccessSameCertTestCases {
-    private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
-
-    @Rule
-    public final TestRule mFlakyTestTRule = new FlakyTestRule(3);
-
-    @Before
-    public void setUp() throws Exception {
-        allowSyncAdapterRunInBackgroundAndDataInBackground();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        disallowSyncAdapterRunInBackgroundAndDataInBackground();
-    }
-
-    @Test
-    public void testAccountAccess_sameCertAsAuthenticatorCanSeeAccount() throws Exception {
-        if (!hasDataConnection() || !hasNotificationSupport()) {
-            return;
-        }
-
-        Intent intent = new Intent(getContext(), StubActivity.class);
-        Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
-        AccountManager accountManager = getContext().getSystemService(AccountManager.class);
-        Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
-                null, null).getResult();
-
-        Account addedAccount = new Account(
-                result.getString(AccountManager.KEY_ACCOUNT_NAME),
-                        result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-
-        waitForSyncManagerAccountChangeUpdate();
-
-        try {
-            CountDownLatch latch = new CountDownLatch(1);
-
-            SyncAdapter.setOnPerformSyncDelegate((Account account, Bundle extras,
-                    String authority, ContentProviderClient provider, SyncResult syncResult)
-                    -> latch.countDown());
-
-            Bundle extras = new Bundle();
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
-            extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
-            SyncRequest request = new SyncRequest.Builder()
-                    .setSyncAdapter(null, "com.android.cts.stub.provider")
-                    .syncOnce()
-                    .setExtras(extras)
-                    .setExpedited(true)
-                    .setManual(true)
-                    .build();
-            ContentResolver.requestSync(request);
-
-            assertTrue(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } finally {
-            accountManager.removeAccount(addedAccount, activity, null, null);
-            activity.finish();
-        }
-    }
-
-    private Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getContext();
-    }
-
-    private void waitForSyncManagerAccountChangeUpdate() {
-        // Wait for the sync manager to be notified for the new account.
-        // Unfortunately, there is no way to detect this event, sigh...
-        SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
-    }
-
-    private boolean hasDataConnection() {
-        ConnectivityManager connectivityManager = getContext().getSystemService(
-                ConnectivityManager.class);
-        NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
-        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
-    }
-
-    private boolean hasNotificationSupport() {
-        return !getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
-        // Allow us to run in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd deviceidle whitelist +" + getContext().getPackageName());
-        // Allow us to use data in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
-    }
-
-    private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
-        // Allow us to run in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd deviceidle whitelist -" + getContext().getPackageName());
-        // Allow us to use data in the background
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
-    }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
deleted file mode 100644
index e5664f1..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
+++ /dev/null
@@ -1,52 +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
- */
-
-package android.content.cts;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Rule for running flaky tests that runs the test up to attempt
- * count and if one run succeeds reports the tests as passing.
- */
-// TODO: Move this puppy in a common place, so ppl can use it.
-public class FlakyTestRule implements TestRule {
-    private final int mAttemptCount;
-
-    public FlakyTestRule(int attemptCount) {
-        mAttemptCount = attemptCount;
-    }
-
-    @Override
-    public Statement apply(Statement statement, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                Throwable throwable = null;
-                for (int i = 0; i < mAttemptCount; i++) {
-                    try {
-                        statement.evaluate();
-                        return;
-                    } catch (Throwable t) {
-                        throwable = t;
-                    }
-                }
-                throw throwable;
-            };
-        };
-    }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java
deleted file mode 100644
index e93a070..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import android.accounts.Account;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.content.SyncResult;
-import android.os.Bundle;
-
-public class SyncAdapter extends AbstractThreadedSyncAdapter {
-    private static final Object sLock = new Object();
-
-    private static OnPerformSyncDelegate sOnPerformSyncDelegate;
-
-    public interface OnPerformSyncDelegate {
-        void onPerformSync(Account account, Bundle extras, String authority,
-                ContentProviderClient provider, SyncResult syncResult);
-    }
-
-    public static void setOnPerformSyncDelegate(OnPerformSyncDelegate delegate) {
-        synchronized (sLock) {
-            sOnPerformSyncDelegate = delegate;
-        }
-    }
-
-    public SyncAdapter(Context context, boolean autoInitialize) {
-        super(context, autoInitialize);
-    }
-
-    @Override
-    public void onPerformSync(Account account, Bundle extras, String authority,
-            ContentProviderClient provider, SyncResult syncResult) {
-        OnPerformSyncDelegate delegate;
-        synchronized (sLock) {
-            delegate = sOnPerformSyncDelegate;
-        }
-        if (delegate != null) {
-            delegate.onPerformSync(account, extras, authority, provider, syncResult);
-        }
-    }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java
deleted file mode 100644
index e8da82c..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class SyncService extends Service {
-    private SyncAdapter mInstance;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mInstance = new SyncAdapter(this, false);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mInstance.getSyncAdapterBinder();
-    }
-}
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml b/hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml
deleted file mode 100644
index 76c4fb4..0000000
--- a/hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml
+++ /dev/null
@@ -1,40 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.stub">
-
-    <application>
-        <service
-                android:name=".StubAuthenticator">
-            <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator"/>
-            </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
-        </service>
-
-        <provider
-            android:name=".StubProvider"
-            android:authorities="com.android.cts.stub.provider"
-            android:exported="true"
-            android:syncable="true">
-        </provider>
-
-    </application>
-
-</manifest>
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java b/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
deleted file mode 100644
index eec9d21..0000000
--- a/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.stub;
-
-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 StubAuthenticator extends Service {
-    public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
-
-    private Authenticator mAuthenticator;
-
-    @Override
-    public void onCreate() {
-        mAuthenticator = new Authenticator(this);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mAuthenticator.getIBinder();
-    }
-
-    public class Authenticator extends AbstractAccountAuthenticator {
-        public Authenticator(Context context) {
-            super(context);
-            removeAccounts();
-        }
-
-        @Override
-        public Bundle editProperties(AccountAuthenticatorResponse response,
-                String accountType) {
-            return null;
-        }
-
-        @Override
-        public Bundle addAccount(AccountAuthenticatorResponse response,
-                String accountType, String tokenType, String[] strings,
-                Bundle bundle) throws NetworkErrorException {
-            AccountManager accountManager = getSystemService(AccountManager.class);
-            accountManager.addAccountExplicitly(new Account("foo", accountType), "bar", null);
-
-            Bundle result = new Bundle();
-            result.putString(AccountManager.KEY_ACCOUNT_NAME, "foo");
-            result.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
-            response.onResult(result);
-
-            return null;
-        }
-
-        @Override
-        public Bundle confirmCredentials(AccountAuthenticatorResponse response,
-                Account account, Bundle bundle) throws NetworkErrorException {
-            return null;
-        }
-
-        @Override
-        public Bundle getAuthToken(AccountAuthenticatorResponse response,
-                Account account, String type, Bundle bundle) throws NetworkErrorException {
-            if (TOKEN_TYPE_REMOVE_ACCOUNTS.equals(type)) {
-                removeAccounts();
-            }
-            return null;
-        }
-
-        @Override
-        public String getAuthTokenLabel(String tokenName) {
-            return null;
-        }
-
-        @Override
-        public Bundle updateCredentials(AccountAuthenticatorResponse response,
-                Account account, String tokenType, Bundle bundle)
-                throws NetworkErrorException {
-            return null;
-        }
-
-        @Override
-        public Bundle hasFeatures(AccountAuthenticatorResponse response,
-                Account account, String[] options) throws NetworkErrorException {
-            return null;
-        }
-
-        @Override
-        public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
-                Account account) throws NetworkErrorException {
-            Bundle result = new Bundle();
-            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
-            return result;
-        }
-
-        private void removeAccounts() {
-            AccountManager accountManager = getSystemService(AccountManager.class);
-            for (Account account : accountManager.getAccounts()) {
-                accountManager.removeAccountExplicitly(account);
-            }
-        }
-    }
-}
diff --git a/hostsidetests/cpptools/AndroidTest.xml b/hostsidetests/cpptools/AndroidTest.xml
index 94a1154..6e60080 100644
--- a/hostsidetests/cpptools/AndroidTest.xml
+++ b/hostsidetests/cpptools/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CPP host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="devtools" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/deviceidle/Android.mk b/hostsidetests/deviceidle/Android.mk
new file mode 100644
index 0000000..e73d70f
--- /dev/null
+++ b/hostsidetests/deviceidle/Android.mk
@@ -0,0 +1,32 @@
+# 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 := CtsDeviceIdleHostTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_CTS_TEST_PACKAGE := android.deviceidle
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/deviceidle/AndroidTest.xml b/hostsidetests/deviceidle/AndroidTest.xml
new file mode 100644
index 0000000..fecade9
--- /dev/null
+++ b/hostsidetests/deviceidle/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?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 the CTS Content host tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsDeviceIdleHostTestCases.jar" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java b/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java
new file mode 100644
index 0000000..bdba196
--- /dev/null
+++ b/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.deviceidle;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that it is possible to remove apps from the system whitelist
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class DeviceIdleWhitelistTest extends BaseHostJUnit4Test {
+
+    private static final String DEVICE_IDLE_COMMAND_PREFIX = "cmd deviceidle sys-whitelist ";
+    private static final String RESET_SYS_WHITELIST_COMMAND = "cmd deviceidle sys-whitelist reset";
+    private static final String SHOW_SYS_WHITELIST_COMMAND = DEVICE_IDLE_COMMAND_PREFIX;
+
+    private List<String> mOriginalSystemWhitelist;
+
+    @Before
+    public void setUp() throws Exception {
+        getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+        mOriginalSystemWhitelist = getSystemWhitelist();
+        if (mOriginalSystemWhitelist.size() < 1) {
+            LogUtil.CLog.w("No packages found in system whitelist");
+            Assume.assumeTrue(false);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+    }
+
+    @Test
+    public void testRemoveFromSysWhitelist() throws Exception {
+        final String packageToRemove = mOriginalSystemWhitelist.get(0);
+        getDevice().executeShellCommand(DEVICE_IDLE_COMMAND_PREFIX + "-" + packageToRemove);
+        final List<String> newWhitelist = getSystemWhitelist();
+        assertFalse("Package " + packageToRemove + " not removed from whitelist",
+                newWhitelist.contains(packageToRemove));
+    }
+
+    @Test
+    public void testRemovesPersistedAcrossReboots() throws Exception {
+        for (int i = 0; i < mOriginalSystemWhitelist.size(); i+=2) {
+            // remove odd indexed packages from the whitelist
+            getDevice().executeShellCommand(
+                    DEVICE_IDLE_COMMAND_PREFIX + "-" + mOriginalSystemWhitelist.get(i));
+        }
+        final List<String> whitelistBeforeReboot = getSystemWhitelist();
+        Thread.sleep(10_000); // write to disk happens after 5 seconds
+        getDevice().reboot();
+        Thread.sleep(5_000); // to make sure service is initialized
+        final List<String> whitelistAfterReboot = getSystemWhitelist();
+        assertEquals(whitelistBeforeReboot.size(), whitelistAfterReboot.size());
+        for (int i = 0; i < whitelistBeforeReboot.size(); i++) {
+            assertTrue(whitelistAfterReboot.contains(whitelistBeforeReboot.get(i)));
+        }
+    }
+
+    private List<String> getSystemWhitelist() throws DeviceNotAvailableException {
+        final String output = getDevice().executeShellCommand(SHOW_SYS_WHITELIST_COMMAND).trim();
+        final List<String> packages = new ArrayList<>();
+        for (String line : output.split("\n")) {
+            final int i = line.indexOf(',');
+            packages.add(line.substring(0, i));
+        }
+        return packages;
+    }
+}
diff --git a/hostsidetests/devicepolicy/Android.mk b/hostsidetests/devicepolicy/Android.mk
index 9cbd49e..95af5e7 100644
--- a/hostsidetests/devicepolicy/Android.mk
+++ b/hostsidetests/devicepolicy/Android.mk
@@ -27,7 +27,7 @@
 LOCAL_CTS_TEST_PACKAGE := android.adminhostside
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := cts arcts vts general-tests
 
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index 51711dd..6fc38c8 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
 
     <!-- Push the list of public APIs to device -->
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
index 0873334..2943695 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
@@ -31,10 +31,9 @@
     android-support-v4  \
     ctstestrunner  \
     ub-uiautomator  \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
index 1f488e5..97659b3 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
@@ -27,6 +27,8 @@
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <service android:name="com.android.cts.devicepolicy.accountcheck.TestAuthenticator"
             android:exported="true">
             <intent-filter>
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.mk b/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
index b7d99bf..ad93eb4 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 # Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 LOCAL_PACKAGE_NAME := CtsAccountManagementDevicePolicyApp
 
@@ -31,8 +31,9 @@
     android-support-v4 \
     ctstestrunner \
     ub-uiautomator \
-    android-support-test \
-    legacy-android-test
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
index b1e1b53..9c31a62 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <service android:name=".MockAccountService" android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator" />
diff --git a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
index 81b63b2..78795cf 100644
--- a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
@@ -27,6 +27,6 @@
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/Assistant/Android.mk b/hostsidetests/devicepolicy/app/Assistant/Android.mk
index 86144a2..308c0a1 100644
--- a/hostsidetests/devicepolicy/app/Assistant/Android.mk
+++ b/hostsidetests/devicepolicy/app/Assistant/Android.mk
@@ -29,7 +29,11 @@
 
 LOCAL_PACKAGE_NAME := CtsDevicePolicyAssistApp
 
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    compatibility-device-util \
+    android-support-test \
+
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml b/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
index 5fc20de..17ca642 100644
--- a/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="com.android.cts.devicepolicy.assistapp" >
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
 
         <service android:name=".MyInteractionService"
                 android:label="CTS test voice interaction service"
@@ -45,4 +47,4 @@
             android:targetPackage="com.android.cts.devicepolicy.assistapp"
             android:label="Assistant related device policy CTS" />
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/Android.mk b/hostsidetests/devicepolicy/app/AutofillApp/Android.mk
index 522b196..80a8b60 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/AutofillApp/Android.mk
@@ -32,6 +32,6 @@
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
index cf6b6e8..711f984 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
@@ -28,7 +28,7 @@
 
         <service
             android:name=".SimpleAutofillService"
-            android:permission="android.permission.BIND_AUTOFILL" >
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
             <intent-filter>
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/Android.mk b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
index 23eb1f7..86440fc 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
+++ b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
index c8872b4..020d0bd 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
@@ -32,7 +32,7 @@
 
 LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
@@ -57,7 +57,7 @@
 
 LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
index bc8d3d3..938ee07 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ManagementTest.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ManagementTest.java
index b7220be..f6267ca 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ManagementTest.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ManagementTest.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import java.util.List;
@@ -97,4 +98,25 @@
         MoreAsserts.assertEmpty(mDevicePolicyManager.getBindDeviceAdminTargetUsers(
                 AdminReceiver.getComponentName(mContext)));
     }
+
+    public void testCannotStartManagedProfileInBackground() {
+        UserHandle profileUserHandle = Utils.getOtherProfile(mContext);
+        assertNotNull(profileUserHandle);
+        assertEquals(UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE,
+                mDevicePolicyManager.startUserInBackground(AdminReceiver.getComponentName(mContext),
+                        profileUserHandle));
+    }
+
+    public void testCannotStopManagedProfile() {
+        UserHandle profileUserHandle = Utils.getOtherProfile(mContext);
+        assertNotNull(profileUserHandle);
+        assertEquals(UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE,
+                mDevicePolicyManager.stopUser(AdminReceiver.getComponentName(mContext),
+                        profileUserHandle));
+    }
+
+    public void testCannotLogoutManagedProfile() {
+        assertEquals(UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE,
+                mDevicePolicyManager.logoutUser(AdminReceiver.getComponentName(mContext)));
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
new file mode 100644
index 0000000..dd20c9a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
@@ -0,0 +1,41 @@
+# 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_PACKAGE_NAME := CtsCrossProfileAppsTests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES = \
+	android-support-v4 \
+	ctstestrunner \
+	android-support-test \
+	truth-prebuilt \
+	ub-uiautomator
+
+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/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.xml
new file mode 100644
index 0000000..79093d6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.crossprofileappstest">
+
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".MainActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NonMainActivity" android:exported="true"/>
+
+        <activity android:name=".NonExportedActivity" android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.crossprofileappstest"
+                     android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml
new file mode 100644
index 0000000..877d890
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/user_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java
new file mode 100644
index 0000000..ee7af51
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.crossprofileappstest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.CrossProfileApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+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.List;
+
+/**
+ * Test that runs {@link CrossProfileApps} APIs against non-valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrossProfileAppsNonTargetUserTest {
+    private static final String PARAM_TARGET_USER = "TARGET_USER";
+
+    private CrossProfileApps mCrossProfileApps;
+    private UserHandle mTargetUser;
+    private Context mContext;
+
+    @Before
+    public void setupCrossProfileApps() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+    }
+
+    @Before
+    public void readTargetUser() {
+        Context context = InstrumentationRegistry.getContext();
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        UserManager userManager = context.getSystemService(UserManager.class);
+        final int userSn = Integer.parseInt(arguments.getString(PARAM_TARGET_USER));
+        mTargetUser = userManager.getUserForSerialNumber(userSn);
+    }
+
+    @Test
+    public void testTargetUserIsNotInGetTargetProfiles() {
+        List<UserHandle> targetProfiles = mCrossProfileApps.getTargetUserProfiles();
+        assertThat(targetProfiles).doesNotContain(mTargetUser);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotStartActivity() {
+        mCrossProfileApps.startMainActivity(
+                MainActivity.getComponentName(mContext), mTargetUser);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotGetProfileSwitchingLabel() throws Exception {
+        mCrossProfileApps.getProfileSwitchingLabel(mTargetUser);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotGetProfileSwitchingIconDrawable() throws Exception {
+        mCrossProfileApps.getProfileSwitchingIconDrawable(mTargetUser);
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java
new file mode 100644
index 0000000..31baa79
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.crossprofileappstest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.CrossProfileApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+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.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test that runs {@link CrossProfileApps} APIs against valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrossProfileAppsTargetUserTest {
+    private static final String PARAM_TARGET_USER = "TARGET_USER";
+    private static final String ID_USER_TEXTVIEW =
+            "com.android.cts.crossprofileappstest:id/user_textview";
+    private static final long TIMEOUT_WAIT_UI = TimeUnit.SECONDS.toMillis(10);
+
+    private CrossProfileApps mCrossProfileApps;
+    private UserHandle mTargetUser;
+    private Context mContext;
+    private UiDevice mDevice;
+    private long mUserSerialNumber;
+
+    @Before
+    public void setupCrossProfileApps() {
+        mContext = InstrumentationRegistry.getContext();
+        mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+    }
+
+    @Before
+    public void wakeupDeviceAndPressHome() throws Exception {
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mDevice.wakeUp();
+        mDevice.pressMenu();
+        mDevice.pressHome();
+    }
+
+    @Before
+    public void readTargetUser() {
+        Context context = InstrumentationRegistry.getContext();
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        UserManager userManager = context.getSystemService(UserManager.class);
+        mUserSerialNumber = Long.parseLong(arguments.getString(PARAM_TARGET_USER));
+        mTargetUser = userManager.getUserForSerialNumber(mUserSerialNumber);
+        assertNotNull(mTargetUser);
+    }
+
+    @After
+    public void pressHome() {
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTargetUserIsIngetTargetUserProfiles() {
+        List<UserHandle> targetProfiles = mCrossProfileApps.getTargetUserProfiles();
+        assertThat(targetProfiles).contains(mTargetUser);
+    }
+
+    /**
+     * Verify we succeed to start the activity in another profile by checking UI element.
+     */
+    @Test
+    public void testCanStartMainActivity() throws Exception {
+        mCrossProfileApps.startMainActivity(
+                MainActivity.getComponentName(mContext), mTargetUser);
+
+        // Look for the text view to verify that MainActivity is started.
+        UiObject2 textView = mDevice.wait(
+                Until.findObject(By.res(ID_USER_TEXTVIEW)),
+                TIMEOUT_WAIT_UI);
+        assertNotNull("Failed to start activity in target user", textView);
+        // Look for the text in textview, it should be the serial number of target user.
+        assertEquals("Activity is started in wrong user",
+                String.valueOf(mUserSerialNumber),
+                textView.getText());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotStartNotExportedActivity() throws Exception {
+        mCrossProfileApps.startMainActivity(
+                NonExportedActivity.getComponentName(mContext), mTargetUser);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotStartNonMainActivity() throws Exception {
+        mCrossProfileApps.startMainActivity(
+                NonMainActivity.getComponentName(mContext), mTargetUser);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testCannotStartActivityInOtherPackage() throws Exception {
+        mCrossProfileApps.startMainActivity(new ComponentName(
+                "com.android.cts.launcherapps.simpleapp",
+                "com.android.cts.launcherapps.simpleapp.SimpleActivity"),
+                mTargetUser
+        );
+    }
+
+    @Test
+    public void testGetProfileSwitchingLabel() throws Exception {
+        assertNotNull(mCrossProfileApps.getProfileSwitchingLabel(mTargetUser));
+    }
+
+    @Test
+    public void testGetProfileSwitchingIconDrawable() throws Exception {
+        assertNotNull(mCrossProfileApps.getProfileSwitchingIconDrawable(mTargetUser));
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java
new file mode 100644
index 0000000..c137e80
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java
@@ -0,0 +1,56 @@
+/*
+ * 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.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.lang.Override;
+
+/**
+ * An dummy activity that displays the serial number of the user that it is running into.
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        TextView textView = findViewById(R.id.user_textview);
+        textView.setText(Long.toString(getCurrentUserSerialNumber()));
+    }
+
+    public static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, MainActivity.class);
+    }
+
+    private long getCurrentUserSerialNumber() {
+        UserManager userManager = getSystemService(UserManager.class);
+        return userManager.getSerialNumberForUser(Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
new file mode 100644
index 0000000..0876694
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class NonExportedActivity extends Activity {
+
+    public static ComponentName getComponentName(Context context ){
+        return new ComponentName(context, NonExportedActivity.class);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
new file mode 100644
index 0000000..56ec466
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class NonMainActivity extends Activity {
+
+    public static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, NonMainActivity.class);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
index 1e6e2f1..81d98f1 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
@@ -16,7 +16,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 LOCAL_PACKAGE_NAME := CtsCustomizationApp
 
@@ -26,8 +26,12 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
index 4b20829..be6249f 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
index a46eed8..b9b0949 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
@@ -24,17 +24,16 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES = \
     android-support-v4 \
     ctstestrunner \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
index a8f4f05..5cf7f05 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
@@ -28,10 +28,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    compatibility-device-util \
-    legacy-android-test
+    compatibility-device-util
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
index 4e2cfb6..a7c7470 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
@@ -28,14 +28,13 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    compatibility-device-util \
-    legacy-android-test
+    compatibility-device-util
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+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 := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
index 72d2bb0..55cf2b3 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
@@ -28,6 +28,8 @@
 
 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
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
index e2f9b8d..8725fb7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
@@ -28,6 +28,8 @@
 
 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
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
index b88d537..fdc7e7a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
@@ -28,6 +28,8 @@
 
 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
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
index 7f72ddf..02bf1e5 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
@@ -28,6 +28,8 @@
 
 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
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
index 8c66638..cf21905 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
@@ -28,6 +28,8 @@
 
 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
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
index c818856..7c4902e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsDeviceAndProfileOwnerApp23
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_MODULE_TAGS := optional
 
@@ -24,12 +25,14 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
-LOCAL_JAVA_LIBRARIES = conscrypt legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    conscrypt \
+    android.test.runner.stubs \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
 
-LOCAL_SDK_VERSION := test_current
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../res
 
 # tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index 1c50763..03ccaea 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsDeviceAndProfileOwnerApp25
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_MODULE_TAGS := optional
 
@@ -24,12 +25,14 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
-LOCAL_JAVA_LIBRARIES = conscrypt legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    conscrypt \
+    android.test.runner.stubs \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
 
-LOCAL_SDK_VERSION := test_current
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../res
 
 # tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 81a23d2..1a66c0b 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsDeviceAndProfileOwnerApp
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_MODULE_TAGS := optional
 
@@ -24,15 +25,17 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
-LOCAL_JAVA_LIBRARIES = conscrypt
+LOCAL_JAVA_LIBRARIES := \
+    conscrypt \
+    android.test.runner.stubs \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
 
-LOCAL_SDK_VERSION := test_current
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../res
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index 0dc8bc3..515db7c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -19,6 +19,9 @@
 
     <uses-sdk android:minSdkVersion="23"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.INTERNET" />
@@ -68,6 +71,8 @@
         </activity>
 
         <activity android:name="com.android.cts.deviceandprofileowner.AutofillActivity"/>
+
+        <activity android:name=".PrintActivity"/>
     </application>
 
     <instrumentation
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AllowedAccountManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AllowedAccountManagementTest.java
new file mode 100644
index 0000000..971e711
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AllowedAccountManagementTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.deviceandprofileowner;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+
+import java.io.IOException;
+
+/**
+ * These tests verify that the device / profile owner can use account management APIs to add
+ * accounts even when policies are set. The policies tested are
+ * {@link DevicePolicyManager#setAccountManagementDisabled} and
+ * {@link UserManager#DISALLOW_MODIFY_ACCOUNTS}.
+ *
+ * This test depends on {@link com.android.cts.devicepolicy.accountmanagement.MockAccountService},
+ * which provides authenticator for a mock account type.
+ */
+public class AllowedAccountManagementTest extends BaseDeviceAdminTest {
+
+    // Account type for MockAccountAuthenticator
+    private final static String ACCOUNT_TYPE_1 =
+            "com.android.cts.devicepolicy.accountmanagement.account.type";
+    private final static String ACCOUNT_TYPE_2 = "com.dummy.account";
+    private final static Account ACCOUNT = new Account("user0", ACCOUNT_TYPE_1);
+
+    private AccountManager mAccountManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
+        clearAllAccountManagementDisabled();
+        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_MODIFY_ACCOUNTS);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        clearAllAccountManagementDisabled();
+        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_MODIFY_ACCOUNTS);
+        super.tearDown();
+    }
+
+    public void testAccountManagementDisabled_setterAndGetter() {
+        // Some local tests: adding and removing disabled accounts and make sure
+        // DevicePolicyManager keeps track of the disabled set correctly
+        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
+
+        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
+                true);
+        // Test if disabling ACCOUNT_TYPE_2 affects ACCOUNT_TYPE_1
+        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_2,
+                false);
+        assertEquals(1, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
+        assertEquals(ACCOUNT_TYPE_1,
+                mDevicePolicyManager.getAccountTypesWithManagementDisabled()[0]);
+
+        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
+                false);
+        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
+    }
+
+    public void testAccountManagementDisabled_profileAndDeviceOwnerCanAddAccount()
+            throws AuthenticatorException, IOException, OperationCanceledException {
+        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
+                true);
+
+        assertEquals(0, mAccountManager.getAccountsByType(ACCOUNT_TYPE_1).length);
+        // Management is disabled, but the device / profile owner is still allowed to use the APIs
+        Bundle result = mAccountManager.addAccount(ACCOUNT_TYPE_1,
+                null, null, null, null, null, null).getResult();
+
+        // Normally the expected result of addAccount() is AccountManager returning
+        // an intent to start the authenticator activity for adding new accounts.
+        // But MockAccountAuthenticator returns a new account straightway.
+        assertEquals(ACCOUNT_TYPE_1, result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+    }
+
+    public void testUserRestriction_profileAndDeviceOwnerCanAddAndRemoveAccount()
+            throws AuthenticatorException, IOException, OperationCanceledException {
+        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_MODIFY_ACCOUNTS);
+
+        assertEquals(0, mAccountManager.getAccountsByType(ACCOUNT_TYPE_1).length);
+        // Management is disabled, but the device / profile owner is still allowed to use the APIs
+        Bundle result = mAccountManager.addAccount(ACCOUNT_TYPE_1,
+                null, null, null, null, null, null).getResult();
+
+        // Normally the expected result of addAccount() is AccountManager returning
+        // an intent to start the authenticator activity for adding new accounts.
+        // But MockAccountAuthenticator returns a new account straightway.
+        assertEquals(ACCOUNT_TYPE_1, result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+        result = mAccountManager.removeAccount(ACCOUNT, null, null, null).getResult();
+        assertTrue(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
+    }
+
+    public void testRemoveAccount_noUserRestriction()
+            throws AuthenticatorException, IOException, OperationCanceledException {
+        // We only want to verify removeAccount can through to AccountManagerService without
+        // throwing an Exception, so it's not necessary to add the account before removal.
+        Bundle result = mAccountManager.removeAccount(ACCOUNT, null, null, null).getResult();
+        assertTrue(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
+    }
+
+    private void clearAllAccountManagementDisabled() {
+        for (String accountType : mDevicePolicyManager.getAccountTypesWithManagementDisabled()) {
+            mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, accountType,
+                    false);
+        }
+        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
index efc7115..f26d228 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
@@ -83,8 +83,8 @@
         }
 
         try {
-            // Set volume of ringtone to be 1.
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, /* flag= */ 0);
+            // Set volume of music to be 1.
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, /* flag= */ 0);
 
             // Disallow adjusting volume.
             mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
@@ -93,7 +93,7 @@
 
             // Verify that volume can't be changed.
             mAudioManager.adjustVolume(AudioManager.ADJUST_RAISE, /* flag= */ 0);
-            assertEquals(1, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            assertEquals(1, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
 
             // Allowing adjusting volume.
             mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
@@ -105,7 +105,7 @@
             waitUntil(2, new Callable<Integer>() {
                 @Override
                 public Integer call() throws Exception {
-                    return mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
+                    return mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
                 }
             });
         } finally {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
new file mode 100644
index 0000000..9b1ae92
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.android.cts.deviceandprofileowner;
+
+import android.os.AsyncTask;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class that calls DPM.clearApplicationUserData and verifies that it doesn't time out.
+ */
+public class ClearApplicationDataTest extends BaseDeviceAdminTest {
+    private static final String TEST_PKG = "com.android.cts.intent.receiver";
+    private static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+    private static final Semaphore mSemaphore = new Semaphore(0);
+    private static final long CLEAR_APPLICATION_DATA_TIMEOUT_S = 10;
+
+    public void testClearApplicationData_testPkg() throws Exception {
+        clearApplicationDataTest(TEST_PKG, /* shouldSucceed */ true);
+    }
+
+    public void testClearApplicationData_deviceProvisioning() throws Exception {
+        String deviceProvisioningPackageName = getDeviceProvisioningPackageName();
+        if (deviceProvisioningPackageName == null) {
+            return;
+        }
+        clearApplicationDataTest(deviceProvisioningPackageName, /* shouldSucceed */ false);
+    }
+
+    public void testClearApplicationData_activeAdmin() throws Exception {
+        clearApplicationDataTest(DEVICE_ADMIN_PKG, /* shouldSucceed */ false);
+    }
+
+    private void clearApplicationDataTest(String packageName, boolean shouldSucceed)
+            throws Exception {
+        mDevicePolicyManager.clearApplicationUserData(ADMIN_RECEIVER_COMPONENT,
+                packageName, AsyncTask.THREAD_POOL_EXECUTOR,
+                (String pkg, boolean succeeded) -> {
+                    assertEquals(packageName, pkg);
+                    assertEquals(shouldSucceed, succeeded);
+                    mSemaphore.release();
+                });
+        assertTrue("Clearing application data took too long",
+                mSemaphore.tryAcquire(CLEAR_APPLICATION_DATA_TIMEOUT_S, TimeUnit.SECONDS));
+    }
+
+    private String getDeviceProvisioningPackageName() {
+        final int provisioning_app_id = mContext.getResources().getIdentifier(
+                "config_deviceProvisioningPackage", "string", "android");
+        if (provisioning_app_id > 0) {
+            return mContext.getResources().getString(provisioning_app_id);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DpcAllowedAccountManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DpcAllowedAccountManagementTest.java
deleted file mode 100644
index 291159f..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DpcAllowedAccountManagementTest.java
+++ /dev/null
@@ -1,129 +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.deviceandprofileowner;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.UserManager;
-
-import java.io.IOException;
-
-/**
- * These tests verify that the device / profile owner can use account management APIs to add
- * accounts even when policies are set. The policies tested are
- * {@link DevicePolicyManager#setAccountManagementDisabled} and
- * {@link UserManager#DISALLOW_MODIFY_ACCOUNTS}.
- *
- * This test depends on {@link com.android.cts.devicepolicy.accountmanagement.MockAccountService},
- * which provides authenticator for a mock account type.
- *
- * Note that we cannot test account removal, because only the authenticator can remove an account
- * and the Dpc is not the authenticator for the mock account type.
- */
-public class DpcAllowedAccountManagementTest extends BaseDeviceAdminTest {
-
-    // Account type for MockAccountAuthenticator
-    private final static String ACCOUNT_TYPE_1
-            = "com.android.cts.devicepolicy.accountmanagement.account.type";
-    private final static String ACCOUNT_TYPE_2 = "com.dummy.account";
-    private final static Account ACCOUNT_0 = new Account("user0", ACCOUNT_TYPE_1);
-    private final static Account ACCOUNT_1 = new Account("user1", ACCOUNT_TYPE_1);
-
-    private AccountManager mAccountManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
-        clearAllAccountManagementDisabled();
-        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
-                UserManager.DISALLOW_MODIFY_ACCOUNTS);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        clearAllAccountManagementDisabled();
-        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
-                UserManager.DISALLOW_MODIFY_ACCOUNTS);
-        super.tearDown();
-    }
-
-    public void testAccountManagementDisabled_setterAndGetter() {
-        // Some local tests: adding and removing disabled accounts and make sure
-        // DevicePolicyManager keeps track of the disabled set correctly
-        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
-
-        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
-                true);
-        // Test if disabling ACCOUNT_TYPE_2 affects ACCOUNT_TYPE_1
-        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_2,
-                false);
-        assertEquals(1, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
-        assertEquals(ACCOUNT_TYPE_1,
-                mDevicePolicyManager.getAccountTypesWithManagementDisabled()[0]);
-
-        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
-                false);
-        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
-    }
-
-    public void testAccountManagementDisabled_profileAndDeviceOwnerCanAddAccount()
-            throws AuthenticatorException, IOException, OperationCanceledException {
-        mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, ACCOUNT_TYPE_1,
-                true);
-
-        assertEquals(0, mAccountManager.getAccountsByType(ACCOUNT_TYPE_1).length);
-        // Management is disabled, but the device / profile owner is still allowed to use the APIs
-        Bundle result = mAccountManager.addAccount(ACCOUNT_TYPE_1,
-                null, null, null, null, null, null).getResult();
-
-        // Normally the expected result of addAccount() is AccountManager returning
-        // an intent to start the authenticator activity for adding new accounts.
-        // But MockAccountAuthenticator returns a new account straightway.
-        assertEquals(ACCOUNT_TYPE_1, result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-    }
-
-    public void testUserRestriction_profileAndDeviceOwnerCanAddAccount()
-            throws AuthenticatorException, IOException, OperationCanceledException {
-        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
-                UserManager.DISALLOW_MODIFY_ACCOUNTS);
-
-        assertEquals(0, mAccountManager.getAccountsByType(ACCOUNT_TYPE_1).length);
-        // Management is disabled, but the device / profile owner is still allowed to use the APIs
-        Bundle result = mAccountManager.addAccount(ACCOUNT_TYPE_1,
-                null, null, null, null, null, null).getResult();
-
-        // Normally the expected result of addAccount() is AccountManager returning
-        // an intent to start the authenticator activity for adding new accounts.
-        // But MockAccountAuthenticator returns a new account straightway.
-        assertEquals(ACCOUNT_TYPE_1, result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-    }
-
-    private void clearAllAccountManagementDisabled() {
-        for (String accountType : mDevicePolicyManager.getAccountTypesWithManagementDisabled()) {
-            mDevicePolicyManager.setAccountManagementDisabled(ADMIN_RECEIVER_COMPONENT, accountType,
-                    false);
-        }
-        assertEquals(0, mDevicePolicyManager.getAccountTypesWithManagementDisabled().length);
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/MeteredDataRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/MeteredDataRestrictionTest.java
new file mode 100644
index 0000000..993e9aa
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/MeteredDataRestrictionTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class MeteredDataRestrictionTest extends BaseDeviceAdminTest {
+    private static final String TAG = MeteredDataRestrictionTest.class.getSimpleName();
+
+    private static final String METERED_DATA_APP_PKG
+            = "com.android.cts.devicepolicy.metereddatatestapp";
+    private static final String METERED_DATA_APP_MAIN_ACTIVITY
+            = METERED_DATA_APP_PKG + ".MainActivity";
+
+    private static final long WAIT_FOR_NETWORK_INFO_TIMEOUT_SEC = 8;
+
+    private static final int NUM_TRIES_METERED_STATUS_CHECK = 20;
+    private static final long INTERVAL_METERED_STATUS_CHECK_MS = 500;
+
+    private static final String EXTRA_MESSENGER = "messenger";
+    private static final int MSG_NOTIFY_NETWORK_STATE = 1;
+
+    private final Messenger mCallbackMessenger = new Messenger(new CallbackHandler());
+    private final BlockingQueue<NetworkInfo> mNetworkInfos = new LinkedBlockingQueue<>(1);
+
+    private ConnectivityManager mCm;
+    private WifiManager mWm;
+    private String mMeteredWifi;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        setMeteredNetwork();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        resetMeteredNetwork();
+    }
+
+    public void testSetMeteredDataDisabled() {
+        final List<String> restrictedPkgs = new ArrayList<>();
+        restrictedPkgs.add(METERED_DATA_APP_PKG);
+        final List<String> excludedPkgs = mDevicePolicyManager.setMeteredDataDisabled(
+                ADMIN_RECEIVER_COMPONENT, restrictedPkgs);
+        assertTrue("Packages not restricted: " + excludedPkgs, excludedPkgs.isEmpty());
+
+        List<String> actualRestrictedPkgs = mDevicePolicyManager.getMeteredDataDisabled(
+                ADMIN_RECEIVER_COMPONENT);
+        assertEquals("Actual restricted pkgs: " + actualRestrictedPkgs,
+                1, actualRestrictedPkgs.size());
+        assertTrue("Actual restricted pkgs: " + actualRestrictedPkgs,
+                actualRestrictedPkgs.contains(METERED_DATA_APP_PKG));
+        verifyAppNetworkState(true);
+
+        restrictedPkgs.clear();
+        mDevicePolicyManager.setMeteredDataDisabled(ADMIN_RECEIVER_COMPONENT, restrictedPkgs);
+        actualRestrictedPkgs = mDevicePolicyManager.getMeteredDataDisabled(
+                ADMIN_RECEIVER_COMPONENT);
+        assertTrue("Actual restricted pkgs: " + actualRestrictedPkgs,
+                actualRestrictedPkgs.isEmpty());
+        verifyAppNetworkState(false);
+    }
+
+    private void verifyAppNetworkState(boolean blocked) {
+        final Bundle extras = new Bundle();
+        extras.putBinder(EXTRA_MESSENGER, mCallbackMessenger.getBinder());
+        mNetworkInfos.clear();
+        final Intent launchIntent = new Intent()
+                .setClassName(METERED_DATA_APP_PKG, METERED_DATA_APP_MAIN_ACTIVITY)
+                .putExtras(extras)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(launchIntent);
+
+        try {
+            final NetworkInfo networkInfo = mNetworkInfos.poll(WAIT_FOR_NETWORK_INFO_TIMEOUT_SEC,
+                    TimeUnit.SECONDS);
+            if (networkInfo == null) {
+                fail("Timed out waiting for the network info");
+            }
+
+            final String expectedState = (blocked ? State.DISCONNECTED : State.CONNECTED).name();
+            final String expectedDetailedState
+                    = (blocked ? DetailedState.BLOCKED : DetailedState.CONNECTED).name();
+            assertEquals("Wrong state: " + networkInfo,
+                    expectedState, networkInfo.getState().name());
+            assertEquals("Wrong detailed state: " + networkInfo,
+                    expectedDetailedState, networkInfo.getDetailedState().name());
+        } catch (InterruptedException e) {
+            fail("Waiting for networkinfo got interrupted: " + e);
+        }
+    }
+
+    private class CallbackHandler extends Handler {
+        public CallbackHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_NOTIFY_NETWORK_STATE) {
+                final NetworkInfo networkInfo = (NetworkInfo) msg.obj;
+                if (!mNetworkInfos.offer(networkInfo)) {
+                    Log.e(TAG, "Error while adding networkinfo");
+                }
+            } else {
+                Log.e(TAG, "Unknown msg type: " + msg.what);
+            }
+        }
+    }
+
+    private void setMeteredNetwork() throws Exception {
+        final NetworkInfo networkInfo = mCm.getActiveNetworkInfo();
+        if (networkInfo == null) {
+            fail("Active network is not available");
+        } else if (mCm.isActiveNetworkMetered()) {
+            Log.i(TAG, "Active network already metered: " + networkInfo);
+            return;
+        } else if (networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
+            fail("Active network doesn't support setting metered status: " + networkInfo);
+        }
+        final String netId = setWifiMeteredStatus(true);
+
+        // Set flag so status is reverted on resetMeteredNetwork();
+        mMeteredWifi = netId;
+        // Sanity check.
+        assertWifiMeteredStatus(netId, true);
+        assertActiveNetworkMetered(true);
+    }
+
+    private void resetMeteredNetwork() throws Exception {
+        if (mMeteredWifi != null) {
+            Log.i(TAG, "Resetting metered status for netId=" + mMeteredWifi);
+            setWifiMeteredStatus(mMeteredWifi, false);
+            assertWifiMeteredStatus(mMeteredWifi, false);
+            assertActiveNetworkMetered(false);
+        }
+    }
+
+    private String setWifiMeteredStatus(boolean metered) throws Exception {
+        final String ssid = mWm.getConnectionInfo().getSSID();
+        assertNotNull("null SSID", ssid);
+        final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
+        assertFalse("empty SSID", ssid.isEmpty());
+        setWifiMeteredStatus(netId, metered);
+        return netId;
+    }
+
+    private void setWifiMeteredStatus(String netId, boolean metered) throws Exception {
+        Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
+        executeCmd("cmd netpolicy set metered-network " + netId + " " + metered);
+    }
+
+    private void assertWifiMeteredStatus(String netId, boolean metered) throws Exception {
+        final String cmd = "cmd netpolicy list wifi-networks";
+        final String expectedResult = netId + ";" + metered;
+        String cmdResult = null;
+        for (int i = 0; i < NUM_TRIES_METERED_STATUS_CHECK; ++i) {
+            cmdResult = executeCmd(cmd);
+            if (cmdResult.contains(expectedResult)) {
+                return;
+            }
+            SystemClock.sleep(INTERVAL_METERED_STATUS_CHECK_MS);
+        }
+        fail("Timed out waiting for wifi metered status to change. expected=" + expectedResult
+                + ", actual status=" + cmdResult);
+    }
+
+    private void assertActiveNetworkMetered(boolean metered) {
+        boolean actualMeteredStatus = !metered;
+        for (int i = 0; i < NUM_TRIES_METERED_STATUS_CHECK; ++i) {
+            actualMeteredStatus = mCm.isActiveNetworkMetered();
+            if (actualMeteredStatus == metered) {
+                return;
+            }
+            SystemClock.sleep(INTERVAL_METERED_STATUS_CHECK_MS);
+        }
+        fail("Timed out waiting for active network metered status to change. expected="
+                + metered + "; actual=" + actualMeteredStatus
+                + "; networkInfo=" + mCm.getActiveNetwork());
+    }
+
+    private String executeCmd(String cmd) throws Exception {
+        final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        Log.i(TAG, "Cmd '" + cmd + "' result: " + result);
+        return result;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java
new file mode 100644
index 0000000..7856aff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class PasswordBlacklistTest extends BaseDeviceAdminTest {
+    private static final String TAG = "PasswordBlacklistTest";
+    private static final byte[] TOKEN = "abcdefghijklmnopqrstuvwxyz0123456789".getBytes();
+
+    private boolean mShouldRun = true;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Set up a token to reset the password. This is used to check the blacklist is being
+        // enforced.
+        try {
+            // On devices with password token disabled, calling this method will throw
+            // a security exception. If that's anticipated, then return early without failing.
+            assertTrue(mDevicePolicyManager.setResetPasswordToken(ADMIN_RECEIVER_COMPONENT,
+                    TOKEN));
+        } catch (SecurityException e) {
+            if (e.getMessage().equals("Escrow token is disabled on the current user")) {
+                Log.i(TAG, "Skip some password blacklist test because escrow token is disabled");
+                mShouldRun = false;
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (!mShouldRun) {
+            return;
+        }
+        // Remove the blacklist, password and password reset token
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(ADMIN_RECEIVER_COMPONENT, null, null));
+        assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                ADMIN_RECEIVER_COMPONENT, null, TOKEN, 0));
+        assertTrue(mDevicePolicyManager.clearResetPasswordToken(ADMIN_RECEIVER_COMPONENT));
+    }
+
+    public void testSettingEmptyBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final String notInBlacklist = "4ur3>#C$a#rC3W9Rhs";
+
+        testPasswordBlacklist(null, notInBlacklist);
+    }
+
+    public void testClearingBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final String notInBlacklist = "4ur3>#C$a#rC3W9Rhs";
+
+        testPasswordBlacklist(Arrays.asList(notInBlacklist), null);
+        testPasswordBlacklist(null, notInBlacklist);
+    }
+
+    public void testSettingBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final List<String> blacklist = Arrays.asList("password", "letmein", "football");
+        final String notInBlacklist = "falseponycellfastener";
+
+        testPasswordBlacklist(blacklist, notInBlacklist);
+    }
+
+    public void testChangingBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final List<String> blacklist = Arrays.asList("password", "letmein", "football");
+        final String notInBlacklist = "falseponycellfastener";
+
+        testPasswordBlacklist(Arrays.asList(notInBlacklist), null);
+        testPasswordBlacklist(blacklist, notInBlacklist);
+    }
+
+    public void testBlacklistNotTreatedAsRegex() {
+        if (!mShouldRun) {
+            return;
+        }
+        final List<String> blacklist = Arrays.asList("hi\\d*", ".*", "[^adb]{2}.\\d.\\S");
+        final String notInBlacklist = "hi123";
+
+        testPasswordBlacklist(blacklist, notInBlacklist);
+    }
+
+    public void testBlacklistCaseInsensitive() {
+        if (!mShouldRun) {
+            return;
+        }
+        final List<String> blacklist = Arrays.asList("baseball", "MONKEY", "ShAdOw");
+        final String notInBlacklist = "falsecellfastenerpony";
+
+        testPasswordBlacklist(blacklist, notInBlacklist);
+
+        // These are also blocked by the blacklist as they only differ in case
+        final List<String> inBlacklist = Arrays.asList(
+                "baseball", "BASEBALL", "BASEball",
+                "monkey", "MONKEY", "moNKEy",
+                "shadow", "SHADOW", "ShAdOw");
+        for (final String password : inBlacklist) {
+            assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+                    ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+        }
+    }
+
+    public void testMaxBlacklistSize() {
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, "max size", generateMaxBlacklist()));
+    }
+
+    public void testBlacklistTooBig() {
+        try {
+            mDevicePolicyManager.setPasswordBlacklist(
+                    ADMIN_RECEIVER_COMPONENT, "too big", generateJustTooBigBlacklist());
+            fail("Did not throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+    }
+
+    public void testNullNameWhenSettingBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final String password = "bad one";
+        try {
+            mDevicePolicyManager.setPasswordBlacklist(
+                    ADMIN_RECEIVER_COMPONENT, null, Arrays.asList(password));
+            fail("Did not throw NullPointerException");
+        } catch (NullPointerException e) {
+            assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                    ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+            return;
+        }
+    }
+
+    public void testNullAdminWhenSettingBlacklist() {
+        if (!mShouldRun) {
+            return;
+        }
+        final String password = "example";
+        try {
+            mDevicePolicyManager.setPasswordBlacklist(null, "no admin", Arrays.asList(password));
+            fail("Did not throw NullPointerException");
+        } catch (NullPointerException e) {
+            assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                    ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+            return;
+        }
+    }
+
+    public void testPasswordBlacklistName() {
+        if (!mShouldRun) {
+            return;
+        }
+        final String name = "Version 1.0";
+        final List<String> blacklist = Arrays.asList("one", "1", "i");
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, name, blacklist));
+        assertEquals(
+                mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), name);
+        for (final String password : blacklist) {
+            assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+                    ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+        }
+        assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                ADMIN_RECEIVER_COMPONENT, "notintheblacklist", TOKEN, 0));
+    }
+
+    public void testPasswordBlacklistWithEmptyName() {
+        final String emptyName = "";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, emptyName, Arrays.asList("test", "empty", "name")));
+        assertEquals(
+                mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), emptyName);
+    }
+
+    public void testBlacklistNameCanBeChanged() {
+        final String firstName = "original";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, firstName, Arrays.asList("a")));
+        assertEquals(
+                mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), firstName);
+
+        final String newName = "different";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, newName, Arrays.asList("a")));
+        assertEquals(
+                mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), newName);
+    }
+
+    public void testCannotNameClearedBlacklist() {
+        final String name = "empty!";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, name, null));
+        assertTrue(mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT) == null);
+    }
+
+    public void testClearingBlacklistClearsName() {
+        final String firstName = "gotone";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, firstName, Arrays.asList("something")));
+        assertEquals(
+                mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), firstName);
+
+        final String newName = "empty!";
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, newName, null));
+        assertTrue(mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT) == null);
+    }
+
+    public void testNullAdminWhenGettingBlacklistName() {
+        try {
+            mDevicePolicyManager.getPasswordBlacklistName(null);
+            fail("Did not throw NullPointerException");
+        } catch (NullPointerException e) {
+            return;
+        }
+    }
+
+    public void testBlacklistNotConsideredByIsActivePasswordSufficient() {
+        if (!mShouldRun) {
+            return;
+        }
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+        final String complexPassword = ".password123";
+        assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                ADMIN_RECEIVER_COMPONENT, complexPassword, TOKEN, 0));
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, "Sufficient", Arrays.asList(complexPassword)));
+        assertPasswordSufficiency(true);
+    }
+
+    private static final int MAX_BLACKLIST_ITEM_SIZE = 8;
+
+    /* Generate a list based on the 128 thousand character limit */
+    private List<String> generateMaxBlacklist() {
+        final int numItems = (128 * 1000) / MAX_BLACKLIST_ITEM_SIZE;
+        assertTrue(numItems == 16 * 1000);
+        final List<String> blacklist = new ArrayList(numItems);
+        final String item = new String(new char[MAX_BLACKLIST_ITEM_SIZE]).replace('\0', 'a');
+        for (int i = 0; i < numItems; ++i) {
+            blacklist.add(item);
+        }
+        return blacklist;
+    }
+
+    private List<String> generateJustTooBigBlacklist() {
+        List<String> list = generateMaxBlacklist();
+        list.set(0, new String(new char[MAX_BLACKLIST_ITEM_SIZE + 1]).replace('\0', 'a'));
+        return list;
+    }
+
+    /**
+     * Install a blacklist, ensure items match and don't match it correctly.
+     */
+    private void testPasswordBlacklist(List<String> blacklist, String notInBlacklist) {
+        assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+                ADMIN_RECEIVER_COMPONENT, "Test Blacklist", blacklist));
+
+        if (blacklist != null) {
+            // These are blacklisted so can't be set
+            for (final String password : blacklist) {
+                assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+                        ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+            }
+        }
+
+        if (notInBlacklist != null) {
+            // This isn't blacklisted so can be set
+            assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+                    ADMIN_RECEIVER_COMPONENT, notInBlacklist, TOKEN, 0));
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 6c706ee..70a5d09 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.os.UserManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -47,6 +46,7 @@
     private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
             "com.android.cts.launcherapps.simplepremapp";
     private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+    private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
 
     private static final String PERMISSIONS_ACTIVITY_NAME
             = PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
@@ -227,6 +227,15 @@
         assertSetPermissionGrantStatePreMApp(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
     }
 
+    public void testPermissionGrantState_developmentPermission() throws Exception {
+        assertFailedToSetDevelopmentPermissionGrantState(
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        assertFailedToSetDevelopmentPermissionGrantState(
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+        assertFailedToSetDevelopmentPermissionGrantState(
+                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+    }
+
     private void assertPermissionRequest(int expected) throws Exception {
         assertPermissionRequest(expected, null);
     }
@@ -274,6 +283,18 @@
                 value);
     }
 
+    private void assertFailedToSetDevelopmentPermissionGrantState(int value) throws Exception {
+        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION, value));
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION),
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+        assertEquals(mPackageManager.checkPermission(DEVELOPMENT_PERMISSION,
+                PERMISSION_APP_PACKAGE_NAME),
+                PackageManager.PERMISSION_DENIED);
+    }
+
+
     private void assertSetPermissionGrantStatePreMApp(int value) throws Exception {
         assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
                 SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintActivity.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintActivity.java
new file mode 100644
index 0000000..08638ff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper class used to call the activity in the non-test APK and wait for its result.
+ */
+public class PrintActivity extends Activity {
+
+    private static final String PRINTING_PACKAGE = "com.android.cts.devicepolicy.printingapp";
+    private static final String PRINT_ACTIVITY = PRINTING_PACKAGE + ".PrintActivity";
+    private static final String EXTRA_ERROR_MESSAGE = "error_message";
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private String mErrorMessage;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        startActivityForResult(
+                new Intent().setComponent(new ComponentName(PRINTING_PACKAGE, PRINT_ACTIVITY)), 0);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Use RESULT_FIRST_USER for success to avoid false positives.
+        if (resultCode != RESULT_FIRST_USER) {
+            if (data != null) {
+                mErrorMessage = data.getStringExtra(EXTRA_ERROR_MESSAGE);
+            }
+            if (mErrorMessage == null) {
+                mErrorMessage = "Unknown error, resultCode: " + resultCode;
+            }
+        }
+        mLatch.countDown();
+    }
+
+    public String getErrorMessage() throws InterruptedException {
+        final boolean called = mLatch.await(2, TimeUnit.SECONDS);
+        if (!called) {
+            throw new IllegalStateException("PrintActivity didn't finish in time");
+        }
+        finish();
+        return mErrorMessage;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintingPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintingPolicyTest.java
new file mode 100644
index 0000000..29f6376
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintingPolicyTest.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.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.UserManager;
+
+/**
+ * Validates that Device Owner or Profile Owner can disable printing.
+ */
+public class PrintingPolicyTest extends BaseDeviceAdminTest {
+
+    public void testPrintingPolicy() throws Exception {
+        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_PRINTING);
+        final PrintActivity activity = launchActivity("com.android.cts.deviceandprofileowner",
+                PrintActivity.class, null);
+        final String errorMessage = activity.getErrorMessage();
+        assertNull(errorMessage);
+    }
+}
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 39235b0..0361fd8 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
@@ -60,6 +60,7 @@
             UserManager.DISALLOW_SMS,
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_CREATE_WINDOWS,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
             UserManager.DISALLOW_OUTGOING_BEAM,
             UserManager.DISALLOW_SAFE_BOOT,
@@ -91,7 +92,8 @@
 
             // PO can set them too, but when DO sets them, they're global.
             UserManager.DISALLOW_ADJUST_VOLUME,
-            UserManager.DISALLOW_UNMUTE_MICROPHONE
+            UserManager.DISALLOW_UNMUTE_MICROPHONE,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS
     };
 
     public static final String[] HIDDEN_AND_PROHIBITED = new String[] {
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 b218341..9743840 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
@@ -48,6 +48,7 @@
             UserManager.DISALLOW_SMS,
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_CREATE_WINDOWS,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
             UserManager.DISALLOW_OUTGOING_BEAM,
             UserManager.DISALLOW_SAFE_BOOT,
@@ -55,7 +56,8 @@
             // UserManager.DISALLOW_DATA_ROAMING, // Has unrecoverable side effects.
             UserManager.DISALLOW_SET_USER_ICON,
             UserManager.DISALLOW_BLUETOOTH,
-            UserManager.DISALLOW_AUTOFILL
+            UserManager.DISALLOW_AUTOFILL,
+            UserManager.DISALLOW_UNIFIED_PASSWORD,
     };
 
     public static final String[] DISALLOWED = new String[] {
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 5c7f243..2a9db15 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
@@ -38,11 +38,13 @@
             UserManager.DISALLOW_UNMUTE_MICROPHONE,
             UserManager.DISALLOW_ADJUST_VOLUME,
             UserManager.DISALLOW_OUTGOING_CALLS,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
             UserManager.DISALLOW_OUTGOING_BEAM,
             UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
             UserManager.DISALLOW_SET_USER_ICON,
-            UserManager.DISALLOW_AUTOFILL
+            UserManager.DISALLOW_AUTOFILL,
+            UserManager.DISALLOW_UNIFIED_PASSWORD,
     };
 
     public static final String[] DISALLOWED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 4cc041a..4ef531f 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -17,25 +17,33 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsDeviceOwnerApp
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+                   $(call all-Iaidl-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    conscrypt \
+    cts-junit \
+    android.test.base.stubs \
+    bouncycastle
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     compatibility-device-util \
     android-support-v4 \
     android-support-test \
-    legacy-android-test
-
-LOCAL_SDK_VERSION := test_current
+    cts-security-test-support-library \
+    testng
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 9a09007..4e878c5 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -29,13 +29,15 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <!-- Needed to read the serial number during Device ID attestation tests -->
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <application
         android:testOnly="true">
 
         <uses-library android:name="android.test.runner" />
         <receiver
-            android:name="com.android.cts.deviceowner.BaseDeviceOwnerTest$BasicAdminReceiver"
+            android:name="com.android.cts.deviceowner.BasicAdminReceiver"
             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/device_admin" />
@@ -52,6 +54,20 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
             </intent-filter>
         </receiver>
+        <receiver
+                android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
+                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>
+
+        <service android:name="com.android.cts.deviceowner.CreateAndManageUserTest$PrimaryUserService"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_DEVICE_ADMIN">
+        </service>
 
         <activity
             android:name="com.android.cts.deviceowner.KeyManagementActivity"
@@ -70,14 +86,6 @@
             </intent-filter>
         </activity>
 
-        <!-- we need to give a different taskAffinity so that when we use
-             FLAG_ACTIVITY_NEW_TASK, the system tries to start it in a different task
-        -->
-        <activity
-            android:name="com.android.cts.deviceowner.LockTaskTest$IntentReceivingActivity"
-            android:taskAffinity="com.android.cts.deviceowner.LockTaskTest.IntentReceivingActivity"
-            />
-
         <activity
             android:name=".SetPolicyActivity"
             android:launchMode="singleTop">
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/DeviceOwner/res/xml/device_admin.xml
index fe58d38..c9be6a0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/res/xml/device_admin.xml
@@ -2,5 +2,10 @@
     <uses-policies>
         <reset-password />
         <limit-password />
+        <expire-password />
+        <wipe-data />
+        <watch-login />
+        <disable-keyguard-features />
+        <force-lock />
     </uses-policies>
 </device-admin>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
index a9b43c6..0afc16e 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
@@ -46,7 +46,7 @@
         Context context = InstrumentationRegistry.getContext();
         mDevicePolicyManager = (DevicePolicyManager)
                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        mAdminComponent = BaseDeviceOwnerTest.BasicAdminReceiver.getComponentName(context);
+        mAdminComponent = BasicAdminReceiver.getComponentName(context);
     }
 
     @Test
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AirplaneModeRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AirplaneModeRestrictionTest.java
new file mode 100644
index 0000000..ac73bdf
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AirplaneModeRestrictionTest.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 com.android.cts.deviceowner;
+
+import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test interaction between {@link UserManager#DISALLOW_AIRPLANE_MODE} user restriction and
+ * {@link Settings.Global#AIRPLANE_MODE_ON}.
+ */
+public class AirplaneModeRestrictionTest extends BaseDeviceOwnerTest {
+    private static final String LOG_TAG = "AirplaneModeRestrictionTest";
+    private static final int TIMEOUT_SEC = 5;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_AIRPLANE_MODE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_AIRPLANE_MODE);
+        super.tearDown();
+    }
+
+    public void testAirplaneModeTurnedOffWhenRestrictionSet() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        // Using array so that it can be modified in broadcast receiver.
+        int value[] = new int[1];
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                value[0] = intent.getIntExtra("state", 1);
+                latch.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+        try {
+            Settings.Global.putInt(mContext.getContentResolver(), AIRPLANE_MODE_ON, 1);
+            mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_AIRPLANE_MODE);
+            assertTrue(latch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+            assertEquals(0, value[0]);
+            assertEquals(0, Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    public void testAirplaneModeCannotBeTurnedOnWithRestrictionOn()
+            throws SettingNotFoundException {
+        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_AIRPLANE_MODE);
+        Settings.Global.putInt(mContext.getContentResolver(), AIRPLANE_MODE_ON, 1);
+        assertEquals(0, Settings.Global.getInt(
+                mContext.getContentResolver(), AIRPLANE_MODE_ON));
+    }
+
+    public void testAirplaneModeCanBeTurnedOnWithRestrictionOff() throws SettingNotFoundException {
+        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_AIRPLANE_MODE);
+        Settings.Global.putInt(mContext.getContentResolver(), AIRPLANE_MODE_ON, 1);
+        assertEquals(1, Settings.Global.getInt(
+                mContext.getContentResolver(), AIRPLANE_MODE_ON));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServiceEnabledTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServiceEnabledTest.java
deleted file mode 100644
index fbf1ec7..0000000
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServiceEnabledTest.java
+++ /dev/null
@@ -1,32 +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.deviceowner;
-
-public class BackupServiceEnabledTest extends BaseDeviceOwnerTest {
-
-    /**
-     * Test: Test enabling backup service. This test should be executed after installing a device
-     * owner so that we check that backup service is not enabled by default.
-     * This test will keep backup service disabled after its execution.
-     */
-    public void testEnablingAndDisablingBackupService() {
-        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
-        mDevicePolicyManager.setBackupServiceEnabled(getWho(), true);
-        assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
-        mDevicePolicyManager.setBackupServiceEnabled(getWho(), false);
-        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServicePoliciesTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServicePoliciesTest.java
new file mode 100644
index 0000000..2880219
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BackupServicePoliciesTest.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.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.backup.BackupManager;
+import android.content.ComponentName;
+
+public class BackupServicePoliciesTest extends BaseDeviceOwnerTest {
+
+    private static final String LOCAL_TRANSPORT =
+            "android/com.android.internal.backup.LocalTransport";
+
+    private BackupManager mBackupManager;
+    private ComponentName mLocalBackupTransportComponent;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mBackupManager = new BackupManager(getContext());
+        mLocalBackupTransportComponent = ComponentName.unflattenFromString(LOCAL_TRANSPORT);
+    }
+
+    /**
+     * Test: Test enabling backup service. This test should be executed after installing a device
+     * owner so that we check that backup service is not enabled by default.
+     * This test will keep backup service disabled after its execution.
+     */
+    public void testEnablingAndDisablingBackupService() {
+        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+        mDevicePolicyManager.setBackupServiceEnabled(getWho(), true);
+        assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+        mDevicePolicyManager.setBackupServiceEnabled(getWho(), false);
+        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+    }
+
+    /**
+     * Test setting mandatory backup transport.
+     *
+     * <p>After setting a mandatory backup transport, the backup service should be enabled and the
+     * mandatory backup transport
+     */
+    public void testGetAndSetMandatoryBackupTransport() {
+        assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+
+        // Make backups with the local transport mandatory.
+        mDevicePolicyManager.setMandatoryBackupTransport(getWho(), mLocalBackupTransportComponent);
+
+        // Verify that the backup service has been enabled...
+        assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+
+        // ... and the local transport should be used.
+        assertEquals(
+                mLocalBackupTransportComponent, mDevicePolicyManager.getMandatoryBackupTransport());
+
+        // Disable the backup service again.
+        mDevicePolicyManager.setBackupServiceEnabled(getWho(), false);
+
+        // And verify no mandatory backup transport is set any more.
+        assertNull(mDevicePolicyManager.getMandatoryBackupTransport());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java
new file mode 100644
index 0000000..9490eff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.test.AndroidTestCase;
+
+/**
+ * Base class for affiliated profile-owner based tests.
+ *
+ * This class handles making sure that the test is the affiliated profile owner and that it has an
+ * active admin registered, so that all tests may assume these are done. The admin component can be
+ * accessed through {@link #getWho()}.
+ */
+public abstract class BaseAffiliatedProfileOwnerTest extends AndroidTestCase {
+
+    protected DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        assertDeviceOrAffiliatedProfileOwner();
+    }
+
+    private void assertDeviceOrAffiliatedProfileOwner() {
+        assertNotNull(mDevicePolicyManager);
+        assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
+        boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName());
+        boolean isAffiliatedProfileOwner = mDevicePolicyManager.isProfileOwnerApp(
+                mContext.getPackageName())
+                && mDevicePolicyManager.isAffiliatedUser();
+        assertTrue(isDeviceOwner || isAffiliatedProfileOwner);
+    }
+
+    protected ComponentName getWho() {
+        return BasicAdminReceiver.getComponentName(mContext);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 94aaeb2..9175d9b 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -15,15 +15,11 @@
  */
 package com.android.cts.deviceowner;
 
-import android.app.admin.DeviceAdminReceiver;
+import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Process;
-import android.os.UserHandle;
-import android.support.v4.content.LocalBroadcastManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 
 /**
@@ -36,77 +32,31 @@
  */
 public abstract class BaseDeviceOwnerTest extends AndroidTestCase {
 
-    final static String ACTION_USER_ADDED = "com.android.cts.deviceowner.action.USER_ADDED";
-    final static String ACTION_USER_REMOVED = "com.android.cts.deviceowner.action.USER_REMOVED";
-    final static String EXTRA_USER_HANDLE = "com.android.cts.deviceowner.extra.USER_HANDLE";
-    final static String ACTION_NETWORK_LOGS_AVAILABLE =
-            "com.android.cts.deviceowner.action.ACTION_NETWORK_LOGS_AVAILABLE";
-    final static String EXTRA_NETWORK_LOGS_BATCH_TOKEN =
-            "com.android.cts.deviceowner.extra.NETWORK_LOGS_BATCH_TOKEN";
-
-    public static class BasicAdminReceiver extends DeviceAdminReceiver {
-
-        public static ComponentName getComponentName(Context context) {
-            return new ComponentName(context, BasicAdminReceiver.class);
-        }
-
-        @Override
-        public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
-                String suggestedAlias) {
-            if (uid != Process.myUid() || uri == null) {
-                return null;
-            }
-            return uri.getQueryParameter("alias");
-        }
-
-        @Override
-        public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
-            sendUserAddedOrRemovedBroadcast(context, ACTION_USER_ADDED, userHandle);
-        }
-
-        @Override
-        public void onUserRemoved(Context context, Intent intent, UserHandle userHandle) {
-            sendUserAddedOrRemovedBroadcast(context, ACTION_USER_REMOVED, userHandle);
-        }
-
-        @Override
-        public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
-                int networkLogsCount) {
-            // send the broadcast, the rest of the test happens in NetworkLoggingTest
-            Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
-            batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
-            LocalBroadcastManager.getInstance(context).sendBroadcast(batchIntent);
-        }
-
-        private void sendUserAddedOrRemovedBroadcast(Context context, String action,
-                UserHandle userHandle) {
-            Intent intent = new Intent(action);
-            intent.putExtra(EXTRA_USER_HANDLE, userHandle);
-            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
-        }
-    }
-
-    public static final String PACKAGE_NAME = BaseDeviceOwnerTest.class.getPackage().getName();
-
     protected DevicePolicyManager mDevicePolicyManager;
-
+    protected Instrumentation mInstrumentation;
+    protected UiDevice mDevice;
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mDevicePolicyManager = (DevicePolicyManager)
-                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        assertDeviceOwner(mDevicePolicyManager);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(mInstrumentation);
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        assertDeviceOwner();
     }
 
-    static void assertDeviceOwner(DevicePolicyManager dpm) {
-        assertNotNull(dpm);
-        assertTrue(dpm.isAdminActive(getWho()));
-        assertTrue(dpm.isDeviceOwnerApp(PACKAGE_NAME));
-        assertFalse(dpm.isManagedProfile(getWho()));
+    private void assertDeviceOwner() {
+        assertNotNull(mDevicePolicyManager);
+        assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
+        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName()));
+        assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
     }
 
-    protected static ComponentName getWho() {
-        return new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
+    protected ComponentName getWho() {
+        return BasicAdminReceiver.getComponentName(mContext);
+    }
+
+    protected String executeShellCommand(String... command) throws Exception {
+        return mDevice.executeShellCommand(String.join(" ", command));
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
new file mode 100644
index 0000000..44ad8b3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class BasicAdminReceiver extends DeviceAdminReceiver {
+
+    final static String ACTION_USER_ADDED = "com.android.cts.deviceowner.action.USER_ADDED";
+    final static String ACTION_USER_REMOVED = "com.android.cts.deviceowner.action.USER_REMOVED";
+    final static String ACTION_USER_STARTED = "com.android.cts.deviceowner.action.USER_STARTED";
+    final static String ACTION_USER_STOPPED = "com.android.cts.deviceowner.action.USER_STOPPED";
+    final static String ACTION_USER_SWITCHED = "com.android.cts.deviceowner.action.USER_SWITCHED";
+    final static String EXTRA_USER_HANDLE = "com.android.cts.deviceowner.extra.USER_HANDLE";
+    final static String ACTION_NETWORK_LOGS_AVAILABLE =
+            "com.android.cts.deviceowner.action.ACTION_NETWORK_LOGS_AVAILABLE";
+    final static String EXTRA_NETWORK_LOGS_BATCH_TOKEN =
+            "com.android.cts.deviceowner.extra.NETWORK_LOGS_BATCH_TOKEN";
+
+    public static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, BasicAdminReceiver.class);
+    }
+
+    @Override
+    public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
+            String suggestedAlias) {
+        if (uid != Process.myUid() || uri == null) {
+            return null;
+        }
+        return uri.getQueryParameter("alias");
+    }
+
+    @Override
+    public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
+        sendUserBroadcast(context, ACTION_USER_ADDED, userHandle);
+    }
+
+    @Override
+    public void onUserRemoved(Context context, Intent intent, UserHandle userHandle) {
+        sendUserBroadcast(context, ACTION_USER_REMOVED, userHandle);
+    }
+
+    @Override
+    public void onUserStarted(Context context, Intent intent, UserHandle userHandle) {
+        sendUserBroadcast(context, ACTION_USER_STARTED, userHandle);
+    }
+
+    @Override
+    public void onUserStopped(Context context, Intent intent, UserHandle userHandle) {
+        sendUserBroadcast(context, ACTION_USER_STOPPED, userHandle);
+    }
+
+    @Override
+    public void onUserSwitched(Context context, Intent intent, UserHandle userHandle) {
+        sendUserBroadcast(context, ACTION_USER_SWITCHED, userHandle);
+    }
+
+    @Override
+    public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+            int networkLogsCount) {
+        // send the broadcast, the rest of the test happens in NetworkLoggingTest
+        Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
+        batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(batchIntent);
+    }
+
+    private void sendUserBroadcast(Context context, String action,
+            UserHandle userHandle) {
+        Intent intent = new Intent(action);
+        intent.putExtra(EXTRA_USER_HANDLE, userHandle);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+}
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 0159b128..8f53cbd 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
@@ -17,6 +17,7 @@
 package com.android.cts.deviceowner;
 
 import android.app.ActivityManager;
+import android.app.Service;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -24,18 +25,28 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
-import android.os.Bundle;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
-import java.lang.reflect.Field;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Test {@link DevicePolicyManager#createAndManageUser}.
@@ -50,12 +61,18 @@
     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;
-    private UserHandle mUserHandle;
 
     @Override
     protected void setUp() throws Exception {
@@ -68,11 +85,6 @@
     protected void tearDown() throws Exception {
         mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
         mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
-        // Remove user in case of failed test.
-        if (mUserHandle != null) {
-            mDevicePolicyManager.removeUser(getWho(), mUserHandle);
-            mUserHandle = null;
-        }
         super.tearDown();
     }
 
@@ -122,158 +134,422 @@
         }
     }
 
-// Disabled due to b/29072728
-//    // 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) {
-//            mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
-//                    TestProfileOwner.getComponentName(), bundle, flags);
-//            assertNotNull(mUserHandle);
-//
-//            mDevicePolicyManager.switchUser(getWho(), mUserHandle);
-//            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(), mUserHandle));
-//
-//            mUserHandle = null;
-//        }
-//
-//        mContext.unregisterReceiver(receiver);
-//    }
+    // 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;
+        }
 
-    /**
-     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
-     * method.
-     *
-     * <p>The test creates a user by calling to {@link DevicePolicyManager#createAndManageUser}. It
-     * doesn't remove the user afterwards, so its properties can be queried and tested by host-side
-     * tests.
-     * <p>The user's flags will be checked from the corresponding host-side test.
-     */
-    public void testCreateAndManageEphemeralUser() throws Exception {
+        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 {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
-        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
-        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
-        int makeEphemeralFlag = field.getInt(null);
-
-        // Do not assign return value to mUserHandle, so it is not removed in tearDown.
-        mDevicePolicyManager.createAndManageUser(
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
                 getWho(),
                 testUserName,
                 getWho(),
                 null,
-                makeEphemeralFlag);
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
     }
 
-    /**
-     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
-     * method fails on systems without the split system user.
-     *
-     * <p>To be used by host-side test on systems without the split system user.
-     */
-    public void testCreateAndManageEphemeralUserFails() throws Exception {
+    public void testCreateAndManageUser_LowStorage() throws Exception {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
-        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
-        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
-        int makeEphemeralFlag = field.getInt(null);
-
         try {
             mDevicePolicyManager.createAndManageUser(
                     getWho(),
                     testUserName,
                     getWho(),
                     null,
-                    makeEphemeralFlag);
-        } catch (IllegalArgumentException e) {
-            // Success, the expected exception was thrown.
-            return;
+                /* flags */ 0);
+            fail("createAndManageUser should throw UserOperationException");
+        } catch (UserManager.UserOperationException e) {
+            assertEquals(UserManager.USER_OPERATION_ERROR_LOW_STORAGE, e.getUserOperationResult());
         }
-        fail("createAndManageUser should have thrown IllegalArgumentException");
     }
 
-// Disabled due to b/29072728
-//    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);
-//        }
-//    }
+    public void testCreateAndManageUser_MaxUsers() throws Exception {
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        try {
+            mDevicePolicyManager.createAndManageUser(
+                    getWho(),
+                    testUserName,
+                    getWho(),
+                    null,
+                /* flags */ 0);
+            fail("createAndManageUser should throw UserOperationException");
+        } catch (UserManager.UserOperationException e) {
+            assertEquals(UserManager.USER_OPERATION_ERROR_MAX_USERS, e.getUserOperationResult());
+        }
+    }
+
+    public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+
+        List<UserHandle> secondaryUsers = mDevicePolicyManager.getSecondaryUsers(getWho());
+        assertEquals(1, secondaryUsers.size());
+        assertEquals(userHandle, secondaryUsers.get(0));
+    }
+
+    public void testCreateAndManageUser_SwitchUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
+        try {
+            assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
+        try {
+            assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+            assertEquals(UserManager.USER_OPERATION_ERROR_CURRENT_USER,
+                    mDevicePolicyManager.stopUser(getWho(), userHandle));
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    public void testCreateAndManageUser_StartInBackground() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_STARTED));
+
+        try {
+            // Start user in background and wait for onUserStarted
+            assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                    mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+
+        // Start user in background and should receive max running users error
+        assertEquals(UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS,
+                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+    }
+
+    public void testCreateAndManageUser_StopUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+        assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
+
+        try {
+            assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                    mDevicePolicyManager.stopUser(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        // Set DISALLOW_REMOVE_USER restriction
+        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                DevicePolicyManager.MAKE_USER_EPHEMERAL);
+        Log.d(TAG, "User create: " + userHandle);
+        assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
+
+        try {
+            assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                    mDevicePolicyManager.stopUser(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+            // Clear DISALLOW_REMOVE_USER restriction
+            mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void logoutUser(Context context, DevicePolicyManager devicePolicyManager,
+            ComponentName componentName) {
+        assertEquals("cannot logout user", UserManager.USER_OPERATION_SUCCESS,
+                devicePolicyManager.logoutUser(componentName));
+    }
+
+    public void testCreateAndManageUser_LogoutUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
+
+        try {
+            UserHandle userHandle = runCrossUserVerification(
+                    /* createAndManageUserFlags */ 0, "logoutUser");
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void assertAffiliatedUser(Context context,
+            DevicePolicyManager devicePolicyManager, ComponentName componentName) {
+        assertTrue("not affiliated user", devicePolicyManager.isAffiliatedUser());
+    }
+
+    public void testCreateAndManageUser_Affiliated() throws Exception {
+        runCrossUserVerification(/* createAndManageUserFlags */ 0, "assertAffiliatedUser");
+        PrimaryUserService.assertCrossUserCallArrived();
+    }
+
+    @SuppressWarnings("unused")
+    private static void assertEphemeralUser(Context context,
+            DevicePolicyManager devicePolicyManager, ComponentName componentName) {
+        assertTrue("not ephemeral user", devicePolicyManager.isEphemeralUser(componentName));
+    }
+
+    public void testCreateAndManageUser_Ephemeral() throws Exception {
+        runCrossUserVerification(DevicePolicyManager.MAKE_USER_EPHEMERAL, "assertEphemeralUser");
+        PrimaryUserService.assertCrossUserCallArrived();
+    }
+
+    @SuppressWarnings("unused")
+    private static void assertAllSystemAppsInstalled(Context context,
+            DevicePolicyManager devicePolicyManager, ComponentName componentName) {
+        PackageManager packageManager = context.getPackageManager();
+        // First get a set of installed package names
+        Set<String> installedPackageNames = packageManager
+                .getInstalledApplications(/* flags */ 0)
+                .stream()
+                .map(applicationInfo -> applicationInfo.packageName)
+                .collect(Collectors.toSet());
+        // Then filter all package names by those that are not installed
+        Set<String> uninstalledPackageNames = packageManager
+                .getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES)
+                .stream()
+                .map(applicationInfo -> applicationInfo.packageName)
+                .filter(((Predicate<String>) installedPackageNames::contains).negate())
+                .collect(Collectors.toSet());
+        // Assert that all apps are installed
+        assertTrue("system apps not installed: " + uninstalledPackageNames,
+                uninstalledPackageNames.isEmpty());
+    }
+
+    public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
+        runCrossUserVerification(
+                DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED, "assertAllSystemAppsInstalled");
+        PrimaryUserService.assertCrossUserCallArrived();
+    }
+
+    private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName)
+            throws Exception {
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        // Set affiliation id to allow communication.
+        mDevicePolicyManager.setAffiliationIds(getWho(), Collections.singleton(AFFILIATION_ID));
+
+        // Pack the affiliation id in a bundle so the secondary user can get it.
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(EXTRA_AFFILIATION_ID, AFFILIATION_ID);
+        bundle.putString(EXTRA_METHOD_NAME, methodName);
+
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                SecondaryUserAdminReceiver.getComponentName(getContext()),
+                bundle,
+                createAndManageUserFlags);
+        Log.d(TAG, "User create: " + userHandle);
+        assertEquals(UserManager.USER_OPERATION_SUCCESS,
+                mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+
+        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);
 
-        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
-                null, 0);
-        assertNotNull(mUserHandle);
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
+                getWho(), null, 0);
+        assertNotNull(userHandle);
     }
 
     public void testCreateAndManageUser_RemoveRestrictionSet() {
         mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
 
-        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
-                null, 0);
-        assertNotNull(mUserHandle);
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
+                getWho(), null, 0);
+        assertNotNull(userHandle);
 
-        boolean removed = mDevicePolicyManager.removeUser(getWho(), mUserHandle);
+        boolean removed = mDevicePolicyManager.removeUser(getWho(), userHandle);
         // When the device owner itself has set the user restriction, it should still be allowed
         // to remove a user.
         assertTrue(removed);
@@ -283,20 +559,22 @@
         LocalBroadcastReceiver receiver = new LocalBroadcastReceiver();
         LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
                 getContext());
-        localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_USER_ADDED));
+        localBroadcastManager.registerReceiver(receiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_ADDED));
+        UserHandle userHandle;
         try {
-            mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
+            userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
                     null, 0);
-            assertNotNull(mUserHandle);
-            assertEquals(mUserHandle, receiver.waitForBroadcastReceived());
+            assertNotNull(userHandle);
+            assertEquals(userHandle, receiver.waitForBroadcastReceived());
         } finally {
             localBroadcastManager.unregisterReceiver(receiver);
         }
-        localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_USER_REMOVED));
+        localBroadcastManager.registerReceiver(receiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
         try {
-            assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
-            assertEquals(mUserHandle, receiver.waitForBroadcastReceived());
-            mUserHandle = null;
+            assertTrue(mDevicePolicyManager.removeUser(getWho(), userHandle));
+            assertEquals(userHandle, receiver.waitForBroadcastReceived());
         } finally {
             localBroadcastManager.unregisterReceiver(receiver);
         }
@@ -307,7 +585,7 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            UserHandle userHandle = intent.getParcelableExtra(EXTRA_USER_HANDLE);
+            UserHandle userHandle = intent.getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
             Log.d(TAG, "broadcast receiver received " + intent + " with userHandle "
                     + userHandle);
             mQueue.offer(userHandle);
@@ -318,4 +596,95 @@
             return mQueue.poll(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
         }
     }
+
+    public static final class PrimaryUserService extends Service {
+        private static final Semaphore sSemaphore = new Semaphore(0);
+        private static String sError = null;
+
+        private final ICrossUserService.Stub mBinder = new ICrossUserService.Stub() {
+            public void onEnabledCalled(String error) {
+                Log.d(TAG, "onEnabledCalled on primary user");
+                sError = error;
+                sSemaphore.release();
+            }
+        };
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return mBinder;
+        }
+
+        static void assertCrossUserCallArrived() throws Exception {
+            assertTrue(sSemaphore.tryAcquire(ON_ENABLED_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+            if (sError != null) {
+                throw new Exception(sError);
+            }
+        }
+    }
+
+    public static final class SecondaryUserAdminReceiver extends DeviceAdminReceiver {
+        @Override
+        public void onEnabled(Context context, Intent intent) {
+            Log.d(TAG, "onEnabled called");
+            DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+            ComponentName who = getComponentName(context);
+
+            // Set affiliation ids
+            dpm.setAffiliationIds(
+                    who, Collections.singleton(intent.getStringExtra(EXTRA_AFFILIATION_ID)));
+
+            String error = null;
+            try {
+                Method method = CreateAndManageUserTest.class.getDeclaredMethod(
+                        intent.getStringExtra(EXTRA_METHOD_NAME), Context.class,
+                        DevicePolicyManager.class, ComponentName.class);
+                method.setAccessible(true);
+                method.invoke(null, context, dpm, who);
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                error = e.toString();
+            } catch (InvocationTargetException e) {
+                error = e.getCause().toString();
+            }
+
+            // Call all affiliated users
+            final List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(who);
+            assertEquals(1, targetUsers.size());
+            pingTargetUser(context, dpm, targetUsers.get(0), error);
+        }
+
+        private void pingTargetUser(Context context, DevicePolicyManager dpm, UserHandle target,
+                String error) {
+            Log.d(TAG, "Pinging target: " + target);
+            final ServiceConnection serviceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    Log.d(TAG, "onServiceConnected is called in " + Thread.currentThread().getName());
+                    ICrossUserService crossUserService = ICrossUserService
+                            .Stub.asInterface(service);
+                    try {
+                        crossUserService.onEnabledCalled(error);
+                    } catch (RemoteException re) {
+                        Log.e(TAG, "Error when calling primary user", re);
+                        // Do nothing, primary user will time out
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    Log.d(TAG, "onServiceDisconnected is called");
+                }
+            };
+            final Intent serviceIntent = new Intent(context, PrimaryUserService.class);
+            assertTrue(dpm.bindDeviceAdminServiceAsUser(
+                    getComponentName(context),
+                    serviceIntent,
+                    serviceConnection,
+                    Context.BIND_AUTO_CREATE,
+                    target));
+        }
+
+        public static ComponentName getComponentName(Context context) {
+            return new ComponentName(context, SecondaryUserAdminReceiver.class);
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl
new file mode 100644
index 0000000..b65a1bb
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceowner;
+
+interface ICrossUserService {
+    void onEnabledCalled(String error);
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
index 0b783a0..2d6b89f 100755
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
@@ -15,47 +15,87 @@
  */
 package com.android.cts.deviceowner;
 
+import static android.keystore.cts.CertificateUtils.createCertificate;
 import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
-import static com.android.cts.deviceowner.BaseDeviceOwnerTest.getWho;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 
-import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.keystore.cts.Attestation;
+import android.keystore.cts.AuthorizationList;
 import android.net.Uri;
+import android.os.Build;
+import android.security.AttestedKeyPair;
 import android.security.KeyChain;
 import android.security.KeyChainAliasCallback;
 import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.telephony.TelephonyManager;
 import android.test.ActivityInstrumentationTestCase2;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.cert.Certificate;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
+import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
 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.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.AssetManager;
+import java.util.HashSet;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Set;
+import javax.security.auth.x500.X500Principal;
 
 public class KeyManagementTest extends ActivityInstrumentationTestCase2<KeyManagementActivity> {
 
     private static final long KEYCHAIN_TIMEOUT_MINS = 6;
     private DevicePolicyManager mDevicePolicyManager;
 
+    private static class SupportedKeyAlgorithm {
+        public final String keyAlgorithm;
+        public final String signatureAlgorithm;
+        public final String[] signaturePaddingSchemes;
+
+        public SupportedKeyAlgorithm(
+                String keyAlgorithm, String signatureAlgorithm,
+                String[] signaturePaddingSchemes) {
+            this.keyAlgorithm = keyAlgorithm;
+            this.signatureAlgorithm = signatureAlgorithm;
+            this.signaturePaddingSchemes = signaturePaddingSchemes;
+        }
+    };
+
+    private final SupportedKeyAlgorithm[] SUPPORTED_KEY_ALGORITHMS = new SupportedKeyAlgorithm[] {
+        new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA, "SHA256withRSA",
+                new String[] {KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1}),
+        new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_EC, "SHA256withECDSA", null)
+    };
+
     public KeyManagementTest() {
         super(KeyManagementActivity.class);
     }
@@ -67,7 +107,7 @@
         // Confirm our DeviceOwner is set up
         mDevicePolicyManager = (DevicePolicyManager)
                 getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
-        BaseDeviceOwnerTest.assertDeviceOwner(mDevicePolicyManager);
+        assertDeviceOwner(mDevicePolicyManager);
 
         // Hostside test has set a device lockscreen in order to enable credential storage
     }
@@ -129,17 +169,23 @@
         assertGranted(withhold, false);
     }
 
+    private List<Certificate> loadCertificateChain(String assetName) throws Exception {
+        final Collection<Certificate> certs = loadCertificatesFromAsset(assetName);
+        final ArrayList<Certificate> certChain = new ArrayList(certs);
+        // Some sanity check on the cert chain
+        assertTrue(certs.size() > 1);
+        for (int i = 1; i < certChain.size(); i++) {
+            certChain.get(i - 1).verify(certChain.get(i).getPublicKey());
+        }
+        return certChain;
+    }
+
     public void testCanInstallCertChain() throws Exception {
         // Use assets/generate-client-cert-chain.sh to regenerate the client cert chain.
         final PrivateKey privKey = loadPrivateKeyFromAsset("user-cert-chain.key");
-        final Collection<Certificate> certs = loadCertificatesFromAsset("user-cert-chain.crt");
-        final Certificate[] certChain = certs.toArray(new Certificate[certs.size()]);
+        final Certificate[] certChain = loadCertificateChain("user-cert-chain.crt")
+                .toArray(new Certificate[0]);
         final String alias = "com.android.test.clientkeychain";
-        // Some sanity check on the cert chain
-        assertTrue(certs.size() > 1);
-        for (int i = 1; i < certs.size(); i++) {
-            certChain[i - 1].verify(certChain[i].getPublicKey());
-        }
 
         // Install keypairs.
         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
@@ -215,11 +261,302 @@
         }
     }
 
+    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 Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
+
+        // Install keypair.
+        assertTrue(mDevicePolicyManager.installKeyPair(
+            getWho(), privKey, new Certificate[] {cert}, alias, false, false));
+        try {
+            // Request and retrieve using the alias.
+            assertGranted(alias, false);
+            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            assertGranted(alias, true);
+        } finally {
+            // Delete regardless of whether the test succeeded.
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+        byte[] data = new String("hello").getBytes();
+        Signature sign = Signature.getInstance(algoIdentifier);
+        sign.initSign(privateKey);
+        sign.update(data);
+        return sign.sign();
+    }
+
+    void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+            throws Exception {
+        byte[] data = new String("hello").getBytes();
+        Signature verify = Signature.getInstance(algoIdentifier);
+        verify.initVerify(publicKey);
+        verify.update(data);
+        assertTrue(verify.verify(signature));
+    }
+
+    void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+        verifySignature(algoIdentifier, keyPair.getPublic(),
+                signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+    }
+
+    public void testCanGenerateRSAKeyPair() throws Exception {
+        final String alias = "com.android.test.generated-rsa-1";
+        try {
+            KeyGenParameterSpec spec = 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)
+                    .build();
+
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "RSA", spec, 0);
+            assertNotNull(generated);
+            verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    public void testCanGenerateECKeyPair() throws Exception {
+        final String alias = "com.android.test.generated-ec-1";
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .build();
+
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", spec, 0);
+            assertNotNull(generated);
+            verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    private void validateDeviceIdAttestationData(Certificate leaf,
+            String expectedSerial, String expectedImei, String expectedMeid)
+            throws CertificateParsingException {
+        Attestation attestationRecord = new Attestation((X509Certificate) leaf);
+        AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
+        assertNotNull(teeAttestation);
+        assertEquals(Build.BRAND, teeAttestation.getBrand());
+        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());
+    }
+
+    private void validateAttestationRecord(List<Certificate> attestation,
+            byte[] providedChallenge) throws CertificateParsingException {
+        assertNotNull(attestation);
+        assertTrue(attestation.size() >= 2);
+        X509Certificate leaf = (X509Certificate) attestation.get(0);
+        Attestation attestationRecord = new Attestation(leaf);
+        assertTrue(Arrays.equals(providedChallenge,
+                    attestationRecord.getAttestationChallenge()));
+    }
+
+    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()));
+        // Check that the certificate chain is valid.
+        for (int i = 1; i < chain.size(); i++) {
+            X509Certificate intermediate = (X509Certificate) chain.get(i);
+            PublicKey intermediateKey = intermediate.getPublicKey();
+            leaf.verify(intermediateKey);
+            leaf = intermediate;
+        }
+
+        // leaf is now the root, verify the root is self-signed.
+        PublicKey rootKey = leaf.getPublicKey();
+        leaf.verify(rootKey);
+    }
+
+    /**
+     * Generates a key using DevicePolicyManager.generateKeyPair using the given key algorithm,
+     * then test signing and verifying using generated key.
+     * If {@code signaturePaddings} is not null, it is added to the key parameters specification.
+     * Returns the Attestation leaf certificate.
+     */
+    private Certificate generateKeyAndCheckAttestation(
+            String keyAlgorithm, String signatureAlgorithm,
+            String[] signaturePaddings, int deviceIdAttestationFlags)
+            throws Exception {
+        final String alias =
+                String.format("com.android.test.attested-%s", keyAlgorithm.toLowerCase());
+        byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+        try {
+            KeyGenParameterSpec.Builder specBuilder =  new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setAttestationChallenge(attestationChallenge);
+            if (signaturePaddings != null) {
+                specBuilder.setSignaturePaddings(signaturePaddings);
+            }
+
+            KeyGenParameterSpec spec = specBuilder.build();
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), keyAlgorithm, spec, deviceIdAttestationFlags);
+            assertNotNull(
+                    String.format("Failed generation of %s key with Device ID attestation %d",
+                        keyAlgorithm, deviceIdAttestationFlags), generated);
+            final KeyPair keyPair = generated.getKeyPair();
+            verifySignatureOverData(signatureAlgorithm, keyPair);
+            List<Certificate> attestation = generated.getAttestationRecord();
+            validateAttestationRecord(attestation, attestationChallenge);
+            validateSignatureChain(attestation, keyPair.getPublic());
+            return attestation.get(0);
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    /**
+     * Test key generation, including requesting Key Attestation, for all supported key
+     * algorithms.
+     */
+    public void testCanGenerateKeyPairWithKeyAttestation() throws Exception {
+        for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
+            generateKeyAndCheckAttestation(
+                    supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
+                    supportedKey.signaturePaddingSchemes, 0);
+        }
+    }
+
+    public void testAllVariationsOfDeviceIdAttestation() throws Exception {
+        List<Integer> modesToTest = new ArrayList<Integer>();
+        // All devices must support at least basic device information attestation as well as serial
+        // number attestation.
+        modesToTest.add(ID_TYPE_BASE_INFO);
+        modesToTest.add(ID_TYPE_SERIAL);
+        // Get IMEI and MEID of the device.
+        TelephonyManager telephonyService = (TelephonyManager) getActivity().getSystemService(
+                Context.TELEPHONY_SERVICE);
+        assertNotNull("Need to be able to read device identifiers", telephonyService);
+        String imei = telephonyService.getImei(0);
+        // If the device has a valid IMEI it must support attestation for it.
+        if (imei != null) {
+            modesToTest.add(ID_TYPE_IMEI);
+        }
+
+        int numCombinations = 1 << modesToTest.size();
+        for (int i = 1; i < numCombinations; i++) {
+            // Set the bits in devIdOpt to be passed into generateKeyPair according to the
+            // current modes tested.
+            int devIdOpt = 0;
+            for (int j = 0; j < modesToTest.size(); j++) {
+                if ((i & (1 << j)) != 0) {
+                    devIdOpt = devIdOpt | modesToTest.get(j);
+                }
+            }
+            // Now run the test with all supported key algorithms
+            for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
+                Certificate attestation = generateKeyAndCheckAttestation(
+                        supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
+                        supportedKey.signaturePaddingSchemes, devIdOpt);
+                assertNotNull(String.format(
+                        "Attestation should be valid for key %s with attestation modes %s",
+                        supportedKey.keyAlgorithm, devIdOpt), attestation);
+                // Set the expected values for serial, IMEI and MEID depending on whether
+                // attestation for them was requested.
+                String expectedSerial = null;
+                if ((devIdOpt & ID_TYPE_SERIAL) != 0) {
+                    expectedSerial = Build.getSerial();
+                }
+                String expectedImei = null;
+                if ((devIdOpt & ID_TYPE_IMEI) != 0) {
+                    expectedImei = imei;
+                }
+                // Expected MEID is always null for now.
+                // TODO: Figure out a better way to identify whether MEID attestation on the
+                // device should work.
+                String expectedMeid = null;
+                validateDeviceIdAttestationData(attestation, expectedSerial, expectedImei,
+                        expectedMeid);
+            }
+        }
+    }
+
+    public void testCanSetKeyPairCert() throws Exception {
+        final String alias = "com.android.test.set-ec-1";
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .build();
+
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", spec, 0);
+            assertNotNull(generated);
+            // 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");
+            X509Certificate cert = createCertificate(generated.getKeyPair(), subject, issuer);
+            // Set the certificate chain
+            List<Certificate> certs = new ArrayList<Certificate>();
+            certs.add(cert);
+            mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, certs, true);
+            // Make sure that the alias can now be obtained.
+            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            // And can be retrieved from KeyChain
+            X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(getActivity(), alias);
+            assertEquals(fetchedCerts.length, certs.size());
+            assertTrue(Arrays.equals(fetchedCerts[0].getEncoded(), certs.get(0).getEncoded()));
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    public void testCanSetKeyPairCertChain() throws Exception {
+        final String alias = "com.android.test.set-ec-2";
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .build();
+
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", spec, 0);
+            assertNotNull(generated);
+            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());
+            // And can be retrieved from KeyChain
+            X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(getActivity(), alias);
+            assertEquals(fetchedCerts.length, chain.size());
+            for (int i = 0; i < chain.size(); i++) {
+                assertTrue(Arrays.equals(fetchedCerts[i].getEncoded(), chain.get(i).getEncoded()));
+            }
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
     private void assertGranted(String alias, boolean expected) throws InterruptedException {
         boolean granted = false;
         try {
             granted = (KeyChain.getPrivateKey(getActivity(), alias) != null);
         } catch (KeyChainException e) {
+            if (expected) {
+                e.printStackTrace();
+            }
         }
         assertEquals("Grant for alias: \"" + alias + "\"", expected, granted);
     }
@@ -289,4 +626,15 @@
             return mChosenAlias;
         }
     }
+
+    private void assertDeviceOwner(DevicePolicyManager devicePolicyManager) {
+        assertNotNull(devicePolicyManager);
+        assertTrue(devicePolicyManager.isAdminActive(getWho()));
+        assertTrue(devicePolicyManager.isDeviceOwnerApp(getActivity().getPackageName()));
+        assertFalse(devicePolicyManager.isManagedProfile(getWho()));
+    }
+
+    private ComponentName getWho() {
+        return BasicAdminReceiver.getComponentName(getActivity());
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
index ce79922..f9f9a33 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
@@ -45,10 +45,6 @@
 
     private static final String TAG = LockTaskHostDrivenTest.class.getName();
 
-    private static final String PACKAGE_NAME = LockTaskHostDrivenTest.class.getPackage().getName();
-    private static final ComponentName ADMIN_COMPONENT =
-            new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
-
     private static final String LOCK_TASK_ACTIVITY
             = LockTaskUtilityActivityIfWhitelisted.class.getName();
 
@@ -60,7 +56,7 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
-        mDevicePolicyManager =  mContext.getSystemService(DevicePolicyManager.class);
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
     }
@@ -96,9 +92,11 @@
 
     @Test
     public void clearDefaultHomeIntentReceiver() {
-        mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_COMPONENT,
-                PACKAGE_NAME);
-        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
+        mDevicePolicyManager.clearPackagePersistentPreferredActivities(
+                BasicAdminReceiver.getComponentName(mContext),
+                mContext.getPackageName());
+        mDevicePolicyManager.setLockTaskPackages(BasicAdminReceiver.getComponentName(mContext),
+                new String[0]);
     }
 
     private void checkLockedActivityIsRunning() throws Exception {
@@ -113,19 +111,20 @@
     }
 
     private void launchLockTaskActivity() {
-        Intent intent = new Intent();
-        intent.setClassName(PACKAGE_NAME, LOCK_TASK_ACTIVITY);
+        Intent intent = new Intent(mContext, LockTaskUtilityActivityIfWhitelisted.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(LockTaskUtilityActivity.START_LOCK_TASK, true);
         mContext.startActivity(intent);
     }
 
     private void setDefaultHomeIntentReceiver() {
-        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+        mDevicePolicyManager.setLockTaskPackages(BasicAdminReceiver.getComponentName(mContext),
+                new String[]{mContext.getPackageName()});
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
         intentFilter.addCategory(Intent.CATEGORY_HOME);
         intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
-        mDevicePolicyManager.addPersistentPreferredActivity(ADMIN_COMPONENT, intentFilter,
-                new ComponentName(PACKAGE_NAME, LOCK_TASK_ACTIVITY));
+        mDevicePolicyManager.addPersistentPreferredActivity(
+                BasicAdminReceiver.getComponentName(mContext), intentFilter,
+                new ComponentName(mContext.getPackageName(), LOCK_TASK_ACTIVITY));
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
index 9c5380a..c3d59bc 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
@@ -15,14 +15,23 @@
  */
 package com.android.cts.deviceowner;
 
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
 
-import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -39,6 +48,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 public class LockTaskTest {
 
@@ -46,7 +57,7 @@
 
     private static final String PACKAGE_NAME = LockTaskTest.class.getPackage().getName();
     private static final ComponentName ADMIN_COMPONENT =
-            new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
+            new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
     private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
 
     private static final String UTILITY_ACTIVITY
@@ -54,18 +65,23 @@
     private static final String UTILITY_ACTIVITY_IF_WHITELISTED
             = "com.android.cts.deviceowner.LockTaskUtilityActivityIfWhitelisted";
 
-    private static final String RECEIVING_ACTIVITY_PACKAGE_NAME
-            = "com.android.cts.intent.receiver";
-    private static final String RECEIVING_ACTIVITY_NAME
-            = "com.android.cts.intent.receiver.IntentReceiverActivity";
+    private static final String RECEIVER_ACTIVITY_PACKAGE_NAME =
+            "com.android.cts.intent.receiver";
+    private static final String RECEIVER_ACTIVITY_NAME =
+            "com.android.cts.intent.receiver.IntentReceiverActivity";
     private static final String ACTION_JUST_CREATE =
             "com.android.cts.action.JUST_CREATE";
+    private static final String ACTION_CREATE_AND_WAIT =
+            "com.android.cts.action.CREATE_AND_WAIT";
+    private static final String RECEIVER_ACTIVITY_CREATED_ACTION =
+            "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_CREATED";
+    private static final String RECEIVER_ACTIVITY_DESTROYED_ACTION =
+            "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_DESTROYED";
 
-    private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 20000;  // 20 seconds
-    private static final int ACTIVITY_RUNNING_TIMEOUT_MILLIS = 10000;  // 10 seconds
-    private static final int ACTIVITY_DESTROYED_TIMEOUT_MILLIS = 60000;  // 60 seconds
-    public static final String RECEIVING_ACTIVITY_CREATED_ACTION
-            = "com.android.cts.deviceowner.RECEIVING_ACTIVITY_CREATED_ACTION";
+    private static final long ACTIVITY_RESUMED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+    private static final long ACTIVITY_RUNNING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+    private static final long ACTIVITY_DESTROYED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
+
     /**
      * The tests below need to keep detailed track of the state of the activity
      * that is started and stopped frequently.  To do this it sends a number of
@@ -105,31 +121,27 @@
                     mIntentHandled = true;
                     LockTaskTest.this.notify();
                 }
-            } else if (RECEIVING_ACTIVITY_CREATED_ACTION.equals(action)) {
-                synchronized(mReceivingActivityCreatedLock) {
-                    mReceivingActivityWasCreated = true;
-                    mReceivingActivityCreatedLock.notify();
+            } else if (RECEIVER_ACTIVITY_CREATED_ACTION.equals(action)) {
+                synchronized(mReceiverActivityRunningLock) {
+                    mIsReceiverActivityRunning = true;
+                    mReceiverActivityRunningLock.notify();
+                }
+            } else if (RECEIVER_ACTIVITY_DESTROYED_ACTION.equals(action)) {
+                synchronized (mReceiverActivityRunningLock) {
+                    mIsReceiverActivityRunning = false;
+                    mReceiverActivityRunningLock.notify();
                 }
             }
         }
     };
 
-    public static class IntentReceivingActivity extends Activity {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            sendBroadcast(new Intent(RECEIVING_ACTIVITY_CREATED_ACTION));
-            finish();
-        }
-    }
-
     private volatile boolean mIsActivityRunning;
     private volatile boolean mIsActivityResumed;
-    private volatile boolean mReceivingActivityWasCreated;
+    private volatile boolean mIsReceiverActivityRunning;
     private volatile boolean mIntentHandled;
     private final Object mActivityRunningLock = new Object();
     private final Object mActivityResumedLock = new Object();
-    private final Object mReceivingActivityCreatedLock = new Object();
+    private final Object mReceiverActivityRunningLock = new Object();
 
     private Context mContext;
     private ActivityManager mActivityManager;
@@ -139,17 +151,17 @@
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
 
-        mDevicePolicyManager = (DevicePolicyManager)
-                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
         IntentFilter filter = new IntentFilter();
         filter.addAction(LockTaskUtilityActivity.CREATE_ACTION);
         filter.addAction(LockTaskUtilityActivity.DESTROY_ACTION);
         filter.addAction(LockTaskUtilityActivity.INTENT_ACTION);
         filter.addAction(LockTaskUtilityActivity.RESUME_ACTION);
         filter.addAction(LockTaskUtilityActivity.PAUSE_ACTION);
-        filter.addAction(RECEIVING_ACTIVITY_CREATED_ACTION);
+        filter.addAction(RECEIVER_ACTIVITY_CREATED_ACTION);
+        filter.addAction(RECEIVER_ACTIVITY_DESTROYED_ACTION);
         mContext.registerReceiver(mReceiver, filter);
     }
 
@@ -172,6 +184,41 @@
         assertFalse(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
     }
 
+    // Setting and unsetting the lock task features. The actual UI behavior is tested with CTS
+    // verifier.
+    @Test
+    public void testSetLockTaskFeatures() {
+        final int[] flags = new int[] {
+                LOCK_TASK_FEATURE_SYSTEM_INFO,
+                LOCK_TASK_FEATURE_NOTIFICATIONS,
+                LOCK_TASK_FEATURE_HOME,
+                LOCK_TASK_FEATURE_OVERVIEW,
+                LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                LOCK_TASK_FEATURE_KEYGUARD
+        };
+
+        int cumulative = LOCK_TASK_FEATURE_NONE;
+        for (int flag : flags) {
+            if (flag == LOCK_TASK_FEATURE_OVERVIEW) {
+                // Overview can only be used in combination with HOME
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () -> mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, flag));
+            } else {
+                mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, flag);
+                assertEquals(flag, mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+            }
+
+            cumulative |= flag;
+            mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, cumulative);
+            assertEquals(cumulative, mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+
+            mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, LOCK_TASK_FEATURE_NONE);
+            assertEquals(LOCK_TASK_FEATURE_NONE,
+                    mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+        }
+    }
+
     // Start lock task, verify that ActivityManager knows thats what is going on.
     @Test
     public void testStartLockTask() throws Exception {
@@ -219,6 +266,40 @@
         assertFalse(mIsActivityResumed);
     }
 
+    // Verifies that removing the whitelist authorization immediately finishes the corresponding
+    // locked task. The other locked task(s) should remain locked.
+    @Test
+    public void testUpdateWhitelisting_twoTasks() throws Exception {
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
+                RECEIVER_ACTIVITY_PACKAGE_NAME});
+
+        // Start first locked task
+        startLockTask(UTILITY_ACTIVITY);
+        waitForResume();
+
+        // Start the other task from the running activity
+        mIsReceiverActivityRunning = false;
+        Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, true /*shouldWait*/);
+        mContext.startActivity(launchIntent);
+        synchronized (mReceiverActivityRunningLock) {
+            mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+            assertTrue(mIsReceiverActivityRunning);
+        }
+
+        // Remove whitelist authorization of the second task
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+        synchronized (mReceiverActivityRunningLock) {
+            mReceiverActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
+            assertFalse(mIsReceiverActivityRunning);
+        }
+
+        assertLockTaskModeActive();
+        assertTrue(mIsActivityRunning);
+        assertTrue(mIsActivityResumed);
+
+        stopAndFinish(UTILITY_ACTIVITY);
+    }
+
     // This launches an activity that is in the current task.
     // This should always be permitted as a part of lock task (since it isn't a new task).
     @Test
@@ -227,15 +308,15 @@
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
-        mReceivingActivityWasCreated = false;
-        Intent launchIntent = getIntentReceivingActivityIntent(0);
+        mIsReceiverActivityRunning = false;
+        Intent launchIntent = createReceiverActivityIntent(false /*newTask*/, false /*shouldWait*/);
         Intent lockTaskUtility = getLockTaskUtility(UTILITY_ACTIVITY);
         lockTaskUtility.putExtra(LockTaskUtilityActivity.START_ACTIVITY, launchIntent);
         mContext.startActivity(lockTaskUtility);
 
-        synchronized (mReceivingActivityCreatedLock) {
-            mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
-            assertTrue(mReceivingActivityWasCreated);
+        synchronized (mReceiverActivityRunningLock) {
+            mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+            assertTrue(mIsReceiverActivityRunning);
         }
         stopAndFinish(UTILITY_ACTIVITY);
     }
@@ -245,17 +326,16 @@
     @Test
     public void testStartActivity_outsideTaskWhitelisted() throws Exception {
         mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
-                RECEIVING_ACTIVITY_PACKAGE_NAME });
+                RECEIVER_ACTIVITY_PACKAGE_NAME});
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
-        mReceivingActivityWasCreated = false;
-        Intent launchIntent = getIntentReceivingActivityIntent(0);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mIsReceiverActivityRunning = false;
+        Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, false /*shouldWait*/);
         mContext.startActivity(launchIntent);
-        synchronized (mReceivingActivityCreatedLock) {
-            mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
-            assertTrue(mReceivingActivityWasCreated);
+        synchronized (mReceiverActivityRunningLock) {
+            mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+            assertTrue(mIsReceiverActivityRunning);
         }
         stopAndFinish(UTILITY_ACTIVITY);
     }
@@ -268,11 +348,11 @@
         startLockTask(UTILITY_ACTIVITY);
         waitForResume();
 
-        Intent launchIntent = getIntentReceivingActivityIntent(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, false /*shouldWait*/);
         mContext.startActivity(launchIntent);
         synchronized (mActivityResumedLock) {
             mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
-            assertFalse(mReceivingActivityWasCreated);
+            assertFalse(mIsReceiverActivityRunning);
         }
         stopAndFinish(UTILITY_ACTIVITY);
     }
@@ -341,6 +421,27 @@
         assertFalse(mIsActivityResumed);
     }
 
+    // Start lock task with ActivityOptions
+    @Test
+    public void testActivityOptions_whitelisted() throws Exception {
+        mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+        startLockTaskWithOptions(UTILITY_ACTIVITY);
+        waitForResume();
+
+        // Verify that activity open and activity manager is in lock task.
+        assertLockTaskModeActive();
+        assertTrue(mIsActivityRunning);
+        assertTrue(mIsActivityResumed);
+
+        stopAndFinish(UTILITY_ACTIVITY);
+    }
+
+    // Starting a non-whitelisted activity with ActivityOptions is not allowed
+    @Test(expected = SecurityException.class)
+    public void testActivityOptions_nonWhitelisted() throws Exception {
+        startLockTaskWithOptions(UTILITY_ACTIVITY);
+    }
+
     /**
      * Checks that lock task mode is active and fails the test if it isn't.
      */
@@ -412,6 +513,15 @@
     }
 
     /**
+     * Starts LockTaskUtilityActivity with {@link ActivityOptions#setLockTaskEnabled(boolean)}
+     */
+    private void startLockTaskWithOptions(String className) throws InterruptedException {
+        Intent intent = getLockTaskUtility(className);
+        Bundle options = ActivityOptions.makeBasic().setLockTaskEnabled(true).toBundle();
+        startAndWait(intent, options);
+    }
+
+    /**
      * Calls stopLockTask on the LockTaskUtilityActivity
      */
     private void stopLockTask(String className) throws InterruptedException {
@@ -435,9 +545,16 @@
      * the command.
      */
     private void startAndWait(Intent intent) throws InterruptedException {
+        startAndWait(intent, null);
+    }
+
+    /**
+     * Same as {@link #startAndWait(Intent)}, but with additional {@link ActivityOptions}.
+     */
+    private void startAndWait(Intent intent, Bundle options) throws InterruptedException {
         mIntentHandled = false;
         synchronized (this) {
-            mContext.startActivity(intent);
+            mContext.startActivity(intent, options);
             // Give 20 secs to finish.
             wait(ACTIVITY_RUNNING_TIMEOUT_MILLIS);
             assertTrue(mIntentHandled);
@@ -456,12 +573,13 @@
         return intent;
     }
 
-    private Intent getIntentReceivingActivityIntent(int flags) {
-        Intent intent = new Intent();
+    /** Create an intent to launch {@link #RECEIVER_ACTIVITY_NAME}. */
+    private Intent createReceiverActivityIntent(boolean newTask, boolean shouldWait) {
+        final Intent intent = new Intent();
         intent.setComponent(
-                new ComponentName(RECEIVING_ACTIVITY_PACKAGE_NAME, RECEIVING_ACTIVITY_NAME));
-        intent.setAction(ACTION_JUST_CREATE);
-        intent.setFlags(flags);
+                new ComponentName(RECEIVER_ACTIVITY_PACKAGE_NAME, RECEIVER_ACTIVITY_NAME));
+        intent.setAction(shouldWait ? ACTION_CREATE_AND_WAIT : ACTION_JUST_CREATE);
+        intent.setFlags(newTask ? Intent.FLAG_ACTIVITY_NEW_TASK : 0);
         return intent;
     }
 }
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 6b62e88..cb723d5 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
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.support.test.InstrumentationRegistry;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
@@ -36,6 +37,7 @@
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -43,6 +45,7 @@
 public class NetworkLoggingTest extends BaseDeviceOwnerTest {
 
     private static final String TAG = "NetworkLoggingTest";
+    private static final String ARG_BATCH_COUNT = "batchCount";
     private static final int FAKE_BATCH_TOKEN = -666; // real batch tokens are always non-negative
     private static final int FULL_LOG_BATCH_SIZE = 1200;
     private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.deviceowner";
@@ -71,20 +74,28 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (BaseDeviceOwnerTest.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
-                mGenerateNetworkTraffic = false;
-                mCurrentBatchToken = intent.getLongExtra(
-                        BaseDeviceOwnerTest.EXTRA_NETWORK_LOGS_BATCH_TOKEN, FAKE_BATCH_TOKEN);
-                if (mCountDownLatch != null) {
-                    mCountDownLatch.countDown();
+            if (BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
+                long token =
+                        intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
+                                FAKE_BATCH_TOKEN);
+                // Retrieve network logs.
+                final List<NetworkEvent> events = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
+                        token);
+                if (events == null) {
+                    fail("Failed to retrieve batch of network logs with batch token " + token);
+                    return;
                 }
+                if (mBatchCountDown.getCount() > 0) {
+                    mNetworkEvents.addAll(events);
+                }
+                mBatchCountDown.countDown();
             }
         }
     };
 
-    private CountDownLatch mCountDownLatch;
-    private long mCurrentBatchToken;
-    private volatile boolean mGenerateNetworkTraffic;
+    private CountDownLatch mBatchCountDown;
+    private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+    private int mBatchesRequested = 1;
 
     @Override
     protected void tearDown() throws Exception {
@@ -124,12 +135,11 @@
      * traffic, so that the batch of logs is created
      */
     public void testNetworkLoggingAndRetrieval() throws Exception {
-        mCountDownLatch = new CountDownLatch(1);
-        mCurrentBatchToken = FAKE_BATCH_TOKEN;
-        mGenerateNetworkTraffic = true;
+        mBatchesRequested = InstrumentationRegistry.getArguments().getInt(ARG_BATCH_COUNT, 1);
+        mBatchCountDown = new CountDownLatch(mBatchesRequested);
         // register a receiver that listens for DeviceAdminReceiver#onNetworkLogsAvailable()
         final IntentFilter filterNetworkLogsAvailable = new IntentFilter(
-                BaseDeviceOwnerTest.ACTION_NETWORK_LOGS_AVAILABLE);
+                BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE);
         LocalBroadcastManager.getInstance(mContext).registerReceiver(mNetworkLogsReceiver,
                 filterNetworkLogsAvailable);
 
@@ -144,36 +154,41 @@
 
         // TODO: here test that facts about logging are shown in the UI
 
+        // Fetch and verify the batches of events.
+        generateBatches();
+    }
+
+    private void generateBatches() throws Exception {
         // visit websites to verify their dns lookups are logged
         for (final String url : LOGGED_URLS_LIST) {
             connectToWebsite(url);
         }
 
-        // generate enough traffic to fill a batch.
-        int dummyReqNo = generateDummyTraffic();
+        // generate enough traffic to fill the batches.
+        int dummyReqNo = 0;
+        for (int i = 0; i < mBatchesRequested; i++) {
+            dummyReqNo += generateDummyTraffic();
+        }
 
         // if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
-        // 3 minutes just in case
-        mCountDownLatch.await(3, TimeUnit.MINUTES);
+        // 3 minutes per batch just in case
+        int timeoutMins = 3 * mBatchesRequested;
+        mBatchCountDown.await(timeoutMins, TimeUnit.MINUTES);
         LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mNetworkLogsReceiver);
-        if (mGenerateNetworkTraffic) {
-            fail("Carried out 100 iterations and waited for 3 minutes, but still didn't get"
+        if (mBatchCountDown.getCount() > 0) {
+            fail("Generated events for " + mBatchesRequested + " batches and waited for "
+                    + timeoutMins + " minutes, but still didn't get"
                     + " DeviceAdminReceiver#onNetworkLogsAvailable() callback");
         }
 
-        // retrieve and verify network logs
-        final List<NetworkEvent> networkEvents = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
-                mCurrentBatchToken);
-        if (networkEvents == null) {
-            fail("Failed to retrieve batch of network logs with batch token " + mCurrentBatchToken);
-            return;
-        }
-
+        // Verify network logs.
+        assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
         // For each of the real URLs we have two events: one DNS and one connect. Dummy requests
         // don't require DNS queries.
         int eventsExpected =
-                Math.min(FULL_LOG_BATCH_SIZE, 2 * LOGGED_URLS_LIST.length + dummyReqNo);
-        verifyNetworkLogs(networkEvents, eventsExpected);
+                Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
+                        2 * LOGGED_URLS_LIST.length + dummyReqNo);
+        verifyNetworkLogs(mNetworkEvents, eventsExpected);
     }
 
     private void verifyNetworkLogs(List<NetworkEvent> networkEvents, int eventsExpected) {
@@ -189,6 +204,10 @@
             if (i > 0) {
                 assertTrue(currentEvent.getTimestamp() >= networkEvents.get(i - 1).getTimestamp());
             }
+            // verify that the event IDs are monotonically increasing
+            if (i > 0) {
+                assertTrue(currentEvent.getId() == (networkEvents.get(i - 1).getId() + 1));
+            }
             // count how many events come from the CTS app
             if (CTS_APP_PACKAGE_NAME.equals(currentEvent.getPackageName())) {
                 ctsPackageNameCounter++;
@@ -270,8 +289,8 @@
 
     private int makeDummyRequests(int port) {
         int reqNo;
-        final String DUMMY_SERVER = "127.0.0.1:" + port + "";
-        for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mGenerateNetworkTraffic; reqNo++) {
+        final String DUMMY_SERVER = "127.0.0.1:" + port;
+        for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mBatchCountDown.getCount() > 0; reqNo++) {
             connectToWebsite(DUMMY_SERVER);
             try {
                 // Just to prevent choking the server.
@@ -303,7 +322,7 @@
                     output.flush();
                     output.close();
                 } catch (IOException e) {
-                    if (mGenerateNetworkTraffic) {
+                    if (mBatchCountDown.getCount() > 0) {
                         Log.w(TAG, "Failed to serve connection", e);
                     } else {
                         break;
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/OverrideApnTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/OverrideApnTest.java
new file mode 100644
index 0000000..73c6653
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/OverrideApnTest.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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.telephony.data.ApnSetting;
+
+import java.net.InetAddress;
+import java.util.List;
+
+/**
+ * Test override APN APIs.
+ */
+public class OverrideApnTest extends BaseDeviceOwnerTest {
+    private static final String TEST_APN_NAME = "testApnName";
+    private static final String UPDATE_APN_NAME = "updateApnName";
+    private static final String TEST_ENTRY_NAME = "testEntryName";
+    private static final String UPDATE_ETNRY_NAME = "updateEntryName";
+    private static final String TEST_OPERATOR_NUMERIC = "123456789";
+    private static final int TEST_PORT = 123;
+
+    private ApnSetting getTestApn() throws Exception {
+        return new ApnSetting.Builder()
+                .setApnName(TEST_APN_NAME)
+                .setEntryName(TEST_ENTRY_NAME)
+                .setOperatorNumeric(TEST_OPERATOR_NUMERIC)
+                .setPort(TEST_PORT)
+                .setProxy(getTestProxy())
+                .build();
+    }
+
+    private InetAddress getTestProxy() throws Exception {
+        return InetAddress.getByName("123.123.123.123");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        List <ApnSetting> apnList = mDevicePolicyManager.getOverrideApns(getWho());
+        for (ApnSetting apn : apnList) {
+            boolean deleted = mDevicePolicyManager.removeOverrideApn(getWho(), apn.getId());
+            assertTrue("Fail to clean up override APNs.", deleted);
+        }
+        mDevicePolicyManager.setOverrideApnsEnabled(getWho(), false);
+    }
+
+    public void testAddGetRemoveOverrideApn() throws Exception {
+        int insertedId = mDevicePolicyManager.addOverrideApn(getWho(), getTestApn());
+        assertTrue(insertedId != 0);
+        List <ApnSetting> apnList = mDevicePolicyManager.getOverrideApns(getWho());
+        assertEquals(1, apnList.size());
+        assertEquals(TEST_APN_NAME, apnList.get(0).getApnName());
+        assertEquals(TEST_ENTRY_NAME, apnList.get(0).getEntryName());
+        assertEquals(TEST_OPERATOR_NUMERIC, apnList.get(0).getOperatorNumeric());
+        assertEquals(TEST_PORT, apnList.get(0).getPort());
+        assertEquals(getTestProxy(), apnList.get(0).getProxy());
+        assertTrue(mDevicePolicyManager.removeOverrideApn(getWho(), insertedId));
+        apnList = mDevicePolicyManager.getOverrideApns(getWho());
+        assertEquals(0, apnList.size());
+    }
+
+    public void testRemoveOverrideApnFailsForIncorrectId() throws Exception {
+        assertFalse(mDevicePolicyManager.removeOverrideApn(getWho(), -1));
+    }
+
+    public void testUpdateOverrideApn() throws Exception {
+        int insertedId = mDevicePolicyManager.addOverrideApn(getWho(), getTestApn());
+        assertTrue(insertedId != 0);
+
+        final ApnSetting updateApn = (new ApnSetting.Builder())
+                .setEntryName(UPDATE_ETNRY_NAME)
+                .setApnName(UPDATE_APN_NAME)
+                .build();
+        assertTrue(mDevicePolicyManager.updateOverrideApn(getWho(), insertedId, updateApn));
+
+        List <ApnSetting> apnList = mDevicePolicyManager.getOverrideApns(getWho());
+        assertEquals(1, apnList.size());
+        assertEquals(UPDATE_APN_NAME, apnList.get(0).getApnName());
+        assertEquals(UPDATE_ETNRY_NAME, apnList.get(0).getEntryName());
+        // The old entry got completely replaced by the new APN.
+        assertEquals("", apnList.get(0).getOperatorNumeric());
+        assertEquals(-1, apnList.get(0).getPort());
+        assertEquals(null, apnList.get(0).getProxy());
+        assertTrue(mDevicePolicyManager.removeOverrideApn(getWho(), insertedId));
+    }
+
+    public void testSetAndGetOverrideApnEnabled() throws Exception {
+        mDevicePolicyManager.setOverrideApnsEnabled(getWho(), true);
+        assertTrue(mDevicePolicyManager.isOverrideApnEnabled(getWho()));
+        mDevicePolicyManager.setOverrideApnsEnabled(getWho(), false);
+        assertFalse(mDevicePolicyManager.isOverrideApnEnabled(getWho()));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
new file mode 100644
index 0000000..5280e06
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
@@ -0,0 +1,140 @@
+package com.android.cts.deviceowner;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+
+/**
+ * Test case for package install and uninstall.
+ */
+public class PackageInstallTest extends BaseAffiliatedProfileOwnerTest {
+    private static final String TEST_APP_LOCATION =
+            "/data/local/tmp/cts/packageinstaller/CtsEmptyTestApp.apk";
+    private static final String TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+    private static final int REQUEST_CODE = 0;
+    private static final String ACTION_INSTALL_COMMIT =
+            "com.android.cts.deviceowner.INTENT_PACKAGE_INSTALL_COMMIT";
+    private static final int PACKAGE_INSTALLER_STATUS_UNDEFINED = -1000;
+
+    private PackageManager mPackageManager;
+    private PackageInstaller mPackageInstaller;
+    private PackageInstaller.Session mSession;
+    private BlockingBroadcastReceiver mBroadcastReceiver;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPackageManager = mContext.getPackageManager();
+        mPackageInstaller = mPackageManager.getPackageInstaller();
+        assertNotNull(mPackageInstaller);
+
+        mBroadcastReceiver = new BlockingBroadcastReceiver(mContext, ACTION_INSTALL_COMMIT);
+        mBroadcastReceiver.register();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mBroadcastReceiver.unregisterQuietly();
+        if (mSession != null) {
+            mSession.abandon();
+        }
+
+        super.tearDown();
+    }
+
+    public void testPackageInstall() throws Exception {
+        assertFalse(isPackageInstalled(TEST_APP_PKG));
+
+        // Install the package.
+        installPackage(TEST_APP_LOCATION);
+
+        Intent intent = mBroadcastReceiver.awaitForBroadcast();
+        assertNotNull(intent);
+        assertEquals(PackageInstaller.STATUS_SUCCESS,
+                intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                        PACKAGE_INSTALLER_STATUS_UNDEFINED));
+        assertEquals(TEST_APP_PKG, intent.getStringExtra(
+                PackageInstaller.EXTRA_PACKAGE_NAME));
+        assertTrue(isPackageInstalled(TEST_APP_PKG));
+    }
+
+    public void testPackageUninstall() throws Exception {
+        assertTrue(isPackageInstalled(TEST_APP_PKG));
+
+        // Uninstall the package.
+        mPackageInstaller.uninstall(TEST_APP_PKG, getCommitCallback());
+
+        Intent intent = mBroadcastReceiver.awaitForBroadcast();
+        assertNotNull(intent);
+        assertEquals(PackageInstaller.STATUS_SUCCESS,
+                intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                        PACKAGE_INSTALLER_STATUS_UNDEFINED));
+        assertEquals(TEST_APP_PKG, intent.getStringExtra(
+                PackageInstaller.EXTRA_PACKAGE_NAME));
+        assertFalse(isPackageInstalled(TEST_APP_PKG));
+    }
+
+    public void testKeepPackageCache() throws Exception {
+        // Set keep package cache.
+        mDevicePolicyManager.setKeepUninstalledPackages(getWho(),
+                Collections.singletonList(TEST_APP_PKG));
+    }
+
+    public void testInstallExistingPackage() throws Exception {
+        assertFalse(isPackageInstalled(TEST_APP_PKG));
+
+        // Install the existing package.
+        assertTrue(mDevicePolicyManager.installExistingPackage(getWho(), TEST_APP_PKG));
+        assertTrue(isPackageInstalled(TEST_APP_PKG));
+    }
+
+    private void installPackage(String packageLocation) throws Exception {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName(TEST_APP_PKG);
+        int sessionId = mPackageInstaller.createSession(params);
+        mSession = mPackageInstaller.openSession(sessionId);
+
+        File file = new File(packageLocation);
+        InputStream in = new FileInputStream(file);
+        OutputStream out = mSession.openWrite("PackageInstallTest", 0, file.length());
+        byte[] buffer = new byte[65536];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            out.write(buffer, 0, c);
+        }
+        mSession.fsync(out);
+        out.close();
+        mSession.commit(getCommitCallback());
+        mSession.close();
+    }
+
+    private IntentSender getCommitCallback() {
+        // Create a PendingIntent and use it to generate the IntentSender
+        Intent broadcastIntent = new Intent(ACTION_INSTALL_COMMIT);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext,
+                REQUEST_CODE,
+                broadcastIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        return pendingIntent.getIntentSender();
+    }
+
+    private boolean isPackageInstalled(String packageName) {
+        try {
+            return mPackageManager.getPackageInfo(packageName, 0) != null;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}
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 201f382..054a5a5 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
@@ -15,15 +15,187 @@
  */
 package com.android.cts.deviceowner;
 
-import android.app.admin.SecurityLog.SecurityEvent;
-import android.os.Parcel;
-import android.os.SystemClock;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.SecurityLog.LEVEL_ERROR;
+import static android.app.admin.SecurityLog.LEVEL_INFO;
+import static android.app.admin.SecurityLog.LEVEL_WARNING;
+import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+import static android.app.admin.SecurityLog.TAG_ADB_SHELL_INTERACTIVE;
+import static android.app.admin.SecurityLog.TAG_APP_PROCESS_START;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
+import static android.app.admin.SecurityLog.TAG_CERT_VALIDATION_FAILURE;
+import static android.app.admin.SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED;
+import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET;
+import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISMISSED;
+import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT;
+import static android.app.admin.SecurityLog.TAG_KEYGUARD_SECURED;
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+import static android.app.admin.SecurityLog.TAG_KEY_IMPORT;
+import static android.app.admin.SecurityLog.TAG_KEY_INTEGRITY_VIOLATION;
+import static android.app.admin.SecurityLog.TAG_LOGGING_STARTED;
+import static android.app.admin.SecurityLog.TAG_LOGGING_STOPPED;
+import static android.app.admin.SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL;
+import static android.app.admin.SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET;
+import static android.app.admin.SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET;
+import static android.app.admin.SecurityLog.TAG_MEDIA_MOUNT;
+import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT;
+import static android.app.admin.SecurityLog.TAG_OS_SHUTDOWN;
+import static android.app.admin.SecurityLog.TAG_OS_STARTUP;
+import static android.app.admin.SecurityLog.TAG_PASSWORD_COMPLEXITY_SET;
+import static android.app.admin.SecurityLog.TAG_PASSWORD_EXPIRATION_SET;
+import static android.app.admin.SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET;
+import static android.app.admin.SecurityLog.TAG_REMOTE_LOCK;
+import static android.app.admin.SecurityLog.TAG_SYNC_RECV_FILE;
+import static android.app.admin.SecurityLog.TAG_SYNC_SEND_FILE;
+import static android.app.admin.SecurityLog.TAG_USER_RESTRICTION_ADDED;
+import static android.app.admin.SecurityLog.TAG_USER_RESTRICTION_REMOVED;
+import static android.app.admin.SecurityLog.TAG_WIPE_FAILURE;
 
+import static com.google.common.collect.ImmutableList.of;
+
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.UserManager;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.crypto.spec.SecretKeySpec;
 
 public class SecurityLoggingTest extends BaseDeviceOwnerTest {
+    private static final String ARG_BATCH_NUMBER = "batchNumber";
+    private static final String PREF_KEY_PREFIX = "batch-last-id-";
+    private static final String PREF_NAME = "batchIds";
+
+    // For brevity.
+    private static final Class<String> S = String.class;
+    private static final Class<Long> L = Long.class;
+    private static final Class<Integer> I = Integer.class;
+
+    private static final Map<Integer, List<Class>> PAYLOAD_TYPES_MAP =
+            new ImmutableMap.Builder<Integer, List<Class>>()
+                    .put(TAG_ADB_SHELL_INTERACTIVE, of())
+                    .put(TAG_ADB_SHELL_CMD, of(S))
+                    .put(TAG_SYNC_RECV_FILE, of(S))
+                    .put(TAG_SYNC_SEND_FILE, of(S))
+                    .put(TAG_APP_PROCESS_START, of(S, L, I, I, S, S))
+                    .put(TAG_KEYGUARD_DISMISSED, of())
+                    .put(TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, of(I, I))
+                    .put(TAG_KEYGUARD_SECURED, of())
+                    .put(TAG_OS_STARTUP, of(S, S))
+                    .put(TAG_OS_SHUTDOWN, of())
+                    .put(TAG_LOGGING_STARTED, of())
+                    .put(TAG_LOGGING_STOPPED, of())
+                    .put(TAG_MEDIA_MOUNT, of(S, S))
+                    .put(TAG_MEDIA_UNMOUNT, of(S, S))
+                    .put(TAG_LOG_BUFFER_SIZE_CRITICAL, of())
+                    .put(TAG_PASSWORD_EXPIRATION_SET, of(S, I, I, L))
+                    .put(TAG_PASSWORD_COMPLEXITY_SET, of(S, I, I, I, I, I, I, I, I, I, I))
+                    .put(TAG_PASSWORD_HISTORY_LENGTH_SET, of(S, I, I, I))
+                    .put(TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, of(S, I, I, L))
+                    .put(TAG_MAX_PASSWORD_ATTEMPTS_SET, of(S, I, I, I))
+                    .put(TAG_KEYGUARD_DISABLED_FEATURES_SET, of(S, I, I, I))
+                    .put(TAG_REMOTE_LOCK, of(S, I, I))
+                    .put(TAG_WIPE_FAILURE, of())
+                    .put(TAG_KEY_GENERATED, of(I, S, I))
+                    .put(TAG_KEY_IMPORT, of(I, S, I))
+                    .put(TAG_KEY_DESTRUCTION, of(I, S, I))
+                    .put(TAG_CERT_AUTHORITY_INSTALLED, of(I, S))
+                    .put(TAG_CERT_AUTHORITY_REMOVED, of(I, S))
+                    .put(TAG_USER_RESTRICTION_ADDED, of(S, I, S))
+                    .put(TAG_USER_RESTRICTION_REMOVED, of(S, I, S))
+                    .put(TAG_CRYPTO_SELF_TEST_COMPLETED, of(I))
+                    .put(TAG_KEY_INTEGRITY_VIOLATION, of(S, I))
+                    .put(TAG_CERT_VALIDATION_FAILURE, of(S))
+                    .build();
+
+    private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
+    private static final String IMPORTED_KEY_ALIAS = "imported_key_alias";
+
+    /*
+     * The CA cert below is the content of cacert.pem as generated by:
+     *
+     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out 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-----";
+
+    private static final String TEST_CA_SUBJECT = "o=internet widgits pty ltd,st=some-state,c=au";
+
+    // Indices of various fields in event payload.
+    private static final int SUCCESS_INDEX = 0;
+    private static final int ALIAS_INDEX = 1;
+    private static final int UID_INDEX = 2;
+    private static final int SUBJECT_INDEX = 1;
+    private static final int ADMIN_PKG_INDEX = 0;
+    private static final int ADMIN_USER_INDEX = 1;
+    private static final int TARGET_USER_INDEX = 2;
+    private static final int PWD_LEN_INDEX = 3;
+    private static final int PWD_QUALITY_INDEX = 4;
+    private static final int LETTERS_INDEX = 5;
+    private static final int NON_LETTERS_INDEX = 6;
+    private static final int NUMERIC_INDEX = 7;
+    private static final int UPPERCASE_INDEX = 8;
+    private static final int LOWERCASE_INDEX = 9;
+    private static final int SYMBOLS_INDEX = 10;
+    private static final int PWD_EXPIRATION_INDEX = 3;
+    private static final int PWD_HIST_LEN_INDEX = 3;
+    private static final int USER_RESTRICTION_INDEX = 2;
+    private static final int MAX_PWD_ATTEMPTS_INDEX = 3;
+    private static final int KEYGUARD_FEATURES_INDEX = 3;
+    private static final int MAX_SCREEN_TIMEOUT_INDEX = 3;
+
+    // Value that indicates success in events that have corresponding field in their payload.
+    private static final int SUCCESS_VALUE = 1;
+
+    private static final int TEST_PWD_LENGTH = 10;
+    // Min number of various character types to use.
+    private static final int TEST_PWD_CHARS = 2;
+
+    private static final long TEST_PWD_EXPIRATION_TIMEOUT = TimeUnit.DAYS.toMillis(356);
+    private static final int TEST_PWD_HISTORY_LENGTH = 3;
+    private static final int TEST_PWD_MAX_ATTEMPTS = 5;
+    private static final long TEST_MAX_TIME_TO_LOCK = TimeUnit.HOURS.toMillis(1);
 
     /**
      * Test: retrieving security logs can only be done if there's one user on the device or all
@@ -50,19 +222,127 @@
     }
 
     /**
-     * Test: retrieving security logs. This test has should be called when security logging is
-     * enabled and directly after reboot with device owner installed so that security logging
-     * actually takes place and fetching the logs isn't subject to rate limiting.
+     * Test: retrieves security logs and verifies that all events generated as a result of host
+     * side actions and by {@link #testGenerateLogs()} are there.
      */
-    public void testGetSecurityLogs() {
-        List<SecurityEvent> events = mDevicePolicyManager.retrieveSecurityLogs(getWho());
+    public void testVerifyGeneratedLogs() throws Exception {
+        final List<SecurityEvent> events = getEvents();
+        verifyAutomaticEventsPresent(events);
+        verifyKeystoreEventsPresent(events);
+        verifyKeyChainEventsPresent(events);
+        verifyAdminEventsPresent(events);
+    }
 
-        // There must be at least some events, e.g. PackageManager logs all process launches.
+    private void verifyAutomaticEventsPresent(List<SecurityEvent> events) {
+        verifyOsStartupEventPresent(events);
+        verifyLoggingStartedEventPresent(events);
+        verifyCryptoSelfTestEventPresent(events);
+    }
+
+    private void verifyKeyChainEventsPresent(List<SecurityEvent> events) {
+        verifyCertInstalledEventPresent(events);
+        verifyCertUninstalledEventPresent(events);
+    }
+
+    private void verifyKeystoreEventsPresent(List<SecurityEvent> events) {
+        verifyKeyGeneratedEventPresent(events, GENERATED_KEY_ALIAS);
+        verifyKeyDeletedEventPresent(events, GENERATED_KEY_ALIAS);
+        verifyKeyImportedEventPresent(events, IMPORTED_KEY_ALIAS);
+        verifyKeyDeletedEventPresent(events, IMPORTED_KEY_ALIAS);
+    }
+
+    private void verifyAdminEventsPresent(List<SecurityEvent> events) {
+        verifyPasswordComplexityEventsPresent(events);
+        verifyUserRestrictionEventsPresent(events);
+        verifyLockingPolicyEventsPresent(events);
+    }
+
+    /**
+     * Generates events for positive test cases.
+     */
+    public void testGenerateLogs() throws Exception {
+        generateKeystoreEvents();
+        generateKeyChainEvents();
+        generateAdminEvents();
+    }
+
+    private void generateKeyChainEvents() {
+        installCaCert();
+        uninstallCaCert();
+    }
+
+    private void generateKeystoreEvents() throws Exception {
+        generateKey(GENERATED_KEY_ALIAS);
+        deleteKey(GENERATED_KEY_ALIAS);
+        importKey(IMPORTED_KEY_ALIAS);
+        deleteKey(IMPORTED_KEY_ALIAS);
+    }
+
+    private void generateAdminEvents() {
+        generatePasswordComplexityEvents();
+        generateUserRestrictionEvents();
+        generateLockingPolicyEvents();
+    }
+
+    /**
+     * Fetches and sanity-checks the events.
+     */
+    private List<SecurityEvent> getEvents() throws Exception {
+        List<SecurityEvent> events = null;
+        // Retry once after seeping for 1 second, in case "dpm force-security-logs" hasn't taken
+        // effect just yet.
+        for (int i = 0; i < 2 && events == null; i++) {
+            events = mDevicePolicyManager.retrieveSecurityLogs(getWho());
+            if (events == null) Thread.sleep(1000);
+        }
+
+        verifySecurityLogs(events);
+
+        return events;
+    }
+
+    /**
+     * Test: check that there are no gaps between ids in two consecutive batches. Shared preference
+     * is used to store these numbers between test invocations.
+     */
+    public void testVerifyLogIds() throws Exception {
+        final String param = InstrumentationRegistry.getArguments().getString(ARG_BATCH_NUMBER);
+        final int batchId = param == null ? 0 : Integer.parseInt(param);
+        final List<SecurityEvent> events = getEvents();
+        final SharedPreferences prefs =
+                mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+
+        final long firstId = events.get(0).getId();
+        if (batchId == 0) {
+            assertEquals("Event id wasn't reset.", 0L, firstId);
+        } else {
+            final String prevBatchLastIdKey = PREF_KEY_PREFIX + (batchId - 1);
+            assertTrue("Last event id from previous batch not found in shared prefs",
+                    prefs.contains(prevBatchLastIdKey));
+            final long prevBatchLastId = prefs.getLong(prevBatchLastIdKey, 0);
+            assertEquals("Event ids aren't consecutive between batches",
+                    firstId, prevBatchLastId + 1);
+        }
+
+        final String currBatchLastIdKey = PREF_KEY_PREFIX + batchId;
+        final long lastId = events.get(events.size() - 1).getId();
+        prefs.edit().putLong(currBatchLastIdKey, lastId).commit();
+    }
+
+    private void verifySecurityLogs(List<SecurityEvent> events) {
         assertTrue("Unable to get events", events != null && events.size() > 0);
 
         // We don't know much about the events, so just call public API methods.
         for (int i = 0; i < events.size(); i++) {
-            SecurityEvent event = events.get(i);
+            final SecurityEvent event = events.get(i);
+
+            verifyPayloadTypes(event);
+
+            // Test id for monotonically increasing.
+            if (i > 0) {
+                assertEquals("Event IDs are not monotonically increasing within the batch",
+                        events.get(i - 1).getId() + 1, event.getId());
+            }
 
             // Test parcelling: flatten to a parcel.
             Parcel p = Parcel.obtain();
@@ -70,9 +350,12 @@
             p.setDataPosition(0);
 
             // Restore from parcel and check contents.
-            SecurityEvent restored = SecurityEvent.CREATOR.createFromParcel(p);
+            final SecurityEvent restored = SecurityEvent.CREATOR.createFromParcel(p);
             p.recycle();
 
+            final int level = event.getLogLevel();
+            assertTrue(level == LEVEL_INFO || level == LEVEL_WARNING || level == LEVEL_ERROR);
+
             // For some events data is encapsulated into Object array.
             if (event.getData() instanceof Object[]) {
                 assertTrue("Parcelling changed the array returned by getData",
@@ -81,6 +364,8 @@
                 assertEquals("Parcelling changed the result of getData",
                         event.getData(), restored.getData());
             }
+            assertEquals("Parcelling changed the result of getId",
+                    event.getId(), restored.getId());
             assertEquals("Parcelling changed the result of getTag",
                     event.getTag(), restored.getTag());
             assertEquals("Parcelling changed the result of getTimeNanos",
@@ -90,6 +375,79 @@
         }
     }
 
+    private void verifyPayloadTypes(SecurityEvent event) {
+        final List<Class> payloadTypes = PAYLOAD_TYPES_MAP.get(event.getTag());
+        assertNotNull("event type unknown: " + event.getTag(), payloadTypes);
+
+        if (payloadTypes.size() == 0) {
+            // No payload.
+            assertNull("non-null payload", event.getData());
+        } else if (payloadTypes.size() == 1) {
+            // Singleton payload.
+            assertTrue(payloadTypes.get(0).isInstance(event.getData()));
+        } else {
+            // Payload is incapsulated into Object[]
+            assertTrue(event.getData() instanceof Object[]);
+            final Object[] dataArray = (Object[]) event.getData();
+            assertEquals(payloadTypes.size(), dataArray.length);
+            for (int i = 0; i < payloadTypes.size(); i++) {
+                assertTrue(payloadTypes.get(i).isInstance(dataArray[i]));
+            }
+        }
+    }
+
+    private void verifyOsStartupEventPresent(List<SecurityEvent> events) {
+        final SecurityEvent event = findEvent("os startup", events, TAG_OS_STARTUP);
+        // Verified boot state
+        assertTrue(ImmutableSet.of("green", "yellow", "orange").contains(getString(event, 0)));
+        // dm-verity mode
+        assertTrue(ImmutableSet.of("enforcing", "eio").contains(getString(event, 1)));
+    }
+
+    private void verifyCryptoSelfTestEventPresent(List<SecurityEvent> events) {
+        final SecurityEvent event = findEvent("crypto self test complete",
+                events, TAG_CRYPTO_SELF_TEST_COMPLETED);
+        // Success code.
+        assertEquals(1, getInt(event));
+    }
+
+    private void verifyLoggingStartedEventPresent(List<SecurityEvent> events) {
+        findEvent("logging started", events, TAG_LOGGING_STARTED);
+    }
+
+    private SecurityEvent findEvent(String description, List<SecurityEvent> events, int tag) {
+        return findEvent(description, events, e -> e.getTag() == tag);
+    }
+
+    private SecurityEvent findEvent(String description, List<SecurityEvent> events,
+            Predicate<SecurityEvent> predicate) {
+        final List<SecurityEvent> matches =
+                events.stream().filter(predicate).collect(Collectors.toList());
+        assertEquals("Invalid number of matching events: " + description, 1, matches.size());
+        return matches.get(0);
+    }
+
+    private static Object getDatum(SecurityEvent event, int index) {
+        final Object[] dataArray = (Object[]) event.getData();
+        return dataArray[index];
+    }
+
+    private static String getString(SecurityEvent event, int index) {
+        return (String) getDatum(event, index);
+    }
+
+    private static int getInt(SecurityEvent event) {
+        return (Integer) event.getData();
+    }
+
+    private static int getInt(SecurityEvent event, int index) {
+        return (Integer) getDatum(event, index);
+    }
+
+    private static long getLong(SecurityEvent event, int index) {
+        return (Long) getDatum(event, index);
+    }
+
     /**
      * Test: Test enabling security logging. This test should be executed after installing a device
      * owner so that we check that logging is not enabled by default. This test has a side effect:
@@ -108,18 +466,215 @@
     public void testDisablingSecurityLogging() {
         mDevicePolicyManager.setSecurityLoggingEnabled(getWho(), false);
         assertFalse(mDevicePolicyManager.isSecurityLoggingEnabled(getWho()));
+
+        // Verify that logs are actually not available.
+        assertNull(mDevicePolicyManager.retrieveSecurityLogs(getWho()));
     }
 
     /**
      * Test: retrieving security logs should be rate limited - subsequent attempts should return
      * null.
      */
-    public void testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval() {
-        List<SecurityEvent> logs = mDevicePolicyManager.retrieveSecurityLogs(getWho());
+    public void testSecurityLoggingRetrievalRateLimited() {
+        final List<SecurityEvent> logs = mDevicePolicyManager.retrieveSecurityLogs(getWho());
         // if logs is null it means that that attempt was rate limited => test PASS
         if (logs != null) {
             assertNull(mDevicePolicyManager.retrieveSecurityLogs(getWho()));
             assertNull(mDevicePolicyManager.retrieveSecurityLogs(getWho()));
         }
     }
+
+    private void generateKey(String keyAlias) throws Exception {
+        final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+        generator.initialize(
+                new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_SIGN).build());
+        final KeyPair keyPair = generator.generateKeyPair();
+        assertNotNull(keyPair);
+    }
+
+    private void verifyKeyGeneratedEventPresent(List<SecurityEvent> events, String alias) {
+        findEvent("key generated", events,
+                e -> e.getTag() == TAG_KEY_GENERATED
+                        && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE
+                        && getString(e, ALIAS_INDEX).contains(alias)
+                        && getInt(e, UID_INDEX) == Process.myUid());
+    }
+
+    private void importKey(String alias) throws Exception{
+        final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+        ks.load(null);
+        ks.setEntry(alias, new KeyStore.SecretKeyEntry(new SecretKeySpec(new byte[32], "AES")),
+                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).build());
+    }
+
+    private void verifyKeyImportedEventPresent(List<SecurityEvent> events, String alias) {
+        findEvent("key imported", events,
+                e -> e.getTag() == TAG_KEY_IMPORT
+                        && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE
+                        && getString(e, ALIAS_INDEX).contains(alias)
+                        && getInt(e, UID_INDEX) == Process.myUid());
+    }
+
+    private void deleteKey(String keyAlias) throws Exception {
+        final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+        ks.load(null);
+        ks.deleteEntry(keyAlias);
+    }
+
+    private void verifyKeyDeletedEventPresent(List<SecurityEvent> events, String alias) {
+        findEvent("key deleted", events,
+                e -> e.getTag() == TAG_KEY_DESTRUCTION
+                        && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE
+                        && getString(e, ALIAS_INDEX).contains(alias)
+                        && getInt(e, UID_INDEX) == Process.myUid());
+    }
+
+    private void installCaCert() {
+        mDevicePolicyManager.installCaCert(getWho(), TEST_CA.getBytes());
+    }
+
+    private void verifyCertInstalledEventPresent(List<SecurityEvent> events) {
+        findEvent("cert authority installed", events,
+                e -> e.getTag() == TAG_CERT_AUTHORITY_INSTALLED
+                        && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE
+                        && getString(e, SUBJECT_INDEX).equals(TEST_CA_SUBJECT));
+    }
+
+    private void uninstallCaCert() {
+        mDevicePolicyManager.uninstallCaCert(getWho(), TEST_CA.getBytes());
+    }
+
+    private void verifyCertUninstalledEventPresent(List<SecurityEvent> events) {
+        findEvent("cert authority removed", events,
+                e -> e.getTag() == TAG_CERT_AUTHORITY_REMOVED
+                        && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE
+                        && getString(e, SUBJECT_INDEX).equals(TEST_CA_SUBJECT));
+    }
+
+    private void generatePasswordComplexityEvents() {
+        mDevicePolicyManager.setPasswordQuality(getWho(), PASSWORD_QUALITY_COMPLEX);
+        mDevicePolicyManager.setPasswordMinimumLength(getWho(), TEST_PWD_LENGTH);
+        mDevicePolicyManager.setPasswordMinimumLetters(getWho(), TEST_PWD_CHARS);
+        mDevicePolicyManager.setPasswordMinimumNonLetter(getWho(), TEST_PWD_CHARS);
+        mDevicePolicyManager.setPasswordMinimumUpperCase(getWho(), TEST_PWD_CHARS);
+        mDevicePolicyManager.setPasswordMinimumLowerCase(getWho(), TEST_PWD_CHARS);
+        mDevicePolicyManager.setPasswordMinimumNumeric(getWho(), TEST_PWD_CHARS);
+        mDevicePolicyManager.setPasswordMinimumSymbols(getWho(), TEST_PWD_CHARS);
+    }
+
+    private void verifyPasswordComplexityEventsPresent(List<SecurityEvent> events) {
+        final int userId = Process.myUserHandle().getIdentifier();
+        // This reflects default values for password complexity event payload fields.
+        final Object[] expectedPayload = new Object[] {
+                getWho().getPackageName(), // admin package
+                userId,                    // admin user
+                userId,                    // target user
+                0,                         // default password length
+                0,                         // default password quality
+                1,                         // default min letters
+                0,                         // default min non-letters
+                1,                         // default min numeric
+                0,                         // default min uppercase
+                0,                         // default min lowercase
+                1,                         // default min symbols
+        };
+
+        // The order should be consistent with the order in generatePasswordComplexityEvents(), so
+        // that the expected values change in the same sequence as when setting password policies.
+        expectedPayload[PWD_QUALITY_INDEX] = PASSWORD_QUALITY_COMPLEX;
+        findPasswordComplexityEvent("set pwd quality", events, expectedPayload);
+        expectedPayload[PWD_LEN_INDEX] = TEST_PWD_LENGTH;
+        findPasswordComplexityEvent("set pwd length", events, expectedPayload);
+        expectedPayload[LETTERS_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min letters", events, expectedPayload);
+        expectedPayload[NON_LETTERS_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min non-letters", events, expectedPayload);
+        expectedPayload[UPPERCASE_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min uppercase", events, expectedPayload);
+        expectedPayload[LOWERCASE_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min lowercase", events, expectedPayload);
+        expectedPayload[NUMERIC_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min numeric", events, expectedPayload);
+        expectedPayload[SYMBOLS_INDEX] = TEST_PWD_CHARS;
+        findPasswordComplexityEvent("set pwd min symbols", events, expectedPayload);
+    }
+
+    private void generateLockingPolicyEvents() {
+        mDevicePolicyManager.setPasswordExpirationTimeout(getWho(), TEST_PWD_EXPIRATION_TIMEOUT);
+        mDevicePolicyManager.setPasswordHistoryLength(getWho(), TEST_PWD_HISTORY_LENGTH);
+        mDevicePolicyManager.setMaximumFailedPasswordsForWipe(getWho(), TEST_PWD_MAX_ATTEMPTS);
+        mDevicePolicyManager.setKeyguardDisabledFeatures(getWho(), KEYGUARD_DISABLE_FINGERPRINT);
+        mDevicePolicyManager.setMaximumTimeToLock(getWho(), TEST_MAX_TIME_TO_LOCK);
+        mDevicePolicyManager.lockNow();
+    }
+
+    private void verifyLockingPolicyEventsPresent(List<SecurityEvent> events) {
+        final int userId = Process.myUserHandle().getIdentifier();
+
+        findEvent("set password expiration", events,
+                e -> e.getTag() == TAG_PASSWORD_EXPIRATION_SET &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        getInt(e, TARGET_USER_INDEX) == userId &&
+                        getLong(e, PWD_EXPIRATION_INDEX) == TEST_PWD_EXPIRATION_TIMEOUT);
+
+        findEvent("set password history length", events,
+                e -> e.getTag() == TAG_PASSWORD_HISTORY_LENGTH_SET &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        getInt(e, TARGET_USER_INDEX) == userId &&
+                        getInt(e, PWD_HIST_LEN_INDEX) == TEST_PWD_HISTORY_LENGTH);
+
+        findEvent("set password attempts", events,
+                e -> e.getTag() == TAG_MAX_PASSWORD_ATTEMPTS_SET &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        getInt(e, TARGET_USER_INDEX) == userId &&
+                        getInt(e, MAX_PWD_ATTEMPTS_INDEX) == TEST_PWD_MAX_ATTEMPTS);
+
+        findEvent("set keyguard disabled features", events,
+                e -> e.getTag() == TAG_KEYGUARD_DISABLED_FEATURES_SET &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        getInt(e, TARGET_USER_INDEX) == userId &&
+                        getInt(e, KEYGUARD_FEATURES_INDEX) == KEYGUARD_DISABLE_FINGERPRINT);
+
+        findEvent("set screen lock timeout", events,
+                e -> e.getTag() == TAG_MAX_SCREEN_LOCK_TIMEOUT_SET &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        getInt(e, TARGET_USER_INDEX) == userId &&
+                        getLong(e, MAX_SCREEN_TIMEOUT_INDEX) == TEST_MAX_TIME_TO_LOCK);
+
+        findEvent("set screen lock timeout", events,
+                e -> e.getTag() == TAG_REMOTE_LOCK &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId);
+    }
+
+    private void findPasswordComplexityEvent(
+            String description, List<SecurityEvent> events, Object[] expectedPayload) {
+        findEvent(description, events,
+                e -> e.getTag() == TAG_PASSWORD_COMPLEXITY_SET &&
+                        Arrays.equals((Object[]) e.getData(), expectedPayload));
+    }
+
+    private void generateUserRestrictionEvents() {
+        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_FUN);
+        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_FUN);
+    }
+
+    private void verifyUserRestrictionEventsPresent(List<SecurityEvent> events) {
+        findUserRestrictionEvent("set user restriction", events, TAG_USER_RESTRICTION_ADDED);
+        findUserRestrictionEvent("clear user restriction", events, TAG_USER_RESTRICTION_REMOVED);
+    }
+
+    private void findUserRestrictionEvent(String description, List<SecurityEvent> events, int tag) {
+        final int userId = Process.myUserHandle().getIdentifier();
+        findEvent(description, events,
+                e -> e.getTag() == tag &&
+                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                        getInt(e, ADMIN_USER_INDEX) == userId &&
+                        UserManager.DISALLOW_FUN.equals(getString(e, USER_RESTRICTION_INDEX)));
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
index 028bf2e..514b9e8 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
@@ -63,7 +63,7 @@
                 getSystemService(Context.DEVICE_POLICY_SERVICE);
         String command = intent.getStringExtra(EXTRA_COMMAND);
         Log.i(TAG, "Command: \"" + command);
-        ComponentName admin = BaseDeviceOwnerTest.getWho();
+        ComponentName admin = BasicAdminReceiver.getComponentName(this);
         if (ADD_RESTRICTION_COMMAND.equals(command)) {
             String restrictionKey = intent.getStringExtra(EXTRA_RESTRICTION_KEY);
             dpm.addUserRestriction(admin, restrictionKey);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetSystemSettingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetSystemSettingTest.java
new file mode 100644
index 0000000..06e6149
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetSystemSettingTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.provider.Settings;
+
+/**
+ * Test {@link DevicePolicyManager#setSystemSetting}.
+ */
+public class SetSystemSettingTest extends BaseDeviceOwnerTest {
+
+    private static final String TEST_BRIGHTNESS_1 = "200";
+    private static final String TEST_BRIGHTNESS_2 = "100";
+
+    private void testSetBrightnessWithValue(String testBrightness) {
+        mDevicePolicyManager.setSystemSetting(getWho(),
+                Settings.System.SCREEN_BRIGHTNESS, testBrightness);
+        assertEquals(testBrightness, Settings.System.getString(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS));
+    }
+
+    public void testSetBrightness() {
+        testSetBrightnessWithValue(TEST_BRIGHTNESS_1);
+        testSetBrightnessWithValue(TEST_BRIGHTNESS_2);
+    }
+
+    public void testSetSystemSettingsFailsForNonWhitelistedSettings() throws Exception {
+        try {
+            mDevicePolicyManager.setSystemSetting(getWho(),
+                    Settings.System.TEXT_AUTO_REPLACE, "0");
+            fail("Didn't throw security exception.");
+        } catch (SecurityException e) {
+            // Should throw SecurityException.
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetTimeTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetTimeTest.java
new file mode 100644
index 0000000..3ccbd9e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetTimeTest.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 com.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+
+import java.util.Calendar;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link DevicePolicyManager#setTime} and @link {DevicePolicyManager#setTimeZone}
+ */
+public class SetTimeTest extends BaseDeviceOwnerTest {
+
+    private final long TEST_TIME_1 = 10000000;
+    private final long TEST_TIME_2 = 100000000;
+    private final String TEST_TIME_ZONE_1 = "America/New_York";
+    private final String TEST_TIME_ZONE_2 = "America/Los_Angeles";
+    private final long TIMEOUT_SEC = 10;
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME, "1");
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME_ZONE, "1");
+        super.tearDown();
+    }
+
+    private void testSetTimeWithValue(long testTime) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                latch.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
+
+        try {
+            assertTrue(mDevicePolicyManager.setTime(getWho(), testTime));
+            assertTrue(latch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+            assertTrue(System.currentTimeMillis() <= testTime + (TIMEOUT_SEC + 1) * 1000);
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    public void testSetTime() throws Exception {
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME, "0");
+        testSetTimeWithValue(TEST_TIME_1);
+        testSetTimeWithValue(TEST_TIME_2);
+    }
+
+    public void testSetTimeFailWithAutoTimeOn() {
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME, "1");
+        assertFalse(mDevicePolicyManager.setTime(getWho(), TEST_TIME_1));
+    }
+
+    private void testSetTimeZoneWithValue(String testTimeZone) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                latch.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED));
+
+        try {
+            assertTrue(mDevicePolicyManager.setTimeZone(getWho(), testTimeZone));
+            assertTrue(latch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+            assertEquals(testTimeZone, Calendar.getInstance().getTimeZone().getID());
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    public void testSetTimeZone() throws Exception {
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME_ZONE, "0");
+        testSetTimeZoneWithValue(TEST_TIME_ZONE_1);
+        testSetTimeZoneWithValue(TEST_TIME_ZONE_2);
+    }
+
+    public void testSetTimeZoneFailWithAutoTimezoneOn() {
+        mDevicePolicyManager.setGlobalSetting(getWho(), Settings.Global.AUTO_TIME_ZONE, "1");
+        assertFalse(mDevicePolicyManager.setTimeZone(getWho(), TEST_TIME_ZONE_1));
+    }
+}
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 58e7ef6..313505e 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
@@ -15,16 +15,22 @@
  */
 package com.android.cts.deviceowner;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.SystemUpdatePolicy.ValidationFailedException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Build;
+import android.icu.util.Calendar;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.Pair;
 
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -37,67 +43,70 @@
     private static final int TIMEOUT_MS = 20_000;
 
     private final Semaphore mPolicyChangedSemaphore = new Semaphore(0);
+    private final Semaphore mTimeChangedSemaphore = new Semaphore(0);
     private final BroadcastReceiver policyChangedReceiver = new BroadcastReceiver() {
         @Override
-        public void onReceive(Context arg0, Intent arg1) {
-            mPolicyChangedSemaphore.release();
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED.equals(action)) {
+                mPolicyChangedSemaphore.release();
+            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
+                mTimeChangedSemaphore.release();
+            }
         }
     };
-    private boolean mHasFeature;
+
+    private int mSavedAutoTimeConfig;
+    private LocalDate mSavedSystemDate;
+    private boolean mRestoreDate;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mHasFeature = ApiLevelUtil.isAfter(Build.VERSION_CODES.LOLLIPOP_MR1);
-
-        if (mHasFeature) {
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED);
-            mContext.registerReceiver(policyChangedReceiver, filter);
-        }
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        mContext.registerReceiver(policyChangedReceiver, filter);
+        clearFreezeRecord();
+        mSavedAutoTimeConfig = Settings.Global.getInt(mContext.getContentResolver(),
+                Global.AUTO_TIME, 0);
+        executeShellCommand("settings put global auto_time 0");
+        mSavedSystemDate = LocalDate.now();
+        mRestoreDate = false;
     }
 
     @Override
     protected void tearDown() throws Exception {
-        if (mHasFeature) {
-            mContext.unregisterReceiver(policyChangedReceiver);
-            mDevicePolicyManager.setSystemUpdatePolicy(getWho(), null);
+        mDevicePolicyManager.setSystemUpdatePolicy(getWho(), null);
+        clearFreezeRecord();
+        if (mRestoreDate) {
+            setSystemDate(mSavedSystemDate);
         }
+        executeShellCommand("settings put global auto_time",
+                Integer.toString(mSavedAutoTimeConfig));
+        // This needs to happen last since setSystemDate() relies on the receiver for
+        // synchronization.
+        mContext.unregisterReceiver(policyChangedReceiver);
         super.tearDown();
     }
 
     public void testSetEmptytInstallPolicy() {
-        if (!mHasFeature) {
-            return;
-        }
         testPolicy(null);
     }
 
     public void testSetAutomaticInstallPolicy() {
-        if (!mHasFeature) {
-            return;
-        }
         testPolicy(SystemUpdatePolicy.createAutomaticInstallPolicy());
     }
 
     public void testSetWindowedInstallPolicy() {
-        if (!mHasFeature) {
-            return;
-        }
         testPolicy(SystemUpdatePolicy.createWindowedInstallPolicy(0, 720));
     }
 
     public void testSetPostponeInstallPolicy() {
-        if (!mHasFeature) {
-            return;
-        }
         testPolicy(SystemUpdatePolicy.createPostponeInstallPolicy());
     }
 
     public void testShouldFailInvalidWindowPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             SystemUpdatePolicy.createWindowedInstallPolicy(24 * 60 + 1, 720);
             fail("Invalid window start should not be accepted.");
@@ -116,9 +125,117 @@
         } catch (IllegalArgumentException expected) { }
     }
 
+    public void testFreezePeriodValidation() {
+        // Dates are in MM-DD format
+        validateFreezePeriodsSucceeds("01-01", "01-02");
+        validateFreezePeriodsSucceeds("01-31", "01-31");
+        validateFreezePeriodsSucceeds("11-01", "01-15");
+        validateFreezePeriodsSucceeds("02-01", "02-29");
+        validateFreezePeriodsSucceeds("03-01", "03-31", "09-01", "09-30");
+        validateFreezePeriodsSucceeds("10-01", "10-31", "12-31", "01-31");
+        validateFreezePeriodsSucceeds("01-01", "02-28", "05-01", "06-30", "09-01", "10-31");
+        validateFreezePeriodsSucceeds("11-02", "01-15", "03-18", "04-30", "08-01", "08-30");
+
+        // full overlap
+        validateFreezePeriodsFailsOverlap("12-01", "01-31", "12-25", "01-15");
+        // partial overlap
+        validateFreezePeriodsFailsOverlap("03-01", "03-31", "03-15", "01-01");
+        // touching interval
+        validateFreezePeriodsFailsOverlap("01-31", "01-31", "02-01", "02-01");
+        validateFreezePeriodsFailsOverlap("12-01", "12-31", "04-01", "04-01", "01-01", "01-30");
+
+        // entire year
+        validateFreezePeriodsFailsTooLong("01-01", "12-31");
+        // Regular long period
+        validateFreezePeriodsSucceeds("01-01", "03-31", "06-01", "08-29");
+        validateFreezePeriodsFailsTooLong("01-01", "03-31", "06-01", "08-30");
+        // long period spanning across year end
+        validateFreezePeriodsSucceeds("11-01", "01-29");
+        validateFreezePeriodsFailsTooLong("11-01", "01-30");
+        // Leap year handling
+        validateFreezePeriodsSucceeds("12-01", "02-28");
+        validateFreezePeriodsFailsTooLong("12-01", "03-01");
+
+        // Regular short separation
+        validateFreezePeriodsFailsTooClose( "01-01", "01-01", "01-03", "01-03");
+        // Short interval spans across end of year
+        validateFreezePeriodsSucceeds("01-31", "03-01", "11-01", "12-01");
+        validateFreezePeriodsFailsTooClose("01-30", "03-01", "11-01", "12-01");
+        // Short separation is after wrapped period
+        validateFreezePeriodsSucceeds("03-03", "03-31", "12-31", "01-01");
+        validateFreezePeriodsFailsTooClose("03-02", "03-31", "12-31", "01-01");
+        // Short separation including Feb 29
+        validateFreezePeriodsSucceeds("12-01", "01-15", "03-17", "04-01");
+        validateFreezePeriodsFailsTooClose("12-01", "01-15", "03-16", "04-01");
+        // Short separation including Feb 29
+        validateFreezePeriodsSucceeds("01-01", "02-28", "04-30", "06-01");
+        validateFreezePeriodsSucceeds("01-01", "02-29", "04-30", "06-01");
+        validateFreezePeriodsFailsTooClose("01-01", "03-01", "04-30", "06-01");
+    }
+
+    public void testFreezePeriodCanBeSetAndChanged() throws Exception {
+        setPolicyWithFreezePeriod("11-02", "01-15", "03-18", "04-30");
+        // Set to a different period should work
+        setPolicyWithFreezePeriod("08-01", "08-30");
+        // Clear freeze period should work
+        setPolicyWithFreezePeriod();
+        // Set to the original period should work
+        setPolicyWithFreezePeriod("11-02", "01-15", "03-18", "04-30");
+    }
+
+    public void testFreezePeriodCannotSetIfTooCloseToPrevious() throws Exception {
+        setSystemDate(LocalDate.of(2018, 2, 28));
+        setPolicyWithFreezePeriod("01-01", "03-01", "06-01", "06-30");
+        // Clear policy
+        mDevicePolicyManager.setSystemUpdatePolicy(getWho(), null);
+        // Set to a conflict period (too close with previous period [2-28, 2-28]) should fail,
+        // despite the previous policy was cleared from the system just now.
+        try {
+            setPolicyWithFreezePeriod("04-29", "04-30");
+            fail("Did no flag invalid period");
+        } catch (ValidationFailedException e) {
+            assertEquals(e.getMessage(),
+                    ValidationFailedException.ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE,
+                    e.getErrorCode());
+        }
+        // This should succeed as the new freeze period is exactly 60 days away.
+        setPolicyWithFreezePeriod("04-30", "04-30");
+    }
+
+    public void testFreezePeriodCannotSetIfTooLongWhenCombinedWithPrevious() throws Exception {
+        setSystemDate(LocalDate.of(2012, 4, 1));
+        setPolicyWithFreezePeriod("03-01", "05-01");
+        setSystemDate(LocalDate.of(2012, 4, 30));
+        // Despite the wait for broadcast in setSystemDate(), TIME_CHANGED broadcast is asynchronous
+        // so give DevicePolicyManagerService more time to receive TIME_CHANGED and to update the
+        // freeze period record.
+        Thread.sleep(5000);
+        // Set to a conflict period (too long when combined with previous period [04-01, 04-30])
+        // should fail
+        try {
+            setPolicyWithFreezePeriod("04-30", "06-30");
+            fail("Did no flag invalid period");
+        } catch (SystemUpdatePolicy.ValidationFailedException e) {
+            assertEquals(e.getMessage(),
+                    ValidationFailedException.ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG,
+                    e.getErrorCode());
+        }
+        // This should succeed as the combined length (59 days) is just below threshold (90 days).
+        setPolicyWithFreezePeriod("05-01", "06-29");
+    }
+
+    public void testFreezePeriodForOneYear() throws Exception {
+        // Set a normal period every day for 365 days
+        for (int i = 1; i <= 365; i++) {
+            // Add two days so the test date range wraps around year-end
+            setSystemDate(LocalDate.ofYearDay(2019, i).plusDays(2));
+            testFreezePeriodCanBeSetAndChanged();
+        }
+    }
+
     private void testPolicy(SystemUpdatePolicy policy) {
         mDevicePolicyManager.setSystemUpdatePolicy(getWho(), policy);
-        waitForBroadcast();
+        waitForPolicyChangedBroadcast();
         SystemUpdatePolicy newPolicy = mDevicePolicyManager.getSystemUpdatePolicy();
         if (policy == null) {
             assertNull(newPolicy);
@@ -132,7 +249,82 @@
             }
         }
     }
-    private void waitForBroadcast() {
+
+    private void setPolicyWithFreezePeriod(String...dates) {
+        SystemUpdatePolicy policy = SystemUpdatePolicy.createPostponeInstallPolicy();
+        setFreezePeriods(policy, dates);
+        mDevicePolicyManager.setSystemUpdatePolicy(getWho(), policy);
+
+        List<Pair<Integer, Integer>> loadedFreezePeriods = mDevicePolicyManager
+                .getSystemUpdatePolicy().getFreezePeriods();
+        assertEquals(dates.length / 2, loadedFreezePeriods.size());
+        for (int i = 0; i < dates.length; i += 2) {
+            assertEquals(parseDate(dates[i]), (int) loadedFreezePeriods.get(i / 2).first);
+            assertEquals(parseDate(dates[i + 1]), (int) loadedFreezePeriods.get(i / 2).second);
+        }
+    }
+
+    private void validateFreezePeriodsSucceeds(String...dates)  {
+        SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy();
+        setFreezePeriods(p, dates);
+    }
+
+    private void validateFreezePeriodsFails(int errorCode, String... dates)  {
+        SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy();
+        try {
+            setFreezePeriods(p, dates);
+            fail("Exception not thrown for dates: " + String.join(" ", dates));
+        } catch (SystemUpdatePolicy.ValidationFailedException e) {
+            assertEquals("Exception not expected: " + e.getMessage(),
+                    errorCode,e.getErrorCode());
+        }
+    }
+
+    private void validateFreezePeriodsFailsOverlap(String... dates)  {
+        validateFreezePeriodsFails(ValidationFailedException.ERROR_DUPLICATE_OR_OVERLAP, dates);
+    }
+
+    private void validateFreezePeriodsFailsTooLong(String... dates)  {
+        validateFreezePeriodsFails(ValidationFailedException.ERROR_NEW_FREEZE_PERIOD_TOO_LONG,
+                dates);
+    }
+
+    private void validateFreezePeriodsFailsTooClose(String... dates)  {
+        validateFreezePeriodsFails(ValidationFailedException.ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE,
+                dates);
+    }
+
+    //dates are in MM-DD format
+    private void setFreezePeriods(SystemUpdatePolicy policy, String... dates) {
+        List<Pair<Integer, Integer>> periods = new ArrayList<>();
+        for (int i = 0; i < dates.length; i+= 2) {
+            periods.add(new Pair<>(parseDate(dates[i]), parseDate(dates[i + 1])));
+        }
+        policy.setFreezePeriods(periods);
+    }
+
+    private int parseDate(String date) {
+        // Use leap year when parsing date string to handle "02-29", but force round down
+        // to Feb 28th by overriding the year to non-leap year.
+        return  LocalDate.of(2000, Integer.parseInt(date.substring(0, 2)),
+                Integer.parseInt(date.substring(3, 5))).withYear(2001).getDayOfYear();
+    }
+
+    private void clearFreezeRecord() throws Exception {
+        executeShellCommand("dpm", "clear-freeze-period-record");
+    }
+
+    private void setSystemDate(LocalDate date) throws Exception {
+        mRestoreDate = true;
+        Calendar c = Calendar.getInstance();
+        c.set(Calendar.YEAR, date.getYear());
+        c.set(Calendar.MONTH, date.getMonthValue() - 1);
+        c.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth());
+        mDevicePolicyManager.setTime(getWho(), c.getTimeInMillis());
+        waitForTimeChangedBroadcast();
+    }
+
+    private void waitForPolicyChangedBroadcast() {
         try {
             assertTrue("Timeout while waiting for broadcast.",
                     mPolicyChangedSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -140,4 +332,13 @@
             fail("Interrupted while waiting for broadcast.");
         }
     }
+
+    private void waitForTimeChangedBroadcast() {
+        try {
+            assertTrue("Timeout while waiting for broadcast.",
+                    mTimeChangedSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail("Interrupted while waiting for broadcast.");
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
index 08edf44..a416970 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
@@ -24,16 +24,15 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
-    ctstestrunner \
-    legacy-android-test
+    ctstestrunner
 
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index da0e16c..84c5de6 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -35,6 +35,7 @@
                 <action android:name="com.android.cts.action.NOTIFY_URI_CHANGE"/>
                 <action android:name="com.android.cts.action.OBSERVE_URI_CHANGE"/>
                 <action android:name="com.android.cts.action.JUST_CREATE" />
+                <action android:name="com.android.cts.action.CREATE_AND_WAIT" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java
new file mode 100644
index 0000000..0d2a990
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.cts.intent.receiver;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class that writes to shared preference and verifies that the shared preference gets cleared
+ * after DPM.clearApplicationUserData was called.
+ */
+@SmallTest
+public class ClearApplicationDataTest {
+    private static final String SHARED_PREFERENCE_NAME = "test-preference";
+    private static final String I_WAS_HERE = "I-Was-Here";
+
+    private Context mContext;
+    private SharedPreferences mSharedPrefs;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mSharedPrefs = mContext.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
+    }
+
+    @Test
+    public void testWriteToSharedPreference() {
+        mSharedPrefs.edit().putBoolean(I_WAS_HERE, true).commit();
+        assertTrue(mSharedPrefs.contains(I_WAS_HERE));
+    }
+
+    @Test
+    public void testSharedPreferenceCleared() {
+        assertFalse(mSharedPrefs.contains(I_WAS_HERE));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
index a645a87..5f04be0 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
@@ -57,8 +57,14 @@
     private static final String ACTION_JUST_CREATE =
             "com.android.cts.action.JUST_CREATE";
 
-    public static final String RECEIVING_ACTIVITY_CREATED_ACTION
-            = "com.android.cts.deviceowner.RECEIVING_ACTIVITY_CREATED_ACTION";
+    private static final String ACTION_CREATE_AND_WAIT =
+            "com.android.cts.action.CREATE_AND_WAIT";
+
+    private static final String RECEIVER_ACTIVITY_CREATED_ACTION =
+            "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_CREATED";
+
+    private static final String RECEIVER_ACTIVITY_DESTROYED_ACTION =
+            "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_DESTROYED";
 
     public static final String ACTION_NOTIFY_URI_CHANGE
             = "com.android.cts.action.NOTIFY_URI_CHANGE";
@@ -131,10 +137,19 @@
                 getContentResolver().unregisterContentObserver(uriObserver);
                 handlerThread.quit();
             }
-        } else if (ACTION_JUST_CREATE.equals(action)) {
-            sendBroadcast(new Intent(RECEIVING_ACTIVITY_CREATED_ACTION));
+        } else if (ACTION_JUST_CREATE.equals(action) || ACTION_CREATE_AND_WAIT.equals(action)) {
+            sendBroadcast(new Intent(RECEIVER_ACTIVITY_CREATED_ACTION));
         }
-        finish();
+
+        if (!ACTION_CREATE_AND_WAIT.equals(action)) {
+            finish();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        sendBroadcast(new Intent(RECEIVER_ACTIVITY_DESTROYED_ACTION));
+        super.onDestroy();
     }
 
     private class UriObserver extends ContentObserver {
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.mk b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
index b71ddfb..875094f 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
@@ -24,17 +24,16 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-v4 \
 	ctstestrunner \
-	ub-uiautomator \
-	legacy-android-test
+	ub-uiautomator
 
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
index ed4943f..85e369b 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -24,13 +24,15 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test cts-junit
+LOCAL_JAVA_LIBRARIES := cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES = \
 	android-support-v4 \
 	ctstestrunner \
 	android-support-test \
-	legacy-android-test
+	compatibility-device-util \
+	ShortcutManagerTestUtils \
+	testng
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
new file mode 100644
index 0000000..0349594
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.launchertests;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test that runs {@link UserManager#trySetQuietModeEnabled(boolean, UserHandle)} API
+ * against valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class QuietModeTest {
+    private static final String PARAM_TARGET_USER = "TARGET_USER";
+    private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER";
+    private static final ComponentName LAUNCHER_ACTIVITY =
+            new ComponentName(
+                    "com.android.cts.launchertests.support",
+                    "com.android.cts.launchertests.support.LauncherActivity");
+
+    private static final ComponentName COMMAND_RECEIVER =
+            new ComponentName(
+                    "com.android.cts.launchertests.support",
+                    "com.android.cts.launchertests.support.QuietModeCommandReceiver");
+
+    private UserManager mUserManager;
+    private UserHandle mTargetUser;
+    private Context mContext;
+    private String mOriginalLauncher;
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setupUserManager() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUserManager = mContext.getSystemService(UserManager.class);
+    }
+
+    @Before
+    public void readParams() {
+        Context context = InstrumentationRegistry.getContext();
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        UserManager userManager = context.getSystemService(UserManager.class);
+        final int userSn = Integer.parseInt(arguments.getString(PARAM_TARGET_USER));
+        mTargetUser = userManager.getUserForSerialNumber(userSn);
+        mOriginalLauncher = arguments.getString(PARAM_ORIGINAL_DEFAULT_LAUNCHER);
+    }
+
+    @Before
+    public void wakeupDeviceAndUnlock() throws Exception {
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUiDevice.wakeUp();
+        mUiDevice.pressMenu();
+    }
+
+    @Before
+    @After
+    public void revertToDefaultLauncher() throws Exception {
+        if (TextUtils.isEmpty(mOriginalLauncher)) {
+            return;
+        }
+        setDefaultLauncher(InstrumentationRegistry.getInstrumentation(), mOriginalLauncher);
+        startActivitySync(mOriginalLauncher);
+    }
+
+    @Test
+    public void testTryEnableQuietMode_defaultForegroundLauncher() throws Exception {
+        setTestAppAsDefaultLauncher();
+        startLauncherActivityInTestApp();
+
+        Intent intent = trySetQuietModeEnabled(true);
+        assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent);
+        assertTrue(mUserManager.isQuietModeEnabled(mTargetUser));
+
+        intent = trySetQuietModeEnabled(false);
+        assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_AVAILABLE broadcast", intent);
+        assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+    }
+
+    @Test
+    public void testTryEnableQuietMode_notForegroundLauncher() throws InterruptedException {
+        setTestAppAsDefaultLauncher();
+
+        assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true));
+        assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+    }
+
+    @Test
+    public void testTryEnableQuietMode_notDefaultLauncher() throws Exception {
+        startLauncherActivityInTestApp();
+
+        assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true));
+        assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+    }
+
+    private Intent trySetQuietModeEnabled(boolean enabled) throws Exception {
+        final String action = enabled
+                ? Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
+                : Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
+
+        BlockingBroadcastReceiver receiver =
+                new BlockingBroadcastReceiver(mContext, action);
+        try {
+            receiver.register();
+
+            boolean notShowingConfirmCredential = askLauncherSupportAppToSetQuietMode(enabled);
+            assertTrue(notShowingConfirmCredential);
+
+            return receiver.awaitForBroadcast();
+        } finally {
+            receiver.unregisterQuietly();
+        }
+    }
+
+    /**
+     * Ask launcher support test app to set quiet mode by sending broadcast.
+     * <p>
+     * We cannot simply make this package the launcher and call the API because instrumentation
+     * process would always considered to be in the foreground. The trick here is to send
+     * broadcast to another test app which is launcher itself and call the API through it.
+     * The receiver will then send back the result, and it should be either true, false or
+     * security-exception.
+     * <p>
+     * All the constants defined here should be aligned with
+     * com.android.cts.launchertests.support.QuietModeCommandReceiver.
+     */
+    private boolean askLauncherSupportAppToSetQuietMode(boolean enabled) throws Exception {
+        Intent intent = new Intent("toggle_quiet_mode");
+        intent.setComponent(COMMAND_RECEIVER);
+        intent.putExtra("quiet_mode", enabled);
+        intent.putExtra(Intent.EXTRA_USER, mTargetUser);
+
+        // Ask launcher support app to set quiet mode by sending broadcast.
+        LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
+        mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                blockingQueue.offer(getResultData());
+            }
+        }, null, 0, "", null);
+
+        // Wait for the result.
+        String result = null;
+        for (int i = 0; i < 10; i++) {
+            // Broadcast won't be delivered when the device is sleeping, so wake up the device
+            // in between each attempt.
+            wakeupDeviceAndUnlock();
+            result = blockingQueue.poll(10, TimeUnit.SECONDS);
+            if (!TextUtils.isEmpty(result)) {
+                break;
+            }
+        }
+
+        // Parse the result.
+        assertNotNull(result);
+        if ("true".equalsIgnoreCase(result)) {
+            return true;
+        } else if ("false".equalsIgnoreCase(result)) {
+            return false;
+        } else if ("security-exception".equals(result)) {
+            throw new SecurityException();
+        }
+        throw new IllegalStateException("Unexpected result : " + result);
+    }
+
+    private void startActivitySync(String activity) throws Exception {
+        mUiDevice.executeShellCommand("am start -W -n " + activity);
+    }
+
+    /**
+     * Start the launcher activity in the test app to make it foreground.
+     */
+    private void startLauncherActivityInTestApp() throws Exception {
+        startActivitySync(LAUNCHER_ACTIVITY.flattenToString());
+    }
+
+    private void setTestAppAsDefaultLauncher() {
+        setDefaultLauncher(
+                InstrumentationRegistry.getInstrumentation(),
+                LAUNCHER_ACTIVITY.flattenToString());
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
index dc71264..14abd1a 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -26,5 +26,19 @@
                 <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
             </intent-filter>
         </service>
+
+        <activity android:name=".LauncherActivity">
+            <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>
+
+        <receiver android:name=".QuietModeCommandReceiver" android:exported="true">
+            <intent-filter>
+                <action android:name="toggle_quiet_mode"/>
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java
new file mode 100644
index 0000000..348e46c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.launchertests.support;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java
new file mode 100644
index 0000000..a0a9abb
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java
@@ -0,0 +1,55 @@
+/*
+ * 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.launchertests.support;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+/**
+ * Runs {@link UserManager#requestQuietModeEnabled(boolean, UserHandle)} APIs by receiving
+ * broadcast and returns the result back to the broadcast sender.
+ */
+public class QuietModeCommandReceiver extends BroadcastReceiver {
+    private static final String TAG = "QuietModeReceiver";
+    private static final String ACTION_TOGGLE_QUIET_MODE = "toggle_quiet_mode";
+    private static final String EXTRA_QUIET_MODE = "quiet_mode";
+    private static final String RESULT_SECURITY_EXCEPTION = "security-exception";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!ACTION_TOGGLE_QUIET_MODE.equals(intent.getAction())) {
+            return;
+        }
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        final boolean enableQuietMode = intent.getBooleanExtra(EXTRA_QUIET_MODE, false);
+        final UserHandle targetUser = intent.getParcelableExtra(Intent.EXTRA_USER);
+        String result;
+        try {
+            final boolean setQuietModeResult =
+                    userManager.requestQuietModeEnabled(enableQuietMode, targetUser);
+            result = Boolean.toString(setQuietModeResult);
+            Log.i(TAG, "trySetQuietModeEnabled returns " + setQuietModeResult);
+        } catch (SecurityException ex) {
+            Log.i(TAG, "trySetQuietModeEnabled throws security exception", ex);
+            result = RESULT_SECURITY_EXCEPTION;
+        }
+        setResultData(result);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index b947a61..b14af35 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES = \
 	android-support-v4 \
@@ -32,12 +32,11 @@
 	compatibility-device-util \
 	ub-uiautomator \
 	android-support-test \
-	guava \
-	legacy-android-test
+	guava
 
 LOCAL_SDK_VERSION := test_current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 05ebac0..11a54d3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -77,6 +77,12 @@
                 <category android:name="android.intent.category.DEFAULT"/>
                 <action android:name="com.android.cts.managedprofile.ACTION_TEST_MANAGED_ACTIVITY" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <data android:mimeType="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
         <activity android:name=".PrimaryUserActivity">
             <intent-filter>
@@ -145,6 +151,11 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".WebViewActivity"
+            android:process=":testProcess"/>
+
+        <activity android:name=".TimeoutActivity" android:exported="true"/>
+
         <service
             android:name=".CrossProfileNotificationListenerService"
             android:label="CrossProfileNotificationListenerService"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index 905f7d5..f10822c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,7 +19,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 
 /**
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
index 550f1d3..4e2f58f 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
@@ -168,6 +168,9 @@
             typeName = typeName.substring(0, typeName.length() - 2);
         }
 
+        // Resolve inner classes
+        typeName = typeName.replaceAll("([A-Z].*)\\.", "$1\\$");
+
         // Remove type parameters, if any
         typeName = typeName.replaceAll("<.*>$", "");
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DisallowSharingIntoProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DisallowSharingIntoProfileTest.java
new file mode 100644
index 0000000..8f1f88e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DisallowSharingIntoProfileTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.managedprofile;
+
+import static com.android.cts.managedprofile.BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verify that certain cross profile intent filters are disallowed when the device admin sets
+ * DISALLOW_SHARE_INTO_MANAGED_PROFILE restriction.
+ * <p>
+ * The way we check if a particular cross profile intent filter is disallowed is by trying to
+ * resolve an example intent that matches the intent filter. The cross profile filter functions
+ * correctly if and only if the resolution result contains a system intent forwarder activity
+ * (com.android.internal.app.IntentForwarderActivity), which is the framework's mechanism to
+ * trampoline intents across profiles. Instead of hardcoding the system's intent forwarder activity,
+ * we retrieve it programmatically by resolving known cross profile intents specifically set up for
+ * this purpose: {@link #KNOWN_ACTION_TO_PROFILE} and {@link #KNOWN_ACTION_TO_PERSONAL}
+ */
+public class DisallowSharingIntoProfileTest extends InstrumentationTestCase {
+
+    // These are the data sharing intents which can be forwarded to the managed profile.
+    private final List<Intent> sharingIntentsToProfile = Arrays.asList(
+            new Intent(Intent.ACTION_SEND).setType("*/*"),
+            new Intent(Intent.ACTION_SEND_MULTIPLE).setType("*/*"));
+
+    // These are the data sharing intents which can be forwarded to the primary profile.
+    private final List<Intent> sharingIntentsToPersonal = new ArrayList<>(Arrays.asList(
+            new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(
+                    Intent.CATEGORY_OPENABLE),
+            new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("*/*").addCategory(
+                    Intent.CATEGORY_OPENABLE),
+            new Intent(Intent.ACTION_PICK).setType("*/*").addCategory(
+                    Intent.CATEGORY_DEFAULT),
+            new Intent(Intent.ACTION_PICK).addCategory(Intent.CATEGORY_DEFAULT)));
+
+    // These are the data sharing intents which can be forwarded to the primary profile,
+    // if the device supports FEATURE_CAMERA
+    private final List<Intent> sharingIntentsToPersonalIfCameraExists = Arrays.asList(
+            new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+            new Intent(MediaStore.ACTION_VIDEO_CAPTURE),
+            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA),
+            new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA),
+            new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE),
+            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE));
+
+    private static final String KNOWN_ACTION_TO_PROFILE = ManagedProfileActivity.ACTION;
+    private static final String KNOWN_ACTION_TO_PERSONAL = PrimaryUserActivity.ACTION;
+
+    protected Context mContext;
+    protected DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mContext = getInstrumentation().getContext();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        assertNotNull(mDevicePolicyManager);
+    }
+
+    public void testSetUp() throws Exception {
+        // toggle the restriction to reset system's built-in cross profile intent filters,
+        // simulating the default state of a work profile created by ManagedProvisioning
+        testDisableSharingIntoProfile();
+        testEnableSharingIntoProfile();
+
+        PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+            sharingIntentsToPersonal.addAll(sharingIntentsToPersonalIfCameraExists);
+        }
+
+        mDevicePolicyManager.clearCrossProfileIntentFilters(ADMIN_RECEIVER_COMPONENT);
+        // Set up cross profile intent filters so we can resolve these to find out framework's
+        // intent forwarder activity as ground truth
+        mDevicePolicyManager.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT,
+                new IntentFilter(KNOWN_ACTION_TO_PERSONAL),
+                DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED);
+        mDevicePolicyManager.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT,
+                new IntentFilter(KNOWN_ACTION_TO_PROFILE),
+                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
+    }
+
+    /**
+     * Test sharing initiated from the personal side are mainly driven from the host side, see
+     * ManagedProfileTest.testDisallowSharingIntoProfileFromPersonal() See javadoc of
+     * {@link #DisallowSharingIntoProfileTest} class for the mechanism behind this test.
+     */
+    public void testSharingFromPersonalFails() {
+        ResolveInfo toProfileForwarderInfo = getIntentForwarder(
+                new Intent(KNOWN_ACTION_TO_PROFILE));
+        assertCrossProfileIntentsResolvability(sharingIntentsToProfile, toProfileForwarderInfo,
+                /* expectForwardable */ false);
+    }
+
+    public void testSharingFromPersonalSucceeds() {
+        ResolveInfo toProfileForwarderInfo = getIntentForwarder(
+                new Intent(KNOWN_ACTION_TO_PROFILE));
+        assertCrossProfileIntentsResolvability(sharingIntentsToProfile, toProfileForwarderInfo,
+                /* expectForwardable */ true);
+    }
+
+    /**
+     * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
+     * a work app. See javadoc of {@link #DisallowSharingIntoProfileTest} class for the mechanism
+     * behind this test.
+     */
+    public void testSharingFromProfile() throws Exception {
+        testSetUp();
+        ResolveInfo toPersonalForwarderInfo = getIntentForwarder(
+                new Intent(KNOWN_ACTION_TO_PERSONAL));
+
+        testDisableSharingIntoProfile();
+        assertCrossProfileIntentsResolvability(sharingIntentsToPersonal, toPersonalForwarderInfo,
+                /* expectForwardable */ false);
+        testEnableSharingIntoProfile();
+        assertCrossProfileIntentsResolvability(sharingIntentsToPersonal, toPersonalForwarderInfo,
+                /* expectForwardable */ true);
+    }
+
+    public void testEnableSharingIntoProfile() throws Exception {
+        setSharingEnabled(true);
+    }
+
+    public void testDisableSharingIntoProfile() throws Exception {
+        setSharingEnabled(false);
+    }
+
+    private void setSharingEnabled(boolean enabled) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                latch.countDown();
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED);
+        mContext.registerReceiver(receiver, filter);
+        try {
+            if (enabled) {
+                mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                        UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+            } else {
+                mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                        UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+            }
+            // Wait for the restriction to apply
+            assertTrue("Restriction not applied after 5 seconds", latch.await(5, TimeUnit.SECONDS));
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    private void assertCrossProfileIntentsResolvability(List<Intent> intents,
+            ResolveInfo expectedForwarder, boolean expectForwardable) {
+        for (Intent intent : intents) {
+            List<ResolveInfo> resolveInfoList = mContext.getPackageManager().queryIntentActivities(
+                    intent,
+                    PackageManager.MATCH_DEFAULT_ONLY);
+            if (expectForwardable) {
+                assertTrue("Expect " + intent + " to be forwardable, but resolve list"
+                        + " does not contain expected intent forwarder " + expectedForwarder,
+                        containsResolveInfo(resolveInfoList, expectedForwarder));
+            } else {
+                assertFalse("Expect " + intent + " not to be forwardable, but resolve list "
+                        + "contains intent forwarder " + expectedForwarder,
+                        containsResolveInfo(resolveInfoList, expectedForwarder));
+            }
+        }
+    }
+
+    private ResolveInfo getIntentForwarder(Intent intent) {
+        List<ResolveInfo> result = mContext.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        assertEquals("Expect only one resolve result for " + intent, 1, result.size());
+        return result.get(0);
+    }
+
+    private boolean containsResolveInfo(List<ResolveInfo> list, ResolveInfo info) {
+        for (ResolveInfo entry : list) {
+            if (entry.activityInfo.packageName.equals(info.activityInfo.packageName)
+                    && entry.activityInfo.name.equals(info.activityInfo.name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java
new file mode 100644
index 0000000..971f144
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.managedprofile;
+
+/**
+ * Test to check reporting the state of profile challenge.
+ */
+public class IsUsingUnifiedPasswordTest extends BaseManagedProfileTest {
+
+    public void testNotUsingUnifiedPassword() {
+        assertFalse(mDevicePolicyManager.isUsingUnifiedPassword(ADMIN_RECEIVER_COMPONENT));
+    }
+
+    public void testUsingUnifiedPassword() {
+        assertTrue(mDevicePolicyManager.isUsingUnifiedPassword(ADMIN_RECEIVER_COMPONENT));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
index fa74db7..fd5fcd2 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
@@ -18,8 +18,6 @@
 
 import android.app.admin.DevicePolicyManager;
 
-import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
-
 /**
  * Test lockNow() for use in a managed profile. If called from a managed profile. lockNow() can be
  * passed a flag to evict the CE key of the profile.
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index f086656..a74cba5 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -77,6 +77,8 @@
             .add("setTrustAgentConfiguration")
             .add("getRequiredStrongAuthTimeout")
             .add("setRequiredStrongAuthTimeout")
+            .add("getPasswordBlacklistName")
+            .add("setPasswordBlacklist")
             .build();
 
     private static final String LOG_TAG = "ParentProfileTest";
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
new file mode 100644
index 0000000..a386baa
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
@@ -0,0 +1,66 @@
+/*
+ * 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.managedprofile;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
+
+/**
+ * Helper to set lock timeouts and check if the profile is locked.
+ */
+public class ProfileTimeoutTestHelper extends InstrumentationTestCase {
+    // This should be sufficiently smaller than ManagedProfileTest.PROFILE_TIMEOUT_DELAY_SEC.
+    private static final int TIMEOUT_MS = 5_000;
+    private static final ComponentName ADMIN_COMPONENT = new ComponentName(
+            BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
+
+    private KeyguardManager mKm;
+    private DevicePolicyManager mDpm;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        final Context context = getInstrumentation().getContext();
+        mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mKm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+    }
+
+    public void testSetWorkProfileTimeout() {
+        assertProfileOwner();
+        mDpm.setMaximumTimeToLock(ADMIN_COMPONENT, TIMEOUT_MS);
+        assertEquals("Failed to set timeout",
+                TIMEOUT_MS, mDpm.getMaximumTimeToLock(ADMIN_COMPONENT));
+    }
+
+    public void testDeviceLocked() {
+        assertTrue("Device not locked", mKm.isDeviceLocked());
+    }
+
+    public void testDeviceNotLocked() {
+        assertFalse("Device locked", mKm.isDeviceLocked());
+    }
+
+    private void assertProfileOwner() {
+        assertTrue(mDpm.isProfileOwnerApp(ADMIN_COMPONENT.getPackageName()));
+        assertTrue(mDpm.isManagedProfile(ADMIN_COMPONENT));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SanityTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SanityTest.java
index 28c7e25..683d465 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SanityTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SanityTest.java
@@ -62,6 +62,7 @@
             intent.setComponent(SIMPLE_APP_ACTIVITY);
             // Finish the activity after that.
             intent.putExtra("finish", true);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mContext.startActivity(intent);
             Intent receivedBroadcast = receiver.awaitForBroadcast();
             assertNotNull(receivedBroadcast);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java
new file mode 100644
index 0000000..a574477
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.managedprofile;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Activity to test profile lock timeout.
+ */
+public class TimeoutActivity extends Activity {
+    private static final String KEEP_SCREEN_ON = "keep_screen_on";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final View blankView = new View(this);
+        setContentView(blankView);
+
+        if (getIntent().getBooleanExtra(KEEP_SCREEN_ON, false)) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java
new file mode 100644
index 0000000..b5e16f4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java
@@ -0,0 +1,14 @@
+package com.android.cts.managedprofile;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class WebViewActivity extends Activity {
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(new WebView(this));
+  }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 7ef3a0a..65c7724 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -15,16 +15,8 @@
  */
 package com.android.cts.managedprofile;
 
-
-import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
-
-import org.junit.Ignore;
-
-/**
- * Test wipeData() for use in managed profile. If called from a managed profile, wipeData() should
- * remove the current managed profile. Also, no erasing of external storage should be allowed.
- */
 public class WipeDataTest extends BaseManagedProfileTest {
+    private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
 
     @Override
     protected void setUp() throws Exception {
@@ -35,8 +27,25 @@
         assertTrue(mDevicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
     }
 
+    /**
+     * Test wipeData() for use in managed profile. If called from a managed profile, wipeData()
+     * should remove the current managed profile. Also, no erasing of external storage should be
+     * allowed.
+     */
     public void testWipeData() throws InterruptedException {
         mDevicePolicyManager.wipeData(0);
-        // the test that the profile will indeed be removed is done in the host.
+        // The test that the profile will indeed be removed is done in the host.
+    }
+
+    /**
+     * Test wipeDataWithReason() for use in managed profile. If called from a managed profile,
+     * wipeDataWithReason() should remove the current managed profile.In the mean time, it should
+     * send out a notification containing the reason for wiping data to user. Also, no erasing of
+     * external storage should be allowed.
+     */
+    public void testWipeDataWithReason() throws InterruptedException {
+        mDevicePolicyManager.wipeData(0, TEST_WIPE_DATA_REASON);
+        // The test that the profile will indeed be removed is done in the host.
+        // Notification verification is done in another test.
     }
 }
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
new file mode 100644
index 0000000..3b35cb4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java
@@ -0,0 +1,37 @@
+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/MeteredDataTestApp/Android.mk b/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.mk
new file mode 100644
index 0000000..a63c410b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/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 := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+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 := CtsMeteredDataTestApp
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..d1228f8
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/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.devicepolicy.metereddatatestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application>
+        <activity android:name=".MainActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/src/com/android/cts/devicepolicy/metereddatatestapp/MainActivity.java b/hostsidetests/devicepolicy/app/MeteredDataTestApp/src/com/android/cts/devicepolicy/metereddatatestapp/MainActivity.java
new file mode 100644
index 0000000..09282b3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/src/com/android/cts/devicepolicy/metereddatatestapp/MainActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.metereddatatestapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class MainActivity extends Activity {
+    private static final String TAG = MainActivity.class.getSimpleName();
+
+    private static final String EXTRA_MESSENGER = "messenger";
+    private static final int MSG_NOTIFY_NETWORK_STATE = 1;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Log.i(TAG, "onCreate called");
+        notifyNetworkStateCallback();
+        finish();
+    }
+
+    private void notifyNetworkStateCallback() {
+        final Messenger callbackMessenger = new Messenger(getIntent().getExtras().getBinder(
+                EXTRA_MESSENGER));
+        if (callbackMessenger == null) {
+            return;
+        }
+        try {
+            final NetworkInfo networkInfo = getActiveNetworkInfo();
+            Log.e(TAG, "getActiveNetworkNetworkInfo() is null");
+            callbackMessenger.send(Message.obtain(null,
+                    MSG_NOTIFY_NETWORK_STATE, networkInfo));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while sending the network state", e);
+        }
+    }
+
+    private NetworkInfo getActiveNetworkInfo() {
+        final ConnectivityManager cm = (ConnectivityManager) getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        return cm.getActiveNetworkInfo();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
index 11680e9..a67bacf 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
@@ -24,17 +24,16 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
     ctstestrunner \
-    ub-uiautomator \
-    legacy-android-test
+    ub-uiautomator
 
 LOCAL_SDK_VERSION := test_current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/PrintingApp/Android.mk b/hostsidetests/devicepolicy/app/PrintingApp/Android.mk
new file mode 100644
index 0000000..be8f1f7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PrintingApp/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)
+
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsDevicePolicyPrintingApp
+
+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/PrintingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/PrintingApp/AndroidManifest.xml
new file mode 100644
index 0000000..a55fed7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PrintingApp/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.devicepolicy.printingapp" >
+
+    <application>
+        <activity android:name=".PrintActivity" android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/PrintingApp/src/com/android/cts/devicepolicy/printingapp/PrintActivity.java b/hostsidetests/devicepolicy/app/PrintingApp/src/com/android/cts/devicepolicy/printingapp/PrintActivity.java
new file mode 100644
index 0000000..677439d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PrintingApp/src/com/android/cts/devicepolicy/printingapp/PrintActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy.printingapp;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintJob;
+import android.print.PrintManager;
+
+public class PrintActivity extends Activity {
+
+    private static final String PRINT_JOB_NAME = "Test print job";
+    private static final String EXTRA_ERROR_MESSAGE = "error_message";
+    private static final int STATE_INIT = 0;
+    private static final int STATE_STARTED = 1;
+    private static final int STATE_FINISHED = 2;
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        PrintManager printManager = getSystemService(PrintManager.class);
+        final int[] state = new int[]{STATE_INIT};
+        PrintJob printJob = printManager.print(PRINT_JOB_NAME, new PrintDocumentAdapter() {
+            @Override
+            public void onStart() {
+                if (state[0] != STATE_INIT) {
+                    fail("Unexpected call to onStart()");
+                }
+                state[0] = STATE_STARTED;
+            }
+
+            @Override
+            public void onFinish() {
+                if (state[0] != STATE_STARTED) {
+                    fail("Unexpected call to onFinish()");
+                }
+                state[0] = STATE_FINISHED;
+                // Use RESULT_FIRST_USER for success to avoid false positives.
+                setResult(RESULT_FIRST_USER);
+                finish();
+            }
+
+            @Override
+            public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+                    CancellationSignal signal, PrintDocumentAdapter.LayoutResultCallback cb,
+                    Bundle extras) {
+                fail("onLayout() should never be called");
+            }
+
+            @Override
+            public void onWrite(PageRange[] pages, ParcelFileDescriptor dest, CancellationSignal signal,
+                    PrintDocumentAdapter.WriteResultCallback cb) {
+                fail("onWrite() should never be called");
+            }
+        }, new PrintAttributes.Builder().build());
+        if (printJob != null) {
+            fail("print() should return null");
+        }
+    }
+
+    private final void fail(String message) {
+        setResult(RESULT_FIRST_USER + 1, new Intent().putExtra(EXTRA_ERROR_MESSAGE, message));
+        finish();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
index 34b8e08..600a184 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsProfileOwnerApp
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_MODULE_TAGS := optional
 
@@ -24,15 +25,17 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    conscrypt \
+    cts-junit \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     compatibility-device-util \
-    ub-uiautomator \
-    legacy-android-test
-
-LOCAL_SDK_VERSION := test_current
+    ub-uiautomator
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.mk b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
index f0007db..b4f6e6d 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
@@ -32,6 +32,6 @@
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.mk b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.mk
index deb5320..4a6e4f8 100644
--- a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.mk
@@ -31,6 +31,6 @@
 LOCAL_SDK_VERSION := 21
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
index 426dbf2..c44f414 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
@@ -24,15 +24,14 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 LOCAL_STATIC_JAVA_LIBRARIES = \
     android-support-v4 \
     ctstestrunner \
     compatibility-device-util \
     ub-uiautomator \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
new file mode 100644
index 0000000..5f3212f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
@@ -0,0 +1,42 @@
+# 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_PACKAGE_NAME := CtsTransferOwnerIncomingApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    ctstestrunner \
+    compatibility-device-util \
+    ub-uiautomator \
+    android-support-test \
+    testng
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
new file mode 100644
index 0000000..138275f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?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.transferownerincoming">
+
+    <uses-sdk android:minSdkVersion="24"/>
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+    <application
+        android:testOnly="true">
+
+        <uses-library android:name="android.test.runner"/>
+        <receiver
+            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin"/>
+            <meta-data
+                android:name="android.app.support_transfer_ownership"
+                android:value="true"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiverNoMetadata"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.transferownerincoming"
+                     android:label="Transfer Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..66a3730
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml
@@ -0,0 +1,12 @@
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+        <limit-password/>
+        <watch-login/>
+        <reset-password/>
+        <force-lock/>
+        <wipe-data/>
+        <expire-password/>
+        <encrypted-storage/>
+        <disable-camera/>
+    </uses-policies>
+</device-admin>
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
new file mode 100644
index 0000000..7a598d9
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.transferowner;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.PersistableBundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+
+@SmallTest
+public class DeviceAndProfileOwnerTransferIncomingTest {
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {
+        public BasicAdminReceiver() {}
+
+        @Override
+        public void onTransferOwnershipComplete(Context context, PersistableBundle bundle) {
+            putBooleanPref(context, KEY_TRANSFER_COMPLETED_CALLED, true);
+        }
+    }
+
+    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 ARE_PARAMETERS_SAVED = "ARE_PARAMETERS_SAVED";
+
+    protected Context mContext;
+    protected ComponentName mIncomingComponentName;
+    protected DevicePolicyManager mDevicePolicyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mIncomingComponentName = new ComponentName(mContext, BasicAdminReceiver.class.getName());
+    }
+
+    @Test
+    public void testTransferCompleteCallbackIsCalled() {
+        assertTrue(getBooleanPref(mContext, KEY_TRANSFER_COMPLETED_CALLED));
+    }
+
+    @Test
+    public void testIsAffiliationId1() {
+        assertEquals("id.number.1", getAffiliationId());
+    }
+
+    private String getAffiliationId() {
+        ComponentName admin = mIncomingComponentName;
+        DevicePolicyManager dpm = (DevicePolicyManager)
+                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        Set<String> affiliationIds = dpm.getAffiliationIds(admin);
+        assertNotNull(affiliationIds);
+        assertEquals(1, affiliationIds.size());
+        return affiliationIds.iterator().next();
+    }
+
+    @Test
+    public void testTransferOwnershipBundleLoaded() throws Throwable {
+        PersistableBundle bundle = mDevicePolicyManager.getTransferOwnershipBundle();
+        assertNotNull(bundle);
+        assertTrue(bundle.getBoolean(ARE_PARAMETERS_SAVED));
+    }
+
+    @Test
+    public void testTransferOwnershipEmptyBundleLoaded() throws Throwable {
+        PersistableBundle bundle = mDevicePolicyManager.getTransferOwnershipBundle();
+        assertNotNull(bundle);
+        assertTrue(bundle.isEmpty());
+    }
+
+    private static SharedPreferences getPrefs(Context context) {
+        return context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
+    }
+
+    private static void putBooleanPref(Context context, String key, boolean value) {
+        getPrefs(context).edit().putBoolean(key, value).apply();
+    }
+
+    protected static boolean getBooleanPref(Context context, String key) {
+        return getPrefs(context).getBoolean(key, false);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
new file mode 100644
index 0000000..b33da04
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.SystemUpdatePolicy;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class TransferDeviceOwnerIncomingTest extends DeviceAndProfileOwnerTransferIncomingTest {
+    @Test
+    public void testTransferPoliciesAreRetainedAfterTransfer() {
+        assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mIncomingComponentName.getPackageName()));
+        assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
+        assertEquals(Collections.singletonList("test.package"),
+                mDevicePolicyManager.getKeepUninstalledPackages(mIncomingComponentName));
+        assertEquals(123, mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
+        assertSystemPoliciesEqual(SystemUpdatePolicy.createWindowedInstallPolicy(123, 456),
+                mDevicePolicyManager.getSystemUpdatePolicy());
+        assertThrows(SecurityException.class, () -> {
+            mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
+        });
+    }
+
+    private void assertSystemPoliciesEqual(SystemUpdatePolicy policy1, SystemUpdatePolicy policy2) {
+        assertTrue(policy1.getPolicyType() == policy2.getPolicyType()
+                && policy1.getInstallWindowStart() == policy2.getInstallWindowStart()
+                && policy1.getInstallWindowEnd() == policy2.getInstallWindowEnd());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
new file mode 100644
index 0000000..ef7e8ac
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.admin.DevicePolicyManager;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TransferProfileOwnerIncomingTest extends DeviceAndProfileOwnerTransferIncomingTest {
+    @Test
+    public void testTransferPoliciesAreRetainedAfterTransfer() {
+        int passwordLength = 123;
+        int passwordExpirationTimeout = 456;
+        assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+        assertTrue(mDevicePolicyManager.isProfileOwnerApp(mIncomingComponentName.getPackageName()));
+        assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
+        assertTrue(mDevicePolicyManager.getCrossProfileCallerIdDisabled(mIncomingComponentName));
+        assertEquals(
+                passwordLength,
+                mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
+
+        DevicePolicyManager targetParentProfileInstance =
+                mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
+        assertEquals(
+                passwordExpirationTimeout,
+                targetParentProfileInstance.getPasswordExpirationTimeout(mIncomingComponentName));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
new file mode 100644
index 0000000..71e2078
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
@@ -0,0 +1,42 @@
+# 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_PACKAGE_NAME := CtsTransferOwnerOutgoingApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    ctstestrunner \
+    compatibility-device-util \
+    ub-uiautomator \
+    android-support-test \
+    testng
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
new file mode 100644
index 0000000..59feeb3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/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.transferowneroutgoing">
+
+    <uses-sdk android:minSdkVersion="24"/>
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+    <application
+        android:testOnly="true">
+
+        <uses-library android:name="android.test.runner"/>
+        <receiver
+            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.transferowneroutgoing"
+                     android:label="Transfer Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..66a3730
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml
@@ -0,0 +1,12 @@
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+        <limit-password/>
+        <watch-login/>
+        <reset-password/>
+        <force-lock/>
+        <wipe-data/>
+        <expire-password/>
+        <encrypted-storage/>
+        <disable-camera/>
+    </uses-policies>
+</device-admin>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
new file mode 100644
index 0000000..711ba78
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.transferowner;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+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.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class DeviceAndProfileOwnerTransferOutgoingTest {
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {
+        public BasicAdminReceiver() {}
+
+        @Override
+        public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) {
+            putBooleanPref(context, KEY_TRANSFER_AFFILIATED_PROFILE_COMPLETED_CALLED, true);
+        }
+    }
+
+    private static final String TRANSFER_OWNER_INCOMING_PKG =
+            "com.android.cts.transferownerincoming";
+    private static final String TRANSFER_OWNER_INCOMING_TEST_RECEIVER_CLASS =
+            "com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest"
+                    + "$BasicAdminReceiver";
+    private static final String TRANSFER_OWNER_INCOMING_TEST_RECEIVER_NO_METADATA_CLASS =
+            "com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest"
+                    + "$BasicAdminReceiverNoMetadata";
+    private static final String ARE_PARAMETERS_SAVED = "ARE_PARAMETERS_SAVED";
+    static final ComponentName INCOMING_COMPONENT_NAME =
+            new ComponentName(
+                    TRANSFER_OWNER_INCOMING_PKG, TRANSFER_OWNER_INCOMING_TEST_RECEIVER_CLASS);
+    static final ComponentName INCOMING_NO_METADATA_COMPONENT_NAME =
+            new ComponentName(TRANSFER_OWNER_INCOMING_PKG,
+                    TRANSFER_OWNER_INCOMING_TEST_RECEIVER_NO_METADATA_CLASS);
+    private static final ComponentName INVALID_TARGET_COMPONENT =
+            new ComponentName("com.android.cts.intent.receiver", ".BroadcastIntentReceiver");
+    private final static String SHARED_PREFERENCE_NAME = "shared-preference-name";
+    static final String KEY_TRANSFER_AFFILIATED_PROFILE_COMPLETED_CALLED =
+            "key-transfer-affiliated-completed-called";
+
+    protected DevicePolicyManager mDevicePolicyManager;
+    protected ComponentName mOutgoingComponentName;
+    protected Context mContext;
+    private String mOwnerChangedBroadcastAction;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mOutgoingComponentName = new ComponentName(mContext, BasicAdminReceiver.class.getName());
+    }
+
+    protected final void setupTestParameters(String ownerChangedBroadcastAction) {
+        mOwnerChangedBroadcastAction = ownerChangedBroadcastAction;
+    }
+
+    @Test
+    public void testTransferSameAdmin() {
+        PersistableBundle b = new PersistableBundle();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    mDevicePolicyManager.transferOwnership(
+                            mOutgoingComponentName, mOutgoingComponentName, b);
+                });
+    }
+
+    @Test
+    public void testTransferInvalidTarget() {
+        PersistableBundle b = new PersistableBundle();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    mDevicePolicyManager.transferOwnership(mOutgoingComponentName,
+                            INVALID_TARGET_COMPONENT, b);
+                });
+    }
+
+    @Test
+    public void testTransferOwnerChangedBroadcast() throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+                mOwnerChangedBroadcastAction);
+        try {
+            receiver.register();
+            PersistableBundle b = new PersistableBundle();
+            mDevicePolicyManager.transferOwnership(mOutgoingComponentName,
+                    INCOMING_COMPONENT_NAME, b);
+            Intent intent = receiver.awaitForBroadcast();
+            assertNotNull(intent);
+        } finally {
+            receiver.unregisterQuietly();
+        }
+    }
+
+    @Test
+    public void testTransferOwner() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransferOwnershipNullBundle() throws Throwable {
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName,
+                INCOMING_COMPONENT_NAME, null);
+    }
+
+    @Test
+    public void testTransferNoMetadata() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    mDevicePolicyManager.transferOwnership(mOutgoingComponentName,
+                            INCOMING_NO_METADATA_COMPONENT_NAME, b);
+                });
+    }
+
+    @Test
+    public void testClearDisallowAddManagedProfileRestriction() {
+        setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false);
+    }
+
+    @Test
+    public void testAddDisallowAddManagedProfileRestriction() {
+        setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true);
+    }
+
+    @Test
+    public void testSetAffiliationId1() {
+        setAffiliationId("id.number.1");
+    }
+
+    @Test
+    public void testTransferOwnershipBundleSaved() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ARE_PARAMETERS_SAVED, true);
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testGetTransferOwnershipBundleOnlyCalledFromAdmin() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ARE_PARAMETERS_SAVED, true);
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+        assertThrows(SecurityException.class, mDevicePolicyManager::getTransferOwnershipBundle);
+    }
+
+    @Test
+    public void testIsBundleNullNoTransfer() throws Throwable {
+        assertNull(mDevicePolicyManager.getTransferOwnershipBundle());
+    }
+
+    private void setUserRestriction(String restriction, boolean add) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (add) {
+            dpm.addUserRestriction(mOutgoingComponentName, restriction);
+        } else {
+            dpm.clearUserRestriction(mOutgoingComponentName, restriction);
+        }
+    }
+
+    private void setAffiliationId(String id) {
+        ComponentName admin = mOutgoingComponentName;
+        DevicePolicyManager dpm = (DevicePolicyManager)
+                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        Set<String> ids = Collections.singleton(id);
+        dpm.setAffiliationIds(admin, ids);
+        assertEquals(ids, dpm.getAffiliationIds(admin));
+    }
+
+    private static SharedPreferences getPrefs(Context context) {
+        return context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
+    }
+
+    private static void putBooleanPref(Context context, String key, boolean value) {
+        getPrefs(context).edit().putBoolean(key, value).apply();
+    }
+
+    protected static boolean getBooleanPref(Context context, String key) {
+        return getPrefs(context).getBoolean(key, false);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
new file mode 100644
index 0000000..c7f6b49
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SystemUpdatePolicy;
+import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class TransferDeviceOwnerOutgoingTest extends DeviceAndProfileOwnerTransferOutgoingTest {
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setupTestParameters(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
+    }
+
+    @Test
+    public void testTransferWithPoliciesOutgoing() throws Throwable {
+        int passwordLength = 123;
+        mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+        mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
+        mDevicePolicyManager.setKeepUninstalledPackages(mOutgoingComponentName,
+                Collections.singletonList("test.package"));
+        mDevicePolicyManager.setSystemUpdatePolicy(mOutgoingComponentName,
+                SystemUpdatePolicy.createWindowedInstallPolicy(123, 456));
+
+        PersistableBundle b = new PersistableBundle();
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransfer() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+        assertTrue(mDevicePolicyManager.isAdminActive(INCOMING_COMPONENT_NAME));
+        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(INCOMING_COMPONENT_NAME.getPackageName()));
+        assertFalse(
+                mDevicePolicyManager.isDeviceOwnerApp(mOutgoingComponentName.getPackageName()));
+        assertFalse(mDevicePolicyManager.isAdminActive(mOutgoingComponentName));
+        assertThrows(SecurityException.class, () -> {
+            mDevicePolicyManager.getSecondaryUsers(mOutgoingComponentName);
+        });
+    }
+
+    @Test
+    public void testTransferAffiliatedProfileOwnershipCompleteCallbackIsCalled() {
+        assertTrue(getBooleanPref(mContext, KEY_TRANSFER_AFFILIATED_PROFILE_COMPLETED_CALLED));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
new file mode 100644
index 0000000..fa62205
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TransferProfileOwnerOutgoingTest extends DeviceAndProfileOwnerTransferOutgoingTest {
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setupTestParameters(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
+    }
+
+    @Test
+    public void testTransferWithPoliciesOutgoing() throws Throwable {
+        int passwordLength = 123;
+        int passwordExpirationTimeout = 456;
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(mOutgoingComponentName);
+        mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+        mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
+        mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName, true);
+        parentDevicePolicyManager.setPasswordExpirationTimeout(
+                mOutgoingComponentName, passwordExpirationTimeout);
+
+        PersistableBundle b = new PersistableBundle();
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransfer() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        mDevicePolicyManager.transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+        assertTrue(mDevicePolicyManager.isAdminActive(INCOMING_COMPONENT_NAME));
+        assertTrue(mDevicePolicyManager.isProfileOwnerApp(INCOMING_COMPONENT_NAME.getPackageName()));
+        assertFalse(
+                mDevicePolicyManager.isProfileOwnerApp(mOutgoingComponentName.getPackageName()));
+        assertFalse(mDevicePolicyManager.isAdminActive(mOutgoingComponentName));
+        assertThrows(SecurityException.class, () -> {
+            mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName,
+                    false);
+        });
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
index 1b98259..cd3f9b7 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name=".WifiConfigCreatorActivity"
             android:exported="true"
             android:theme="@android:style/Theme.NoDisplay"
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index 591a5be..4425dcd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -90,38 +90,23 @@
         runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
     }
 
-    private void clearPasswordForDeviceOwner() throws Exception {
-        runTests(getDeviceAdminApkPackage(), "ClearPasswordTest");
-    }
-
-    private void makeDoAndClearPassword() throws Exception {
-        // Clear the password.  We do it by promoting the DA to DO.
-        setDeviceOwner(getAdminReceiverComponent(), mUserId, /*expectFailure*/ false);
-        try {
-            clearPasswordForDeviceOwner();
-        } finally {
-            assertTrue("Failed to clear device owner",
-                    removeAdmin(getAdminReceiverComponent(), mUserId));
-            // Clearing DO removes the DA too, so we need to set it again.
-            setDeviceAdmin(getAdminReceiverComponent(), mUserId);
-        }
-    }
-
     public void testResetPassword_nycRestrictions() throws Exception {
         if (!mHasFeature) {
             return;
         }
 
-        // If there's a password, clear it.
-        makeDoAndClearPassword();
         try {
             runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
                             "testResetPassword_nycRestrictions");
         } finally {
-            makeDoAndClearPassword();
+            changeUserCredential(null, "1234", 0);
         }
     }
 
+    private void clearPasswordForDeviceOwner() throws Exception {
+        runTests(getDeviceAdminApkPackage(), "ClearPasswordTest");
+    }
+
     /**
      * Run the tests in DeviceOwnerPasswordTest.java (as device owner).
      */
@@ -131,9 +116,6 @@
         }
 
         setDeviceOwner(getAdminReceiverComponent(), mUserId, /*expectFailure*/ false);
-
-        clearPasswordForDeviceOwner();
-
         try {
             runTests(getDeviceAdminApkPackage(), "DeviceOwnerPasswordTest");
         } finally {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index a612cee..6beff2a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -18,19 +18,26 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
+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 com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.TarUtil;
 
+import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -48,6 +55,14 @@
  */
 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
 
+    @Option(
+            name = "skip-device-admin-feature-check",
+            description = "Flag that allows to skip the check for android.software.device_admin "
+                + "and run the tests no matter what. This is useful for system that do not what "
+                + "to expose that feature publicly."
+    )
+    private boolean mSkipDeviceAdminFeatureCheck = false;
+
     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
 
     protected static final int USER_SYSTEM = 0; // From the UserHandle class.
@@ -116,8 +131,10 @@
     protected void setUp() throws Exception {
         super.setUp();
         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
-        mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
-                && hasDeviceFeature("android.software.device_admin");
+        mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */
+        if (!mSkipDeviceAdminFeatureCheck) {
+            mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin");
+        }
         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
         mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
         mFixedPackages = getDevice().getInstalledPackageNames();
@@ -193,6 +210,10 @@
         return getDevice().getMaxNumberOfUsersSupported();
     }
 
+    protected int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
+        return getDevice().getMaxNumberOfRunningUsersSupported();
+    }
+
     protected int getUserFlags(int userId) throws DeviceNotAvailableException {
         String command = "pm list users";
         String commandOutput = getDevice().executeShellCommand(command);
@@ -219,6 +240,16 @@
         return getDevice().listUsers();
     }
 
+    protected  ArrayList<Integer> listRunningUsers() throws DeviceNotAvailableException {
+        ArrayList<Integer> runningUsers = new ArrayList<>();
+        for (int userId : listUsers()) {
+            if (getDevice().isUserRunning(userId)) {
+                runningUsers.add(userId);
+            }
+        }
+        return runningUsers;
+    }
+
     protected int getFirstManagedProfileUserId() throws DeviceNotAvailableException {
         for (int userId : listUsers()) {
             if ((getUserFlags(userId) & FLAG_MANAGED_PROFILE) != 0) {
@@ -229,16 +260,21 @@
         return 0;
     }
 
+    private void stopUserAsync(int userId) throws Exception {
+        String stopUserCommand = "am stop-user -f " + userId;
+        CLog.d("starting command \"" + stopUserCommand);
+        CLog.d("Output for command " + stopUserCommand + ": "
+                + getDevice().executeShellCommand(stopUserCommand));
+    }
+
     protected void stopUser(int userId) throws Exception {
-        // Wait for the broadcast queue to be idle first to workaround the stop-user timeout issue.
-        waitForBroadcastIdle();
         String stopUserCommand = "am stop-user -w -f " + userId;
         CLog.d("starting command \"" + stopUserCommand + "\" and waiting.");
         CLog.d("Output for command " + stopUserCommand + ": "
                 + getDevice().executeShellCommand(stopUserCommand));
     }
 
-    private void waitForBroadcastIdle() throws DeviceNotAvailableException {
+    protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException {
         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
         try {
             // we allow 8min for the command to complete and 4min for the command to start to
@@ -249,6 +285,24 @@
             String output = receiver.getOutput();
             CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
             if (!output.contains("All broadcast queues are idle!")) {
+                // Gather the system_server dump data for investigation.
+                File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof");
+                if (heapDump != null) {
+                    // If file is too too big, tar if with TarUtil.
+                    String pid = getDevice().getProcessPid("system_server");
+                    // gzip the file it's quite big
+                    File heapDumpGz = TarUtil.gzip(heapDump);
+                    try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) {
+                        addTestLog(
+                                String.format("system_server_dump.%s.%s.hprof",
+                                        pid, getDevice().getDeviceDate()),
+                                LogDataType.GZIP, source);
+                    } finally {
+                        FileUtil.deleteFile(heapDump);
+                    }
+                } else {
+                    CLog.e("Failed to capture the dumpheap from system_server");
+                }
                 // the call most likely failed we should fail the test
                 fail("'am wait-for-broadcase-idle' did not complete.");
                 // TODO: consider adding a reboot or recovery before failing if necessary
@@ -263,12 +317,24 @@
             String stopUserCommand = "am stop-user -w -f " + userId;
             CLog.d("stopping and removing user " + userId);
             getDevice().executeShellCommand(stopUserCommand);
-            assertTrue("Couldn't remove user", getDevice().removeUser(userId));
+            // Ephemeral users may have already been removed after being stopped.
+            if (listUsers().contains(userId)) {
+                assertTrue("Couldn't remove user", getDevice().removeUser(userId));
+            }
         }
     }
 
     protected void removeTestUsers() throws Exception {
-        for (int userId : getUsersCreatedByTests()) {
+        List<Integer> usersCreatedByTests = getUsersCreatedByTests();
+
+        // The time spent on stopUser is depend on how busy the broadcast queue is.
+        // To optimize the time to remove multiple test users, we mark all users as
+        // stopping first, so no more broadcasts will be sent to these users, which make the queue
+        // less busy.
+        for (int userId : usersCreatedByTests) {
+            stopUserAsync(userId);
+        }
+        for (int userId : usersCreatedByTests) {
             removeUser(userId);
         }
     }
@@ -350,7 +416,7 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                     result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
@@ -398,6 +464,12 @@
         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
     }
 
+    /** Checks whether it is possible to start the desired number of users. */
+    protected boolean canStartAdditionalUsers(int numberOfUsers)
+            throws DeviceNotAvailableException {
+        return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
+    }
+
     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
         if (mAvailableFeatures == null) {
             // TODO: Move this logic to ITestDevice.
@@ -807,4 +879,28 @@
         executeShellCommand("input keyevent KEYCODE_WAKEUP");
         executeShellCommand("wm dismiss-keyguard");
     }
+
+    protected void startActivityAsUser(int userId, String packageName, String activityName)
+        throws Exception {
+        wakeupAndDismissKeyguard();
+        String command = "am start -W --user " + userId + " " + packageName + "/" + activityName;
+        getDevice().executeShellCommand(command);
+    }
+
+    protected String getDefaultLauncher() throws Exception {
+        final String PREFIX = "Launcher: ComponentInfo{";
+        final String POSTFIX = "}";
+        final String commandOutput =
+                getDevice().executeShellCommand("cmd shortcut get-default-launcher");
+        if (commandOutput == null) {
+            return null;
+        }
+        String[] lines = commandOutput.split("\\r?\\n");
+        for (String line : lines) {
+            if (line.startsWith(PREFIX) && line.endsWith(POSTFIX)) {
+                return line.substring(PREFIX.length(), line.length() - POSTFIX.length());
+            }
+        }
+        throw new Exception("Default launcher not found");
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
new file mode 100644
index 0000000..8a6f4da
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -0,0 +1,109 @@
+package com.android.cts.devicepolicy;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.io.FileNotFoundException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * In the test, managed profile and secondary user are created. We then verify
+ * {@link android.content.pm.crossprofile.CrossProfileApps} APIs in different directions, like
+ * primary user to managed profile.
+ */
+public class CrossProfileAppsHostSideTest extends BaseDevicePolicyTest {
+    private static final String TEST_PACKAGE = "com.android.cts.crossprofileappstest";
+    private static final String NON_TARGET_USER_TEST_CLASS = ".CrossProfileAppsNonTargetUserTest";
+    private static final String TARGET_USER_TEST_CLASS = ".CrossProfileAppsTargetUserTest";
+    private static final String PARAM_TARGET_USER = "TARGET_USER";
+    private static final String EXTRA_TEST_APK = "CtsCrossProfileAppsTests.apk";
+    private static final String SIMPLE_APP_APK ="CtsSimpleApp.apk";
+
+    private int mProfileId;
+    private int mSecondaryUserId;
+    private boolean mHasManagedUserFeature;
+
+    @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.
+        mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
+        installRequiredApps(mPrimaryUserId);
+
+        if (mHasManagedUserFeature) {
+            createAndStartManagedProfile();
+            installRequiredApps(mProfileId);
+        }
+        if (mSupportsMultiUser) {
+            mSecondaryUserId = createUser();
+            installRequiredApps(mSecondaryUserId);
+        }
+    }
+
+    private void installRequiredApps(int userId)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        installAppAsUser(EXTRA_TEST_APK, userId);
+        installAppAsUser(SIMPLE_APP_APK, userId);
+    }
+
+    public void testPrimaryUserToPrimaryUser() throws Exception {
+        verifyCrossProfileAppsApi(mPrimaryUserId, mPrimaryUserId, NON_TARGET_USER_TEST_CLASS);
+    }
+
+    public void testPrimaryUserToManagedProfile() throws Exception {
+        if (!mHasManagedUserFeature) {
+            return;
+        }
+        verifyCrossProfileAppsApi(mPrimaryUserId, mProfileId, TARGET_USER_TEST_CLASS);
+    }
+
+    public void testManagedProfileToPrimaryUser() throws Exception {
+        if (!mHasManagedUserFeature) {
+            return;
+        }
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, TARGET_USER_TEST_CLASS);
+    }
+
+    public void testPrimaryUserToSecondaryUser() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        verifyCrossProfileAppsApi(mPrimaryUserId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
+    }
+
+    public void testSecondaryUserToManagedProfile() throws Exception {
+        if (!mSupportsMultiUser || !mHasManagedUserFeature) {
+            return;
+        }
+        verifyCrossProfileAppsApi(mSecondaryUserId, mProfileId, NON_TARGET_USER_TEST_CLASS);
+
+    }
+
+    public void testManagedProfileToSecondaryUser() throws Exception {
+        if (!mSupportsMultiUser || !mHasManagedUserFeature) {
+            return;
+        }
+        verifyCrossProfileAppsApi(mProfileId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
+    }
+
+    private void verifyCrossProfileAppsApi(int fromUserId, int targetUserId, String testClass)
+            throws Exception {
+        runDeviceTestsAsUser(
+                TEST_PACKAGE,
+                testClass,
+                null,
+                fromUserId,
+                createTargetUserParam(targetUserId));
+    }
+
+    private void createAndStartManagedProfile() throws Exception {
+        mProfileId = createManagedProfile(mPrimaryUserId);
+        switchUser(mPrimaryUserId);
+        startUser(mProfileId);
+    }
+
+    private Map<String, String> createTargetUserParam(int targetUserId) throws Exception {
+        return Collections.singletonMap(PARAM_TARGET_USER,
+                Integer.toString(getUserSerialNumber(targetUserId)));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index 4042fd5..7b568af 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -31,7 +31,7 @@
     private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
     private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
     private static final String DEVICE_OWNER_ADMIN
-            = DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
+            = DEVICE_OWNER_PKG + ".BasicAdminReceiver";
     private static final String DEVICE_OWNER_ADMIN_COMPONENT
             = DEVICE_OWNER_PKG + "/" + DEVICE_OWNER_ADMIN;
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
new file mode 100644
index 0000000..3dfb850
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -0,0 +1,199 @@
+package com.android.cts.devicepolicy;
+
+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";
+    protected static final String TRANSFER_OWNER_OUTGOING_TEST_RECEIVER =
+            TRANSFER_OWNER_OUTGOING_PKG
+                    + "/com.android.cts.transferowner"
+                    + ".DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver";
+    protected static final String TRANSFER_OWNER_INCOMING_PKG =
+            "com.android.cts.transferownerincoming";
+    protected static final String TRANSFER_OWNER_INCOMING_APK = "CtsTransferOwnerIncomingApp.apk";
+    protected static final String INVALID_TARGET_APK = "CtsIntentReceiverApp.apk";
+
+    protected int mUserId;
+    protected String mOutgoingTestClassName;
+    protected String mIncomingTestClassName;
+
+    public void testTransfer() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransfer", mUserId);
+    }
+
+    public void testTransferSameAdmin() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferSameAdmin", mUserId);
+    }
+
+    public void testTransferInvalidTarget() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(INVALID_TARGET_APK, mUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferInvalidTarget", mUserId);
+    }
+
+    public void testTransferPolicies() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferWithPoliciesOutgoing", mUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+                mIncomingTestClassName,
+                "testTransferPoliciesAreRetainedAfterTransfer", mUserId);
+    }
+
+    public void testTransferOwnerChangedBroadcast() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwnerChangedBroadcast", mUserId);
+    }
+
+    public void testTransferCompleteCallback() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwner", mUserId);
+
+        waitForBroadcastIdle();
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+                mIncomingTestClassName,
+                "testTransferCompleteCallbackIsCalled", mUserId);
+    }
+
+    protected void setupTestParameters(int userId, String outgoingTestClassName,
+            String incomingTestClassName) {
+        mUserId = userId;
+        mOutgoingTestClassName = outgoingTestClassName;
+        mIncomingTestClassName = incomingTestClassName;
+    }
+
+    public void testTransferNoMetadata() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferNoMetadata", mUserId);
+    }
+
+    public void testIsTransferBundlePersisted() throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwnershipBundleSaved", mUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+                mIncomingTestClassName,
+                "testTransferOwnershipBundleLoaded", mUserId);
+    }
+
+    public void testGetTransferOwnershipBundleOnlyCalledFromAdmin()
+            throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testGetTransferOwnershipBundleOnlyCalledFromAdmin", mUserId);
+    }
+
+    public void testBundleEmptyAfterTransferWithNullBundle() throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwnershipNullBundle", mUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+                mIncomingTestClassName,
+                "testTransferOwnershipEmptyBundleLoaded", mUserId);
+    }
+
+    public void testIsBundleNullNoTransfer() throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testIsBundleNullNoTransfer", mUserId);
+    }
+
+    protected int setupManagedProfileOnDeviceOwner(String apkName, String adminReceiverClassName)
+            throws Exception {
+        // Temporary disable the DISALLOW_ADD_MANAGED_PROFILE, so that we can create profile
+        // using adb command.
+        clearDisallowAddManagedProfileRestriction();
+        try {
+            return setupManagedProfile(apkName, adminReceiverClassName);
+        } finally {
+            // Adding back DISALLOW_ADD_MANAGED_PROFILE.
+            addDisallowAddManagedProfileRestriction();
+        }
+    }
+
+    protected int setupManagedProfile(String apkName, String adminReceiverClassName)
+            throws Exception {
+        final int userId = createManagedProfile(mPrimaryUserId);
+        installAppAsUser(apkName, userId);
+        if (!setProfileOwner(adminReceiverClassName, userId, false)) {
+            removeAdmin(TRANSFER_OWNER_OUTGOING_TEST_RECEIVER, userId);
+            getDevice().uninstallPackage(TRANSFER_OWNER_OUTGOING_PKG);
+            fail("Failed to set device owner");
+            return -1;
+        }
+        startUser(userId);
+        return userId;
+    }
+
+    /**
+     * Clear {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
+     */
+    private void clearDisallowAddManagedProfileRestriction() throws Exception {
+        runDeviceTestsAsUser(
+                TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testClearDisallowAddManagedProfileRestriction",
+                mPrimaryUserId);
+    }
+
+    /**
+     * Add {@link android.os.UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
+     */
+    private void addDisallowAddManagedProfileRestriction() throws Exception {
+        runDeviceTestsAsUser(
+                TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testAddDisallowAddManagedProfileRestriction",
+                mPrimaryUserId);
+    }
+
+    /* TODO: Add tests for:
+    * 1. startServiceForOwner
+    * 2. passwordOwner
+    *
+    * */
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index d5aadd0..8609684 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -107,6 +107,13 @@
     protected static final String ASSIST_APP_PKG = "com.android.cts.devicepolicy.assistapp";
     protected static final String ASSIST_APP_APK = "CtsDevicePolicyAssistApp.apk";
 
+    private static final String PRINTING_APP_PKG = "com.android.cts.devicepolicy.printingapp";
+    private static final String PRINTING_APP_APK = "CtsDevicePolicyPrintingApp.apk";
+
+    private static final String METERED_DATA_APP_PKG
+            = "com.android.cts.devicepolicy.meteredtestapp";
+    private static final String METERED_DATA_APP_APK = "CtsMeteredDataTestApp.apk";
+
     private static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
             = "enabled_notification_policy_access_packages";
 
@@ -143,7 +150,9 @@
             getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
             getDevice().uninstallPackage(INTENT_SENDER_PKG);
             getDevice().uninstallPackage(CUSTOMIZATION_APP_PKG);
-            getDevice().uninstallPackage(AUTOFILL_APP_APK);
+            getDevice().uninstallPackage(AUTOFILL_APP_PKG);
+            getDevice().uninstallPackage(PRINTING_APP_PKG);
+            getDevice().uninstallPackage(METERED_DATA_APP_PKG);
             getDevice().uninstallPackage(TEST_APP_PKG);
 
             // Press the HOME key to close any alart dialog that may be shown.
@@ -261,6 +270,15 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
     }
 
+    public void testPermissionGrant_developmentPermission() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(
+                ".PermissionsTest", "testPermissionGrantState_developmentPermission");
+    }
+
     /**
      * Require a device for tests that use the network stack. Headless Androids running in
      * data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
@@ -501,7 +519,7 @@
         }
 
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        executeDeviceTestClass(".DpcAllowedAccountManagementTest");
+        executeDeviceTestClass(".AllowedAccountManagementTest");
     }
 
     public void testAccountManagement_userRestrictionAddAccount() throws Exception {
@@ -642,6 +660,15 @@
                 "testDisallowAutofill_allowed");
     }
 
+    public void testSetMeteredDataDisabled() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(METERED_DATA_APP_APK, mUserId);
+
+        executeDeviceTestClass(".MeteredDataRestrictionTest");
+    }
+
     public void testPackageInstallUserRestrictions() throws Exception {
         if (!mHasFeature) {
             return;
@@ -782,6 +809,13 @@
 
     }
 
+    public void testPasswordBlacklist() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceTestClass(".PasswordBlacklistTest");
+    }
+
     public void testRequiredStrongAuthTimeout() throws Exception {
         if (!mHasFeature) {
             return;
@@ -822,6 +856,44 @@
                 Collections.singletonMap(ARG_ALLOW_FAILURE, Boolean.toString(allowFailures)));
     }
 
+    public void testClearApplicationData_testPkg() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(INTENT_RECEIVER_APK, mUserId);
+        runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+                "testWriteToSharedPreference", mUserId);
+        executeDeviceTestMethod(".ClearApplicationDataTest", "testClearApplicationData_testPkg");
+        runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+                "testSharedPreferenceCleared", mUserId);
+    }
+
+    public void testClearApplicationData_deviceProvisioning() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Clearing data of device configuration app should fail
+        executeDeviceTestMethod(".ClearApplicationDataTest",
+                "testClearApplicationData_deviceProvisioning");
+    }
+
+    public void testClearApplicationData_activeAdmin() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Clearing data of active admin should fail
+        executeDeviceTestMethod(".ClearApplicationDataTest",
+                "testClearApplicationData_activeAdmin");
+    }
+
+    public void testPrintingPolicy() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PRINTING_APP_APK, mUserId);
+        executeDeviceTestClass(".PrintingPolicyTest");
+    }
+
     protected void executeDeviceTestClass(String className) throws Exception {
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
     }
@@ -928,10 +1000,7 @@
      */
     protected void startSimpleActivityAsUser(int userId) throws Exception {
         installAppAsUser(TEST_APP_APK, userId);
-        wakeupAndDismissKeyguard();
-        String command = "am start -W --user " + userId + " " + TEST_APP_PKG + "/"
-                + TEST_APP_PKG + ".SimpleActivity";
-        getDevice().executeShellCommand(command);
+        startActivityAsUser(userId, TEST_APP_PKG, TEST_APP_PKG + ".SimpleActivity");
     }
 
     protected void setScreenCaptureDisabled(int userId, boolean disabled) throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index 8d1c040..ab81ef6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -423,6 +423,46 @@
                 mPrimaryUserId);
     }
 
+    public void testCannotStartManagedProfileInBackground() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
+
+        runDeviceTestsAsUser(
+                COMP_DPC_PKG,
+                MANAGEMENT_TEST,
+                "testCannotStartManagedProfileInBackground",
+                mPrimaryUserId);
+    }
+
+    public void testCannotStopManagedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
+
+        runDeviceTestsAsUser(
+                COMP_DPC_PKG,
+                MANAGEMENT_TEST,
+                "testCannotStopManagedProfile",
+                mPrimaryUserId);
+    }
+
+    public void testCannotLogoutManagedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
+        setSameAffiliationId(profileUserId);
+
+        runDeviceTestsAsUser(
+                COMP_DPC_PKG,
+                MANAGEMENT_TEST,
+                "testCannotLogoutManagedProfile",
+                profileUserId);
+    }
+
     private void verifyBindDeviceAdminServiceAsUser(int profileOwnerUserId) throws Exception {
         // Installing a non managing app (neither device owner nor profile owner).
         installAppAsUser(COMP_DPC_APK2, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7a07d7c..6c3b0b8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,8 +16,13 @@
 
 package com.android.cts.devicepolicy;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.io.File;
+import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
 
 /**
  * Set of tests for Device Owner use cases.
@@ -42,18 +47,30 @@
     private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
-            DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
+            DEVICE_OWNER_PKG + ".BasicAdminReceiver";
     private static final String DEVICE_OWNER_COMPONENT = DEVICE_OWNER_PKG + "/"
             + ADMIN_RECEIVER_TEST_CLASS;
 
-    /** The ephemeral users are implemented and supported on the device. */
-    private boolean mHasEphemeralUserFeature;
+    private static final String TEST_APP_APK = "CtsEmptyTestApp.apk";
+    private static final String TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+    private static final String TEST_APP_LOCATION = "/data/local/tmp/cts/packageinstaller/";
+
+    private static final String ARG_SECURITY_LOGGING_BATCH_NUMBER = "batchNumber";
+    private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
+
+    private static final String ARG_NETWORK_LOGGING_BATCH_COUNT = "batchCount";
+
+    /** CreateAndManageUser is available and an additional user can be created. */
+    private boolean mHasCreateAndManageUserFeature;
+
+    /** Forcing ephemeral users is implemented and supported on the device. */
+    private boolean mHasForceEphemeralUserFeature;
 
     /**
-     * Ephemeral users are implemented, but unsupported on the device (because of missing split
-     * system user).
+     * Force ephemeral users feature is implemented, but unsupported on the device (because of
+     * missing split system user).
      */
-    private boolean mHasDisabledEphemeralUserFeature;
+    private boolean mHasDisabledForceEphemeralUserFeature;
 
     @Override
     protected void setUp() throws Exception {
@@ -67,9 +84,12 @@
                 fail("Failed to set device owner");
             }
         }
-        mHasEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1) && hasUserSplit();
-        mHasDisabledEphemeralUserFeature =
-                mHasFeature && canCreateAdditionalUsers(1) && !hasUserSplit();
+        mHasCreateAndManageUserFeature = mHasFeature && canCreateAdditionalUsers(1)
+                && hasDeviceFeature("android.software.managed_users");
+        mHasForceEphemeralUserFeature = mHasCreateAndManageUserFeature
+                && hasUserSplit();
+        mHasDisabledForceEphemeralUserFeature = mHasCreateAndManageUserFeature
+                && !hasUserSplit();
     }
 
     @Override
@@ -125,7 +145,7 @@
 
     /** Tries to toggle the force-ephemeral-users on and checks it was really set. */
     public void testSetForceEphemeralUsers() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+        if (!mHasForceEphemeralUserFeature) {
             return;
         }
         // Set force-ephemeral-users policy and verify it was set.
@@ -136,7 +156,7 @@
      * Setting force-ephemeral-users policy to true without a split system user should fail.
      */
     public void testSetForceEphemeralUsersFailsWithoutSplitSystemUser() throws Exception {
-        if (mHasDisabledEphemeralUserFeature) {
+        if (mHasDisabledForceEphemeralUserFeature) {
             executeDeviceTestMethod(".ForceEphemeralUsersTest", "testSetForceEphemeralUsersFails");
         }
     }
@@ -148,7 +168,7 @@
      * <p>If the current user is the system user, the other users are removed straight away.
      */
     public void testRemoveUsersOnSetForceEphemeralUsers() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+        if (!mHasForceEphemeralUserFeature) {
             return;
         }
 
@@ -171,7 +191,7 @@
      * before all other users are removed.
      */
     public void testRemoveUsersOnSetForceEphemeralUsersWithUserSwitch() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+        if (!mHasForceEphemeralUserFeature) {
             return;
         }
 
@@ -206,7 +226,7 @@
 
     /** The users created after setting force-ephemeral-users policy to true must be ephemeral. */
     public void testCreateUserAfterSetForceEphemeralUsers() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+        if (!mHasForceEphemeralUserFeature) {
             return;
         }
 
@@ -217,15 +237,191 @@
         assertTrue("User must be ephemeral", 0 != (getUserFlags(userId) & FLAG_EPHEMERAL));
     }
 
-    /**
-     * Test creating an epehemeral user using the DevicePolicyManager's createAndManageUser method.
-     */
-    public void testCreateAndManageEphemeralUser() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+    public void testCreateAndManageUser_LowStorage() throws Exception {
+        if (!mHasCreateAndManageUserFeature) {
             return;
         }
 
-        executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageEphemeralUser");
+        try {
+            // Force low storage
+            getDevice().setSetting("global", "sys_storage_threshold_percentage", "100");
+            getDevice().setSetting("global", "sys_storage_threshold_max_bytes",
+                    String.valueOf(Long.MAX_VALUE));
+
+            // The next createAndManageUser should return USER_OPERATION_ERROR_LOW_STORAGE.
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser_LowStorage");
+        } finally {
+            getDevice().executeShellCommand(
+                    "settings delete global sys_storage_threshold_percentage");
+            getDevice().executeShellCommand(
+                    "settings delete global sys_storage_threshold_max_bytes");
+        }
+    }
+
+    public void testCreateAndManageUser_MaxUsers() throws Exception {
+        if (!mHasCreateAndManageUserFeature) {
+            return;
+        }
+
+        int maxUsers = getDevice().getMaxNumberOfUsersSupported();
+        // Primary user is already there, so we can create up to maxUsers -1.
+        for (int i = 0; i < maxUsers - 1; i++) {
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser");
+        }
+        // The next createAndManageUser should return USER_OPERATION_ERROR_MAX_USERS.
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_MaxUsers");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser.
+     * {@link android.app.admin.DevicePolicyManager#getSecondaryUsers} is tested.
+     */
+    public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+        if (!mHasCreateAndManageUserFeature) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_GetSecondaryUsers");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method and switch
+     * to the user.
+     * {@link android.app.admin.DevicePolicyManager#switchUser} is tested.
+     */
+    public void testCreateAndManageUser_SwitchUser() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_SwitchUser");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method and switch
+     * to the user to test stop user while target user is in foreground.
+     * {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
+     */
+    public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_CannotStopCurrentUser");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method and start
+     * the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
+     */
+    public void testCreateAndManageUser_StartInBackground() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_StartInBackground");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method and start
+     * the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
+     */
+    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
+        if (!mHasCreateAndManageUserFeature) {
+            return;
+        }
+
+        int maxRunningUsers = getDevice().getMaxNumberOfRunningUsersSupported();
+        // Primary user is already running, so we can start up to maxRunningUsers -1.
+        for (int i = 0; i < maxRunningUsers - 1; i++) {
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser_StartInBackground");
+        }
+        // The next startUserInBackground should return USER_OPERATION_ERROR_MAX_RUNNING_USERS.
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_StartInBackground_MaxRunningUsers");
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method and start
+     * the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
+     */
+    public void testCreateAndManageUser_StopUser() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_StopUser");
+        assertNewUserStopped();
+    }
+
+    /**
+     * Test creating an ephemeral user using the DevicePolicyManager's createAndManageUser method
+     * and start the user in background, user is then stopped. The user should be removed
+     * automatically even when DISALLOW_REMOVE_USER is set.
+     */
+    public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser");
+        assertEquals(0, getUsersCreatedByTests().size());
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method, affiliate
+     * the user and start the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#logoutUser} is tested.
+     */
+    public void testCreateAndManageUser_LogoutUser() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_LogoutUser");
+        assertNewUserStopped();
+    }
+
+    /**
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method, affiliate
+     * the user and start the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#isAffiliatedUser} is tested.
+     */
+    public void testCreateAndManageUser_Affiliated() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_Affiliated");
+    }
+
+    /**
+     * Test creating an ephemeral user using the DevicePolicyManager's createAndManageUser method,
+     * affiliate the user and start the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#isEphemeralUser} is tested.
+     */
+    public void testCreateAndManageUser_Ephemeral() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_Ephemeral");
 
         List<Integer> newUsers = getUsersCreatedByTests();
         assertEquals(1, newUsers.size());
@@ -237,49 +433,52 @@
     }
 
     /**
-     * Test that creating an epehemeral user using the DevicePolicyManager's createAndManageUser
-     * method fails on systems without the split system user.
+     * Test creating an user using the DevicePolicyManager's createAndManageUser method, affiliate
+     * the user and start the user in background to test APIs on that user.
+     * {@link android.app.admin.DevicePolicyManager#LEAVE_ALL_SYSTEM_APPS_ENABLED} is tested.
      */
-    public void testCreateAndManageEphemeralUserFailsWithoutSplitSystemUser() throws Exception {
-        if (mHasDisabledEphemeralUserFeature) {
-            executeDeviceTestMethod(
-                    ".CreateAndManageUserTest", "testCreateAndManageEphemeralUserFails");
+    public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
+        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_LeaveAllSystemApps");
+    }
+
+
+    public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
+        if (mHasCreateAndManageUserFeature) {
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser_SkipSetupWizard");
         }
     }
 
-// Disabled due to b/29072728
-//    public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
-//        if (mHasCreateAndManageUserFeature) {
-//            executeDeviceTestMethod(".CreateAndManageUserTest",
-//                "testCreateAndManageUser_SkipSetupWizard");
-//        }
-//    }
-//
-//    public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
-//        if (mHasCreateAndManageUserFeature) {
-//            executeDeviceTestMethod(".CreateAndManageUserTest",
-//                "testCreateAndManageUser_DontSkipSetupWizard");
-//        }
-//    }
+    public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
+        if (mHasCreateAndManageUserFeature) {
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser_DontSkipSetupWizard");
+        }
+    }
 
     public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
-        if (mHasFeature && canCreateAdditionalUsers(1)) {
+        if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_AddRestrictionSet");
+                    "testCreateAndManageUser_AddRestrictionSet");
         }
     }
 
     public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
-        if (mHasFeature && canCreateAdditionalUsers(1)) {
+        if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_RemoveRestrictionSet");
+                    "testCreateAndManageUser_RemoveRestrictionSet");
         }
     }
 
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
-        if (mHasFeature && canCreateAdditionalUsers(1)) {
+        if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testUserAddedOrRemovedBroadcasts");
+                    "testUserAddedOrRemovedBroadcasts");
         }
     }
 
@@ -307,19 +506,40 @@
         if (!mHasFeature) {
             return;
         }
+
+        // Turn logging on.
+        executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
+        // Reboot to ensure ro.device_owner is set to true in logd and logging is on.
+        rebootAndWaitUntilReady();
+
+        // Generate various types of events on device side and check that they are logged.
+        executeDeviceTestMethod(".SecurityLoggingTest", "testGenerateLogs");
+        getDevice().executeShellCommand("dpm force-security-logs");
+        executeDeviceTestMethod(".SecurityLoggingTest", "testVerifyGeneratedLogs");
+
+        // Reboot the device, so the security event ids are reset.
+        rebootAndWaitUntilReady();
+
+        // Verify event ids are consistent across a consecutive batch.
+        for (int batchNumber = 0; batchNumber < 3; batchNumber++) {
+            generateDummySecurityLogs();
+            getDevice().executeShellCommand("dpm force-security-logs");
+            executeDeviceTestMethod(".SecurityLoggingTest", "testVerifyLogIds",
+                    Collections.singletonMap(ARG_SECURITY_LOGGING_BATCH_NUMBER,
+                            Integer.toString(batchNumber)));
+        }
+
+        // Immediately attempting to fetch events again should fail.
         executeDeviceTestMethod(".SecurityLoggingTest",
-                "testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval");
-        try {
-            executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
-            rebootAndWaitUntilReady();
-            // Sleep for ~1 minute so that SecurityLogMonitor fetches the security events. This is
-            // an implementation detail we shouldn't rely on but there is no other way to do it
-            // currently.
-            Thread.sleep(TimeUnit.SECONDS.toMillis(70));
-            executeDeviceTestMethod(".SecurityLoggingTest", "testGetSecurityLogs");
-        } finally {
-            // Always attempt to disable security logging to bring the device to initial state.
-            executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
+                "testSecurityLoggingRetrievalRateLimited");
+        // Turn logging off.
+        executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
+    }
+
+    private void generateDummySecurityLogs() throws DeviceNotAvailableException {
+        // Trigger security events of type TAG_ADB_SHELL_CMD.
+        for (int i = 0; i < SECURITY_EVENTS_BATCH_SIZE; i++) {
+            getDevice().executeShellCommand("echo just_testing_" + i);
         }
     }
 
@@ -343,11 +563,34 @@
         if (!mHasFeature) {
             return;
         }
-
         executeDeviceTestMethod(".NetworkLoggingTest", "testProvidingWrongBatchTokenReturnsNull");
-        executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval");
+        executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+                Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
     }
 
+    public void testNetworkLogging_multipleBatches() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+                Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(2)));
+    }
+
+    public void testNetworkLogging_rebootResetsId() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // First batch: retrieve and verify the events.
+        executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+                Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
+        // Reboot the device, so the security event IDs are re-set.
+        rebootAndWaitUntilReady();
+        // First batch after reboot: retrieve and verify the events.
+        executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+                Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
+    }
+
+
     public void testSetAffiliationId_IllegalArgumentException() throws Exception {
         if (!mHasFeature) {
             return;
@@ -433,21 +676,8 @@
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
         }
-
-        final int userId = createUser();
-        installAppAsUser(INTENT_RECEIVER_APK, userId);
-        installAppAsUser(DEVICE_OWNER_APK, userId);
-        setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
-
-        switchUser(userId);
-        wakeupAndDismissKeyguard();
-
-        // Setting the same affiliation ids on both users and running the lock task tests.
-        runDeviceTestsAsUser(
-                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
-        runDeviceTestsAsUser(
-                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
-        runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".LockTaskTest", userId);
+        final int userId = createAffiliatedSecondaryUser();
+        executeAffiliatedProfileOwnerTest("LockTaskTest", userId);
     }
 
     public void testSystemUpdatePolicy() throws Exception {
@@ -503,16 +733,18 @@
 
     // Execute HardwarePropertiesManagerTest as a device owner.
     public void testHardwarePropertiesManagerAsDeviceOwner() throws Exception {
-        if (!mHasFeature)
+        if (!mHasFeature) {
             return;
+        }
 
         executeDeviceTestMethod(".HardwarePropertiesManagerTest", "testHardwarePropertiesManager");
     }
 
     // Execute VrTemperatureTest as a device owner.
     public void testVrTemperaturesAsDeviceOwner() throws Exception {
-        if (!mHasFeature)
+        if (!mHasFeature) {
             return;
+        }
 
         executeDeviceTestMethod(".VrTemperatureTest", "testVrTemperatures");
     }
@@ -555,6 +787,13 @@
         executeDeviceOwnerTest("BluetoothRestrictionTest");
     }
 
+    public void testSetTime() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("SetTimeTest");
+    }
+
     public void testDeviceOwnerProvisioning() throws Exception {
         if (!mHasFeature) {
             return;
@@ -590,7 +829,125 @@
         if (!mHasFeature || !hasBackupService) {
             return;
         }
-        executeDeviceOwnerTest("BackupServiceEnabledTest");
+        executeDeviceTestMethod(".BackupServicePoliciesTest",
+                "testEnablingAndDisablingBackupService");
+    }
+
+    public void testGetAndSetMandatoryBackupTransport() throws Exception {
+        final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
+        // Backups cannot be made mandatory if the backup feature is not supported.
+        if (!mHasFeature || !hasBackupService) {
+            return;
+        }
+        executeDeviceTestMethod(".BackupServicePoliciesTest",
+                "testGetAndSetMandatoryBackupTransport");
+    }
+
+    public void testPackageInstallCache() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final File apk = buildHelper.getTestFile(TEST_APP_APK);
+        try {
+            getDevice().uninstallPackage(TEST_APP_PKG);
+            assertTrue(getDevice().pushFile(apk, TEST_APP_LOCATION + apk.getName()));
+
+            // Install the package in primary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageInstall", mPrimaryUserId);
+
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testKeepPackageCache", mPrimaryUserId);
+
+            // Remove the package in primary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageUninstall", mPrimaryUserId);
+
+            // Should be able to enable the cached package in primary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testInstallExistingPackage", mPrimaryUserId);
+        } finally {
+            String command = "rm " + TEST_APP_LOCATION + apk.getName();
+            getDevice().executeShellCommand(command);
+            getDevice().uninstallPackage(TEST_APP_PKG);
+        }
+    }
+
+    public void testPackageInstallCache_multiUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+            return;
+        }
+        final int userId = createAffiliatedSecondaryUser();
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final File apk = buildHelper.getTestFile(TEST_APP_APK);
+        try {
+            getDevice().uninstallPackage(TEST_APP_PKG);
+            assertTrue(getDevice().pushFile(apk, TEST_APP_LOCATION + apk.getName()));
+
+            // Install the package in primary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageInstall", mPrimaryUserId);
+
+            // Should be able to enable the package in secondary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testInstallExistingPackage", userId);
+
+            // Remove the package in both user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageUninstall", mPrimaryUserId);
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageUninstall", userId);
+
+            // Install the package in secondary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageInstall", userId);
+
+            // Should be able to enable the package in primary user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testInstallExistingPackage", mPrimaryUserId);
+
+            // Keep the package in cache
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testKeepPackageCache", mPrimaryUserId);
+
+            // Remove the package in both user
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageUninstall", mPrimaryUserId);
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testPackageUninstall", userId);
+
+            // Should be able to enable the cached package in both users
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testInstallExistingPackage", userId);
+            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                    "testInstallExistingPackage", mPrimaryUserId);
+        } finally {
+            String command = "rm " + TEST_APP_LOCATION + apk.getName();
+            getDevice().executeShellCommand(command);
+            getDevice().uninstallPackage(TEST_APP_PKG);
+        }
+    }
+
+    public void testAirplaneModeRestriction() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("AirplaneModeRestrictionTest");
+    }
+
+    public void testSetSystemSetting() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("SetSystemSettingTest");
+    }
+
+    public void testOverrideApn() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("OverrideApnTest");
     }
 
     private void executeDeviceOwnerTest(String testClassName) throws Exception {
@@ -601,6 +958,15 @@
         runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, mPrimaryUserId);
     }
 
+    private void executeAffiliatedProfileOwnerTest(String testClassName, int userId)
+            throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        String testClass = DEVICE_OWNER_PKG + "." + testClassName;
+        runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, userId);
+    }
+
     private void executeDeviceTestMethod(String className, String testName) throws Exception {
         if (!mHasFeature) {
             return;
@@ -608,4 +974,38 @@
         runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
                 /* deviceOwnerUserId */ mPrimaryUserId);
     }
-}
\ No newline at end of file
+
+    private int createAffiliatedSecondaryUser() throws Exception {
+        final int userId = createUser();
+        installAppAsUser(INTENT_RECEIVER_APK, userId);
+        installAppAsUser(DEVICE_OWNER_APK, userId);
+        setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+        switchUser(userId);
+        wakeupAndDismissKeyguard();
+
+        // Setting the same affiliation ids on both users and running the lock task tests.
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+        runDeviceTestsAsUser(
+                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+        return userId;
+    }
+
+    private void executeDeviceTestMethod(String className, String testName,
+            Map<String, String> params) throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
+                /* deviceOwnerUserId */ mPrimaryUserId, params);
+    }
+
+    private void assertNewUserStopped() throws Exception {
+        List<Integer> newUsers = getUsersCreatedByTests();
+        assertEquals(1, newUsers.size());
+        int newUserId = newUsers.get(0);
+
+        assertFalse(getDevice().isUserRunning(newUserId));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
index df4a47c..84bdd7d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
@@ -17,7 +17,7 @@
 package com.android.cts.devicepolicy;
 
 /**
- * Tests for emphemeral users and profiles.
+ * Tests for ephemeral users and profiles.
  */
 public class EphemeralUserTest extends BaseDevicePolicyTest {
 
@@ -35,7 +35,7 @@
 
     /** The user should have the ephemeral flag set if it was created as ephemeral. */
     public void testCreateEphemeralUser() throws Exception {
-        if (!mHasFeature || !hasUserSplit()) {
+        if (!mHasFeature) {
             return;
         }
         int userId = createUser(FLAG_EPHEMERAL);
@@ -59,7 +59,8 @@
      */
     public void testProfileInheritsEphemeral() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")
-                || !hasUserSplit() || !canCreateAdditionalUsers(2)) {
+                || !canCreateAdditionalUsers(2)
+                || !hasUserSplit()) {
             return;
         }
         int userId = createUser(FLAG_EPHEMERAL);
@@ -72,7 +73,7 @@
      * Ephemeral user should be automatically removed after it is stopped.
      */
     public void testRemoveEphemeralOnStop() throws Exception {
-        if (!mHasFeature || !hasUserSplit()) {
+        if (!mHasFeature) {
             return;
         }
         int userId = createUser(FLAG_EPHEMERAL);
@@ -87,7 +88,7 @@
      * and not ephemeral when the feature is not set.
      */
     public void testEphemeralGuestFeature() throws Exception {
-        if (!mHasFeature || !hasUserSplit()) {
+        if (!mHasFeature) {
             return;
         }
         // Create a guest user.
@@ -103,20 +104,6 @@
         }
     }
 
-    /**
-     * Test that creating an ephemeral user fails on systems without the split system user.
-     */
-    public void testCreateEphemeralWithoutUserSplitFails() throws Exception {
-        if (!mHasFeature || hasUserSplit()) {
-            return;
-        }
-        String command ="pm create-user --ephemeral " + "TestUser_" + System.currentTimeMillis();
-        String commandOutput = getDevice().executeShellCommand(command);
-
-        assertEquals("Creating the epehemeral user should fail.",
-                "Error: couldn't create User.", commandOutput.trim());
-    }
-
     private boolean getGuestUsersEphemeral() throws Exception {
         String commandOutput = getDevice().executeShellCommand("dumpsys user");
         String[] outputLines = commandOutput.split("\n");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index ef7099b..b71c4c87 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.cts.devicepolicy;
 
-import org.junit.Test;
-
 /**
  * This class tests the provisioning flow with an APK that declares a single receiver with
  * BIND_DEVICE_ADMIN permissions, which was a requirement for the app sending the
@@ -54,7 +52,6 @@
         super.tearDown();
     }
 
-    @Test
     public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
index fa3627a..93a17e0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -111,6 +111,17 @@
                 "testAccountExist", mParentUserId);
     }
 
+    public void testWebview() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // We start an activity containing WebView in another process and run provisioning to
+        // test that the process is not killed.
+        startActivityAsUser(mParentUserId, MANAGED_PROFILE_PKG, ".WebViewActivity");
+        provisionManagedProfile();
+    }
+
     private void provisionManagedProfile() throws Exception {
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
                 "testProvisionManagedProfile", mParentUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 157c59c..962db8c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -47,7 +47,6 @@
     private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
     private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
 
-    private static final String WIFI_CONFIG_CREATOR_PKG = "com.android.cts.wificonfigcreator";
     private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
 
     private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
@@ -65,8 +64,6 @@
     private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
     private static final String NOTIFICATION_PKG =
             "com.android.cts.managedprofiletests.notificationsender";
-    private static final String NOTIFICATION_ACTIVITY =
-            NOTIFICATION_PKG + ".SendNotification";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
@@ -78,7 +75,6 @@
     private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
 
     private static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
-    private static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
 
     private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.SECONDS.toMillis(30);
 
@@ -87,11 +83,14 @@
     // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
     private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
 
+    private static final String PROFILE_CREDENTIAL = "1234";
+    // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
+    private static final int PROFILE_TIMEOUT_DELAY_MS = 10_000;
+
     private int mParentUserId;
 
     // ID of the profile we'll create. This will always be a profile of the parent.
     private int mProfileUserId;
-    private String mPackageVerifier;
 
     private boolean mHasNfcFeature;
 
@@ -138,6 +137,29 @@
                 mProfileUserId);
     }
 
+    public void testWipeDataWithReason() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertTrue(listUsers().contains(mProfileUserId));
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG,
+                ".WipeDataTest",
+                "testWipeDataWithReason",
+                mProfileUserId);
+        // Note: the managed profile is removed by this test, which will make removeUserCommand in
+        // tearDown() to complain, but that should be OK since its result is not asserted.
+        assertUserGetsRemoved(mProfileUserId);
+        // testWipeDataWithReason() removes the managed profile,
+        // so it needs to separated from other tests.
+        // Check the notification is presented after work profile got removed, so profile user no
+        // longer exists, verification should be run in primary user.
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG,
+                ".WipeDataWithReasonVerificationTest",
+                mParentUserId);
+    }
+
     /**
      *  wipeData() test removes the managed profile, so it needs to separated from other tests.
      */
@@ -147,7 +169,8 @@
         }
         assertTrue(listUsers().contains(mProfileUserId));
         runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".WipeDataTest", mProfileUserId);
+                MANAGED_PROFILE_PKG, ".WipeDataTest",
+                "testWipeData", mProfileUserId);
         // 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);
@@ -178,6 +201,84 @@
                 TIMEOUT_USER_LOCKED_MILLIS);
     }
 
+    /** Profile should get locked if it is not in foreground no matter what. */
+    public void testWorkProfileTimeoutBackground() throws Exception {
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mPrimaryUserId, true);
+        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(true);
+    }
+
+    /** Profile should get locked if it is in foreground but with no user activity. */
+    public void testWorkProfileTimeoutIdleActivity() throws Exception {
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, false);
+        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(true);
+    }
+
+    /** User activity in profile should prevent it from locking. */
+    public void testWorkProfileTimeoutUserActivity() throws Exception {
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, false);
+        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(false);
+    }
+
+    /** Keep screen on window flag in the profile should prevent it from locking. */
+    public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, true);
+        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(false);
+    }
+
+    private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
+        // Set separate challenge.
+        changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
+
+        // Make sure the profile is not prematurely locked.
+        verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
+        verifyOnlyProfileLocked(false);
+        // Set profile timeout to 5 seconds.
+        runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
+    }
+
+    private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
+        final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
+        runProfileTimeoutTest(expectedResultTest, mProfileUserId);
+        // Primary profile shouldn't be locked.
+        runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
+    }
+
+    private void simulateUserInteraction(int timeMs) throws Exception {
+        final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
+        for (int i = 0; i < timeMs; i += timeMs/10) {
+            Thread.sleep(timeMs/10);
+            helper.tapScreenCenter();
+        }
+    }
+
+    private void runProfileTimeoutTest(String method, int userId)
+            throws DeviceNotAvailableException {
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
+                method, userId);
+    }
+
+    private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
+                profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
+    }
+
     public void testMaxOneManagedProfile() throws Exception {
         int newUserId = -1;
         try {
@@ -243,6 +344,42 @@
         // TODO: Test with startActivity
     }
 
+    public void testDisallowSharingIntoProfileFromProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Set up activities: PrimaryUserActivity will only be enabled in the personal user
+        // This activity is used to find out the ground truth about the system's cross profile
+        // intent forwarding activity.
+        disableActivityForUser("PrimaryUserActivity", mProfileUserId);
+
+        // Tests from the profile side
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                ".DisallowSharingIntoProfileTest", "testSharingFromProfile", mProfileUserId);
+    }
+
+    public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
+        // This activity is used to find out the ground truth about the system's cross profile
+        // intent forwarding activity.
+        disableActivityForUser("ManagedProfileActivity", mParentUserId);
+
+        // Tests from the personal side, which is mostly driven from host side.
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSetUp", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testDisableSharingIntoProfile", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSharingFromPersonalFails", mParentUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testEnableSharingIntoProfile", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSharingFromPersonalSucceeds", mParentUserId);
+    }
+
     public void testAppLinks_verificationStatus() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1028,6 +1165,26 @@
         }
     }
 
+    public void testIsUsingUnifiedPassword() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // Freshly created profile profile has no separate challenge.
+        verifyUnifiedPassword(true);
+
+        // Set separate challenge and verify that the API reports it correctly.
+        changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+        verifyUnifiedPassword(false);
+    }
+
+    private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
+        final String testMethod =
+                unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
+                testMethod, mProfileUserId);
+    }
+
     private void disableActivityForUser(String activityName, int userId)
             throws DeviceNotAvailableException {
         String command = "am start -W --user " + userId
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
new file mode 100644
index 0000000..c18d8c3e
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.devicepolicy;
+
+/**
+ * Tests the DPC transfer functionality for device owner. Testing is done by having two dummy DPCs,
+ * CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
+ * 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.
+ */
+public class MixedDeviceOwnerHostSideTransferTest extends
+        DeviceAndProfileOwnerHostSideTransferTest {
+    private static final String TRANSFER_DEVICE_OWNER_OUTGOING_TEST =
+            "com.android.cts.transferowner.TransferDeviceOwnerOutgoingTest";
+    private static final String TRANSFER_DEVICE_OWNER_INCOMING_TEST =
+            "com.android.cts.transferowner.TransferDeviceOwnerIncomingTest";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (mHasFeature) {
+            installAppAsUser(TRANSFER_OWNER_OUTGOING_APK, mPrimaryUserId);
+            if (setDeviceOwner(TRANSFER_OWNER_OUTGOING_TEST_RECEIVER, mPrimaryUserId,
+                    false)) {
+                setupTestParameters(mPrimaryUserId, TRANSFER_DEVICE_OWNER_OUTGOING_TEST,
+                        TRANSFER_DEVICE_OWNER_INCOMING_TEST);
+                installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
+            } else {
+                removeAdmin(TRANSFER_OWNER_OUTGOING_TEST_RECEIVER, mUserId);
+                getDevice().uninstallPackage(TRANSFER_OWNER_OUTGOING_PKG);
+                fail("Failed to set device owner");
+            }
+        }
+    }
+
+    public void testTransferAffiliatedProfileOwnershipCompleteCallback() throws Exception {
+        if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
+            return;
+        }
+        final int profileUserId = setupManagedProfileOnDeviceOwner(TRANSFER_OWNER_OUTGOING_APK,
+                TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+
+        setSameAffiliationId(profileUserId, mOutgoingTestClassName);
+
+        installAppAsUser(TRANSFER_OWNER_INCOMING_APK, profileUserId);
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwner", profileUserId);
+
+        waitForBroadcastIdle();
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferAffiliatedProfileOwnershipCompleteCallbackIsCalled",
+                mUserId);
+    }
+
+    public void testTransferAffiliatedProfileOwnershipInComp() throws Exception {
+        if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
+            return;
+        }
+        final int profileUserId = setupManagedProfileOnDeviceOwner(TRANSFER_OWNER_OUTGOING_APK,
+                TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+
+        setSameAffiliationId(profileUserId, mOutgoingTestClassName);
+
+        installAppAsUser(TRANSFER_OWNER_INCOMING_APK, profileUserId);
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwner", profileUserId);
+        waitForBroadcastIdle();
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "testTransferOwner", mUserId);
+        waitForBroadcastIdle();
+
+        assertAffiliationIdsAreIntact(profileUserId, mIncomingTestClassName);
+
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+                mOutgoingTestClassName,
+                "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/MixedProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
new file mode 100644
index 0000000..5143fb1
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.devicepolicy;
+
+/**
+ * Tests the DPC transfer functionality for profile 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.
+ */
+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 {
+        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) {
+            int profileOwnerUserId = setupManagedProfile(TRANSFER_OWNER_OUTGOING_APK,
+                    TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+            if (profileOwnerUserId != -1) {
+                setupTestParameters(profileOwnerUserId, TRANSFER_PROFILE_OWNER_OUTGOING_TEST,
+                        TRANSFER_PROFILE_OWNER_INCOMING_TEST);
+                installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
new file mode 100644
index 0000000..8c7adbc5
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -0,0 +1,73 @@
+package com.android.cts.devicepolicy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * CTS to verify toggling quiet mode in work profile by using
+ * {@link android.os.UserManager#requestQuietModeEnabled(boolean, android.os.UserHandle)}.
+ */
+public class QuietModeHostsideTest extends BaseDevicePolicyTest {
+    private static final String TEST_PACKAGE = "com.android.cts.launchertests";
+    private static final String TEST_CLASS = ".QuietModeTest";
+    private static final String PARAM_TARGET_USER = "TARGET_USER";
+    private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER";
+    private static final String TEST_APK = "CtsLauncherAppsTests.apk";
+
+    private static final String TEST_LAUNCHER_PACKAGE = "com.android.cts.launchertests.support";
+    private static final String TEST_LAUNCHER_APK = "CtsLauncherAppsTestsSupport.apk";
+
+    private int mProfileId;
+    private String mOriginalLauncher;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mHasFeature = mHasFeature & hasDeviceFeature("android.software.managed_users");
+
+        if(mHasFeature) {
+            mOriginalLauncher = getDefaultLauncher();
+
+            installAppAsUser(TEST_APK, mPrimaryUserId);
+            installAppAsUser(TEST_LAUNCHER_APK, mPrimaryUserId);
+
+            createAndStartManagedProfile();
+            installAppAsUser(TEST_APK, mProfileId);
+
+            waitForBroadcastIdle();
+            wakeupAndDismissKeyguard();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            getDevice().uninstallPackage(TEST_PACKAGE);
+            getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
+        }
+        super.tearDown();
+    }
+
+    public void testQuietMode() throws Exception {
+        runDeviceTestsAsUser(
+                TEST_PACKAGE,
+                TEST_CLASS,
+                null,
+                mPrimaryUserId,
+                createParams(mProfileId));
+    }
+
+    private void createAndStartManagedProfile() throws Exception {
+        mProfileId = createManagedProfile(mPrimaryUserId);
+        switchUser(mPrimaryUserId);
+        startUser(mProfileId);
+    }
+
+    private Map<String, String> createParams(int targetUserId) throws Exception {
+        Map<String, String> params = new HashMap<>();
+        params.put(PARAM_TARGET_USER, Integer.toString(getUserSerialNumber(targetUserId)));
+        params.put(PARAM_ORIGINAL_DEFAULT_LAUNCHER, mOriginalLauncher);
+        return params;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java
new file mode 100644
index 0000000..0b17b17
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.devicepolicy;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Helper to simulate user activity on the device.
+ */
+class UserActivityEmulator {
+    private final int mWidth;
+    private final int mHeight;
+    private final ITestDevice mDevice;
+
+    public UserActivityEmulator(ITestDevice device) throws DeviceNotAvailableException {
+        // Figure out screen size. Output is something like "Physical size: 1440x2880".
+        mDevice = device;
+        final String output = mDevice.executeShellCommand("wm size");
+        final String[] sizes = output.split(" ")[2].split("x");
+        mWidth = Integer.valueOf(sizes[0].trim());
+        mHeight = Integer.valueOf(sizes[1].trim());
+    }
+
+    public void tapScreenCenter() throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(String.format("input tap %d %d", mWidth / 2, mHeight / 2));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java
new file mode 100644
index 0000000..cf02aef
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java
@@ -0,0 +1,8 @@
+//package com.android.cts.devicepolicy.crossprofile;
+//
+//import com.android.cts.devicepolicy.BaseDevicePolicyTest;
+//
+//public class CrossProfileAppsManagedProfileTest extends BaseDevicePolicyTest {
+//
+//
+//}
diff --git a/hostsidetests/dumpsys/AndroidTest.xml b/hostsidetests/dumpsys/AndroidTest.xml
index 252bf90..0ac7c2e 100644
--- a/hostsidetests/dumpsys/AndroidTest.xml
+++ b/hostsidetests/dumpsys/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest">
         <option name="jar" value="CtsDumpsysHostTestCases.jar" />
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
index 1a84011..01b9ca8 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
@@ -21,6 +21,8 @@
     <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24"/>
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name=".MainActivity"
             android:exported="true" />
         <service android:name=".ProcStatsHelperServiceMain"
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
index 9b2e198..d067e56 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
index dd279d9..ca548c8 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
@@ -18,16 +18,16 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
@@ -188,7 +188,7 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                     result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 4d51330..2ae5265 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -442,7 +442,7 @@
     }
 
     private void checkBatteryDischarge(String[] parts) {
-        assertEquals(12, parts.length);
+        assertEquals(14, parts.length);
         assertInteger(parts[4]); // low
         assertInteger(parts[5]); // high
         assertInteger(parts[6]); // screenOn
@@ -451,6 +451,8 @@
         assertInteger(parts[9]); // dischargeScreenOffMah
         assertInteger(parts[10]); // dischargeDozeCount
         assertInteger(parts[11]); // dischargeDozeMah
+        assertInteger(parts[12]); // dischargeLightDozeMah
+        assertInteger(parts[13]); // dischargeDeepDozeMah
     }
 
     private void checkBatteryLevel(String[] parts) {
@@ -723,7 +725,8 @@
                 for (int i = 0; i < TIMESTAMP_COUNT; i++) {
                     numparts[i] = assertInteger(parts[i]);
                 }
-                if (numparts[0] != 0) {
+                // Flags = 1 just means the first frame of the window
+                if (numparts[0] != 0 && numparts[0] != 1) {
                     continue;
                 }
                 // assert VSYNC >= INTENDED_VSYNC
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
index 4dfc1a3..c31cc80 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
@@ -51,6 +51,9 @@
      * --checkin", since the latter is not idempotent.
      */
     public void testProcstatsOutput() throws Exception {
+        if (true) {
+            return; // TODO http://b/70858729
+        }
         // First, run the helper app so that we have some interesting records in the output.
         checkWithProcStatsApp();
 
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/StoragedDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/StoragedDumpsysTest.java
index c8981c5..34da3b6 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/StoragedDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/StoragedDumpsysTest.java
@@ -17,17 +17,9 @@
 package android.dumpsys.cts;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.result.CollectingTestListener;
 
 import java.io.BufferedReader;
 import java.io.StringReader;
-import java.util.Map;
 
 /**
  * Test to check the format of the dumps of the processstats test.
diff --git a/hostsidetests/edi/AndroidTest.xml b/hostsidetests/edi/AndroidTest.xml
index 9449d72..aef3086 100644
--- a/hostsidetests/edi/AndroidTest.xml
+++ b/hostsidetests/edi/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsEdiHostTestCases.jar" />
diff --git a/hostsidetests/gputools/Android.mk b/hostsidetests/gputools/Android.mk
new file mode 100644
index 0000000..b2946be
--- /dev/null
+++ b/hostsidetests/gputools/Android.mk
@@ -0,0 +1,33 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MODULE := CtsGpuToolsHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_STATIC_JAVA_LIBRARIES := platform-test-annotations-host
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/gputools/AndroidTest.xml b/hostsidetests/gputools/AndroidTest.xml
new file mode 100644
index 0000000..98e41a7
--- /dev/null
+++ b/hostsidetests/gputools/AndroidTest.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.
+-->
+
+<configuration description="Config for CtsGpuToolsHostTestCases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-DEBUG.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-RELEASE.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsGpuToolsHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/gputools/apps/Android.mk b/hostsidetests/gputools/apps/Android.mk
new file mode 100644
index 0000000..c913837
--- /dev/null
+++ b/hostsidetests/gputools/apps/Android.mk
@@ -0,0 +1,71 @@
+# 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 := libctsgputools_jni
+LOCAL_SRC_FILES := \
+    jni/CtsGpuToolsJniOnLoad.cpp \
+    jni/android_gputools_cts_RootlessGpuDebug.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-DEBUG
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libctsgputools_jni
+
+LOCAL_AAPT_FLAGS := \
+--rename-manifest-package android.rootlessgpudebug.DEBUG.app \
+--debug-mode
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-RELEASE
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libctsgputools_jni \
+libVkLayer_nullLayerC
+
+LOCAL_AAPT_FLAGS := \
+--rename-manifest-package android.rootlessgpudebug.RELEASE.app
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
new file mode 100755
index 0000000..de8130f
--- /dev/null
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.rootlessgpudebug.app">>
+
+    <application>
+        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp b/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp
new file mode 100644
index 0000000..2761aea
--- /dev/null
+++ b/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_gputools_cts_RootlessGpuDebug(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
+        return JNI_ERR;
+    if (register_android_gputools_cts_RootlessGpuDebug(env))
+        return JNI_ERR;
+    return JNI_VERSION_1_4;
+}
diff --git a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
new file mode 100644
index 0000000..4fdddbc
--- /dev/null
+++ b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "RootlessGpuDebug"
+
+#include <android/log.h>
+#include <jni.h>
+#include <string>
+#include <vulkan/vulkan.h>
+
+#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__)
+
+namespace {
+
+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
+        VK_API_VERSION_1_0,
+    };
+    const VkInstanceCreateInfo instance_info = {
+        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+        nullptr,   // pNext
+        0,         // flags
+        &app_info,
+        0,         // layer count
+        nullptr,   // layers
+        0,         // extension count
+        nullptr,   // extensions
+    };
+    VkInstance instance;
+    VkResult vkResult = vkCreateInstance(&instance_info, nullptr, &instance);
+    if (vkResult == VK_ERROR_INITIALIZATION_FAILED) {
+        result = "vkCreateInstance failed, meaning layers could not be chained.";
+    } else {
+        result = "vkCreateInstance succeeded.";
+    }
+
+    return result;
+}
+
+jstring android_gputools_cts_RootlessGpuDebug_nativeInitVulkan(JNIEnv* env,
+    jclass /*clazz*/)
+{
+    std::string result;
+
+    result = initVulkan();
+
+    return env->NewStringUTF(result.c_str());
+}
+
+static JNINativeMethod gMethods[] = {
+    {    "nativeInitVulkan", "()Ljava/lang/String;",
+         (void*) android_gputools_cts_RootlessGpuDebug_nativeInitVulkan },
+};
+
+} // anonymous namespace
+
+int register_android_gputools_cts_RootlessGpuDebug(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity");
+    return env->RegisterNatives(clazz, gMethods,
+            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
new file mode 100644
index 0000000..ec5052b
--- /dev/null
+++ b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.rootlessgpudebug.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+public class RootlessGpuDebugDeviceActivity extends Activity {
+
+    static {
+        System.loadLibrary("ctsgputools_jni");
+    }
+
+    private static final String TAG = RootlessGpuDebugDeviceActivity.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        String result = nativeInitVulkan();
+        Log.i(TAG, result);
+    }
+
+    private static native String nativeInitVulkan();
+
+}
+
diff --git a/hostsidetests/gputools/layers/Android.mk b/hostsidetests/gputools/layers/Android.mk
new file mode 100644
index 0000000..201fafd
--- /dev/null
+++ b/hostsidetests/gputools/layers/Android.mk
@@ -0,0 +1,71 @@
+# 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 := 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_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+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_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+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_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-LAYERS
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libVkLayer_nullLayerA \
+libVkLayer_nullLayerB \
+libVkLayer_nullLayerC
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/gputools/layers/AndroidManifest.xml b/hostsidetests/gputools/layers/AndroidManifest.xml
new file mode 100755
index 0000000..957b047
--- /dev/null
+++ b/hostsidetests/gputools/layers/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.rootlessgpudebug.LAYERS.app">
+
+    <application android:debuggable="false" android:hasCode="false">
+    </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/layers/jni/nullLayer.cpp b/hostsidetests/gputools/layers/jni/nullLayer.cpp
new file mode 100644
index 0000000..7253b5c
--- /dev/null
+++ b/hostsidetests/gputools/layers/jni/nullLayer.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#include <android/log.h>
+#include <cstring>
+#include <vulkan/vulkan.h>
+#include "vk_layer_interface.h"
+
+#define xstr(a) str(a)
+#define str(a) #a
+
+#define LOG_TAG "nullLayer" 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("nullLayer" xstr(LAYERNAME) " loaded");
+
+
+namespace {
+
+
+// Minimal dispatch table for this simple layer
+struct {
+    PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
+} g_VulkanDispatchTable;
+
+
+template<class T>
+VkResult getProperties(const uint32_t count, const T *properties, uint32_t *pCount,
+                            T *pProperties) {
+    uint32_t copySize;
+
+    if (pProperties == NULL || properties == NULL) {
+        *pCount = count;
+        return VK_SUCCESS;
+    }
+
+    copySize = *pCount < count ? *pCount : count;
+    memcpy(pProperties, properties, copySize * sizeof(T));
+    *pCount = copySize;
+    if (copySize < count) {
+        return VK_INCOMPLETE;
+    }
+
+    return VK_SUCCESS;
+}
+
+static const VkLayerProperties LAYER_PROPERTIES = {
+    "VK_LAYER_ANDROID_nullLayer" xstr(LAYERNAME), VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION), 1, "Layer: nullLayer" xstr(LAYERNAME),
+};
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t *pCount, VkLayerProperties *pProperties) {
+    return getProperties<VkLayerProperties>(1, &LAYER_PROPERTIES, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice /* physicalDevice */, uint32_t *pCount,
+                                                              VkLayerProperties *pProperties) {
+    return getProperties<VkLayerProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* /* pLayerName */, uint32_t *pCount,
+                                                                    VkExtensionProperties *pProperties) {
+    return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice /* physicalDevice */, const char* /* pLayerName */,
+                                                                  uint32_t *pCount, VkExtensionProperties *pProperties) {
+    return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL nullCreateInstance(const VkInstanceCreateInfo* pCreateInfo,
+                                                  const VkAllocationCallbacks* pAllocator,
+                                                  VkInstance* pInstance) {
+
+    VkLayerInstanceCreateInfo *layerCreateInfo = (VkLayerInstanceCreateInfo *)pCreateInfo->pNext;
+
+    const char* msg = "nullCreateInstance called in nullLayer" xstr(LAYERNAME);
+    ALOGI("%s", msg);
+
+    // Step through the pNext chain until we get to the link function
+    while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO ||
+                              layerCreateInfo->function != VK_LAYER_FUNCTION_LINK)) {
+
+      layerCreateInfo = (VkLayerInstanceCreateInfo *)layerCreateInfo->pNext;
+    }
+
+    if(layerCreateInfo == NULL)
+      return VK_ERROR_INITIALIZATION_FAILED;
+
+    // Grab GIPA for the next layer
+    PFN_vkGetInstanceProcAddr gpa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;
+
+    // Track is in our dispatch table
+    g_VulkanDispatchTable.GetInstanceProcAddr = gpa;
+
+    // Advance the chain for next layer
+    layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;
+
+    // Call the next layer
+    PFN_vkCreateInstance createFunc = (PFN_vkCreateInstance)gpa(VK_NULL_HANDLE, "vkCreateInstance");
+    VkResult ret = createFunc(pCreateInfo, pAllocator, pInstance);
+
+    return ret;
+}
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice /* dev */, const char* /* funcName */) {
+    return nullptr;
+}
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* funcName) {
+
+    // Our simple layer only intercepts vkCreateInstance
+    const char* targetFunc = "vkCreateInstance";
+    if (!strncmp(targetFunc, funcName, sizeof(&targetFunc)))
+        return (PFN_vkVoidFunction)nullCreateInstance;
+
+    return (PFN_vkVoidFunction)g_VulkanDispatchTable.GetInstanceProcAddr(instance, funcName);
+}
+
+}  // namespace
+
+// loader-layer interface v0, just wrappers since there is only a layer
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t *pCount,
+                                                                  VkLayerProperties *pProperties) {
+    return EnumerateInstanceLayerProperties(pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t *pCount,
+                                                                VkLayerProperties *pProperties) {
+    return EnumerateDeviceLayerProperties(physicalDevice, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pCount,
+                                                                      VkExtensionProperties *pProperties) {
+    return EnumerateInstanceExtensionProperties(pLayerName, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice,
+                                                                    const char *pLayerName, uint32_t *pCount,
+                                                                    VkExtensionProperties *pProperties) {
+    return EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev, const char *funcName) {
+    return GetDeviceProcAddr(dev, funcName);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char *funcName) {
+    return GetInstanceProcAddr(instance, funcName);
+}
diff --git a/hostsidetests/gputools/layers/jni/vk_layer_interface.h b/hostsidetests/gputools/layers/jni/vk_layer_interface.h
new file mode 100644
index 0000000..f2a5232
--- /dev/null
+++ b/hostsidetests/gputools/layers/jni/vk_layer_interface.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and/or associated documentation files (the "Materials"), to
+ * deal in the Materials without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Materials, and to permit persons to whom the Materials are
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice(s) and this permission notice shall be included in
+ * all copies or substantial portions of the Materials.
+ *
+ * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
+ * USE OR OTHER DEALINGS IN THE MATERIALS.
+ *
+ */
+#pragma once
+
+#include <vulkan/vulkan.h>
+
+// ------------------------------------------------------------------------------------------------
+// CreateInstance and CreateDevice support structures
+
+typedef enum VkLayerFunction_ {
+    VK_LAYER_FUNCTION_LINK = 0,
+    VK_LAYER_FUNCTION_DATA_CALLBACK = 1
+} VkLayerFunction;
+
+typedef struct VkLayerInstanceLink_ {
+    struct VkLayerInstanceLink_* pNext;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+} VkLayerInstanceLink;
+
+typedef VkResult(VKAPI_PTR* PFN_vkSetInstanceLoaderData)(VkInstance instance,
+                                                         void* object);
+typedef VkResult(VKAPI_PTR* PFN_vkSetDeviceLoaderData)(VkDevice device,
+                                                       void* object);
+
+typedef struct {
+    VkStructureType sType;  // VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
+    const void* pNext;
+    VkLayerFunction function;
+    union {
+        VkLayerInstanceLink* pLayerInfo;
+        PFN_vkSetInstanceLoaderData pfnSetInstanceLoaderData;
+    } u;
+} VkLayerInstanceCreateInfo;
+
+typedef struct VkLayerDeviceLink_ {
+    struct VkLayerDeviceLink_* pNext;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+    PFN_vkGetDeviceProcAddr pfnNextGetDeviceProcAddr;
+} VkLayerDeviceLink;
+
+typedef struct {
+    VkStructureType sType;  // VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
+    const void* pNext;
+    VkLayerFunction function;
+    union {
+        VkLayerDeviceLink* pLayerInfo;
+        PFN_vkSetDeviceLoaderData pfnSetDeviceLoaderData;
+    } u;
+} VkLayerDeviceCreateInfo;
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
new file mode 100644
index 0000000..96096fb
--- /dev/null
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -0,0 +1,445 @@
+/*
+ * 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.gputools.cts;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import com.android.ddmlib.Log;
+
+import java.util.Scanner;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that exercise Rootless GPU Debug functionality supported by the loader.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsRootlessGpuDebugHostTest implements IDeviceTest {
+
+    public static final String TAG = CtsRootlessGpuDebugHostTest.class.getSimpleName();
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    // This test ensures that the Vulkan loader can use Settings to load layer
+    // from the base directory of debuggable applications.  Is also tests several
+    // positive and negative scenarios we want to cover (listed below).
+    //
+    // There are three APKs; DEBUG and RELEASE are practically identical with one
+    // being flagged as debuggable.  The LAYERS APK is mainly a conduit for getting
+    // layers onto the device without affecting the other APKs.
+    //
+    // The RELEASE APK does contain one layer to ensure using Settings to enable
+    // layers does not interfere with legacy methods using system properties.
+    //
+    // The layers themselves are practically null, only enough functionality to
+    // satisfy loader enumerating and loading.  They don't actually chain together.
+    //
+    // Positive 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)
+    // - Ensure we can push a layer to debuggable app (testDebugLayerLoadVulkan)
+    // - Ensure we can specify the app to load layers (testDebugLayerLoadVulkan)
+    // - 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 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)
+
+    private static final String CLASS = "RootlessGpuDebugDeviceActivity";
+    private static final String ACTIVITY = "android.rootlessgpudebug.app.RootlessGpuDebugDeviceActivity";
+    private static final String LAYER_A = "nullLayerA";
+    private static final String LAYER_B = "nullLayerB";
+    private static final String LAYER_C = "nullLayerC";
+    private static final String LAYER_A_LIB = "libVkLayer_" + LAYER_A + ".so";
+    private static final String LAYER_B_LIB = "libVkLayer_" + LAYER_B + ".so";
+    private static final String LAYER_C_LIB = "libVkLayer_" + LAYER_C + ".so";
+    private static final String LAYER_A_NAME = "VK_LAYER_ANDROID_" + LAYER_A;
+    private static final String LAYER_B_NAME = "VK_LAYER_ANDROID_" + LAYER_B;
+    private static final String LAYER_C_NAME = "VK_LAYER_ANDROID_" + LAYER_C;
+    private static final String DEBUG_APP = "android.rootlessgpudebug.DEBUG.app";
+    private static final String RELEASE_APP = "android.rootlessgpudebug.RELEASE.app";
+    private static final String LAYERS_APP = "android.rootlessgpudebug.LAYERS.app";
+
+    // 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
+    private static final long LOG_SEARCH_TIMEOUT_MS = 5000;
+
+    private String removeWhitespace(String input) {
+        return input.replaceAll(System.getProperty("line.separator"), "").trim();
+    }
+
+    /**
+     * Grab and format the process ID of requested app.
+     */
+    private String getPID(String app) throws Exception {
+        String pid = mDevice.executeAdbCommand("shell", "pidof", app);
+        return removeWhitespace(pid);
+    }
+
+    /**
+     * Extract the requested layer from APK and copy to tmp
+     */
+    private void setupLayer(String layer) throws Exception {
+
+        // We use the LAYERS apk to facilitate getting layers onto the device for mixing and matching
+        String libPath = mDevice.executeAdbCommand("shell", "pm", "path", LAYERS_APP);
+        libPath = libPath.replaceAll("package:", "");
+        libPath = libPath.replaceAll("base.apk", "");
+        libPath = removeWhitespace(libPath);
+        libPath += "lib/";
+
+        // Use find to get the .so so we can ignore ABI
+        String layerPath = mDevice.executeAdbCommand("shell", "find", libPath + " -name " + layer);
+        layerPath = removeWhitespace(layerPath);
+        mDevice.executeAdbCommand("shell", "cp", layerPath + " /data/local/tmp");
+    }
+
+    /**
+     * Simple helper class for returning multiple results
+     */
+    public class LogScanResult {
+        public boolean found;
+        public int lineNumber;
+    }
+
+    private LogScanResult scanLog(String pid, String searchString) throws Exception {
+        return scanLog(pid, searchString, "");
+    }
+
+    /**
+     * Scan the logcat for requested process ID, returning if found and which line
+     */
+    private LogScanResult scanLog(String pid, String searchString, String endString) throws Exception {
+
+        LogScanResult result = new LogScanResult();
+        result.found = false;
+        result.lineNumber = -1;
+
+        // Scan until output from app is found
+        boolean scanComplete= false;
+
+        // Let the test run a reasonable amount of time before moving on
+        long hostStartTime = System.currentTimeMillis();
+
+        while (!scanComplete && ((System.currentTimeMillis() - hostStartTime) < LOG_SEARCH_TIMEOUT_MS)) {
+
+            // Give our activity a chance to run and fill the log
+            Thread.sleep(1000);
+
+            // Pull the logcat for a single process
+            String logcat = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "--pid=" + pid, "*:V");
+            int lineNumber = 0;
+            Scanner apkIn = new Scanner(logcat);
+            while (apkIn.hasNextLine()) {
+                lineNumber++;
+                String line = apkIn.nextLine();
+                if (line.contains(searchString) && line.endsWith(endString)) {
+                    result.found = true;
+                    result.lineNumber = lineNumber;
+                }
+                if (line.contains("vkCreateInstance succeeded")) {
+                    // Once we've got output from the app, we've collected what we need
+                    scanComplete= true;
+                }
+            }
+            apkIn.close();
+        }
+
+        return result;
+    }
+
+    /**
+     * Remove any temporary files on the device, clear any settings, kill the apps after each test
+     */
+    @After
+    public void cleanup() throws Exception {
+        mDevice.executeAdbCommand("shell", "am", "force-stop", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "am", "force-stop", RELEASE_APP);
+        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", "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", "setprop", "debug.vulkan.layers", "\'\"\"\'");
+    }
+
+    /**
+     * This is the primary test of the feature. It pushes layers to our debuggable app and ensures they are
+     * loaded in the correct order.
+     */
+    @Test
+    public void testDebugLayerLoadVulkan() 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_A_NAME + ":" + LAYER_B_NAME);
+
+        // Copy the layers from our LAYERS APK to tmp
+        setupLayer(LAYER_A_LIB);
+        setupLayer(LAYER_B_LIB);
+
+        // Copy them over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_B_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", LAYER_B_LIB, ";", "chmod", "700", LAYER_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 = "nullCreateInstance called in " + LAYER_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertTrue("LayerA was not loaded", resultA.found);
+
+        String searchStringB = "nullCreateInstance called in " + LAYER_B;
+        LogScanResult resultB = scanLog(pid, searchStringB);
+        Assert.assertTrue("LayerB was not loaded", resultB.found);
+
+        Assert.assertTrue("LayerA should be loaded before LayerB", resultA.lineNumber < resultB.lineNumber);
+    }
+
+    /**
+     * This test ensures that we cannot push a layer to a non-debuggable app
+     * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+     */
+    @Test
+    public void testReleaseLayerLoadVulkan() throws Exception {
+
+        // Set up a layers to be loaded for RELEASE app
+        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 + ":" + LAYER_B_NAME);
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(LAYER_A_LIB);
+
+        // Attempt to copy them over to our RELEASE app (this should fail)
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", RELEASE_APP,
+                                   "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_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 = LAYER_A_NAME + "loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("LayerA was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable apps do not enumerate layers in base
+     * directory if enable_gpu_debug_layers is not enabled.
+     */
+    @Test
+    public void testDebugNotEnabledVulkan() 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", LAYER_A_NAME);
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(LAYER_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_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 = LAYER_A_NAME + "loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("LayerA was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable apps do not enumerate layers in base
+     * directory if gpu_debug_app does not match.
+     */
+    @Test
+    public void testDebugWrongAppVulkan() 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", LAYER_A_NAME);
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(LAYER_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_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 = LAYER_A_NAME + "loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("LayerA was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable apps do not enumerate layers in base
+     * directory if gpu_debug_layers are not set.
+     */
+    @Test
+    public void testDebugNoLayersEnabledVulkan() 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", "foo");
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(LAYER_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_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 = "nullCreateInstance called in " + LAYER_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("LayerA was loaded", resultA.found);
+    }
+
+    /**
+     * This test ensures we can still use properties if no layer found via Settings
+     */
+    @Test
+    public void testSystemPropertyEnableVulkan() 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);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+
+        // Enable layerC (which is packaged with the RELEASE app) with system properties
+        mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers " + LAYER_C_NAME);
+
+        // 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 = LAYER_A_NAME + "loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("LayerA was enumerated", resultA.found);
+
+        String searchStringC = "nullCreateInstance called in " + LAYER_C;
+        LogScanResult resultC = scanLog(pid, searchStringC);
+        Assert.assertTrue("LayerC was not loaded", resultC.found);
+    }
+
+    /**
+     * This test ensures system properties are ignored if Settings load a layer
+     */
+    @Test
+    public void testSystemPropertyIgnoreVulkan() 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", LAYER_A_NAME);
+
+        // Copy the layers from our LAYERS APK
+        setupLayer(LAYER_A_LIB);
+        setupLayer(LAYER_B_LIB);
+
+        // Copy them over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+                                 "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_B_LIB, "|", "run-as", DEBUG_APP,
+                                 "sh", "-c", "\'cat", ">", LAYER_B_LIB, ";", "chmod", "700", LAYER_B_LIB + "\'");
+
+        // Enable layerB with system properties
+        mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers " + LAYER_B_NAME);
+
+        // 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 = "nullCreateInstance called in " + LAYER_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertTrue("LayerA was not loaded", resultA.found);
+
+        String searchStringB = "nullCreateInstance called in " + LAYER_B;
+        LogScanResult resultB = scanLog(pid, searchStringB);
+        Assert.assertFalse("LayerB was loaded", resultB.found);
+    }
+}
diff --git a/hostsidetests/harmfulappwarning/Android.mk b/hostsidetests/harmfulappwarning/Android.mk
new file mode 100644
index 0000000..5ec2e99
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := CtsHarmfulAppWarningHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
+
+
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_CTS_TEST_PACKAGE := android.host.harmfulappwarning
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/harmfulappwarning/AndroidTest.xml b/hostsidetests/harmfulappwarning/AndroidTest.xml
new file mode 100644
index 0000000..bb9a472
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/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 the CTS Harmful App Warning host test cases">
+    <option name="config-descriptor:metadata" key="component" value="art" />
+    <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="CtsHarmfulAppWarningTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsHarmfulAppWarningHostTestCases.jar" />
+        <option name="runtime-hint" value="10s" />
+    </test>
+</configuration>
diff --git a/hostsidetests/harmfulappwarning/sampleapp/Android.mk b/hostsidetests/harmfulappwarning/sampleapp/Android.mk
new file mode 100644
index 0000000..a0aba0d
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/sampleapp/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
+
+LOCAL_PACKAGE_NAME := CtsHarmfulAppWarningSampleApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml b/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
new file mode 100755
index 0000000..5bfbd24
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/sampleapp/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.harmfulappwarning.sampleapp">
+
+    <application>
+        <activity android:name=".SampleDeviceActivity" >
+            <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/harmfulappwarning/sampleapp/res/layout/sample_layout.xml b/hostsidetests/harmfulappwarning/sampleapp/res/layout/sample_layout.xml
new file mode 100644
index 0000000..b34e5a0
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/sampleapp/res/layout/sample_layout.xml
@@ -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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</RelativeLayout>
diff --git a/hostsidetests/harmfulappwarning/sampleapp/src/android/harmfulappwarning/sampleapp/SampleDeviceActivity.java b/hostsidetests/harmfulappwarning/sampleapp/src/android/harmfulappwarning/sampleapp/SampleDeviceActivity.java
new file mode 100644
index 0000000..d1b7869
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/sampleapp/src/android/harmfulappwarning/sampleapp/SampleDeviceActivity.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 android.harmfulappwarning.sampleapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A simple activity which logs to Logcat.
+ *
+ * <p>This serves as a simple target application/activity to set a harmful app warning on.
+ */
+public class SampleDeviceActivity extends Activity {
+
+    private static final String ACTION_ACTIVITY_STARTED =
+            "android.harmfulappwarning.sampleapp.ACTIVITY_STARTED";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.sample_layout);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Intent intent = new Intent(ACTION_ACTIVITY_STARTED);
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/harmfulappwarning/src/android/harmfulappwarning/cts/HarmfulAppWarningTest.java b/hostsidetests/harmfulappwarning/src/android/harmfulappwarning/cts/HarmfulAppWarningTest.java
new file mode 100644
index 0000000..09cfd8b
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/src/android/harmfulappwarning/cts/HarmfulAppWarningTest.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.harmfulappwarning.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.PackageInfo;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Scanner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Host-side tests for the harmful app launch warning
+ *
+ * <p>These tests have a few different components. This is the host-side part of the test, which is
+ * responsible for setting up the environment and passing off execution to the device-side part
+ * of the test.
+ *
+ * <p>The {@link HarmfulAppWarningDeviceTest} class is the device side of these tests. It attempts to launch
+ * the sample warned application, and verifies the correct behavior of the harmful app warning that
+ * is shown by the platform.
+ *
+ * <p>The third component is the sample app, which is just a placeholder app with a basic activity
+ * that only serves as a target for the harmful app warning.
+ *
+ * <p>Run with: atest CtsHarmfulAppWarningHostTestCases
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HarmfulAppWarningTest extends BaseHostJUnit4Test {
+
+    private static final String TEST_APP_PACKAGE_NAME = "android.harmfulappwarning.sampleapp";
+    private static final String TEST_APP_ACTIVITY_CLASS_NAME = "SampleDeviceActivity";
+    private static final String TEST_APP_LAUNCHED_STRING = "Sample activity started.";
+
+    private static final String WARNING_MESSAGE = "This is a warning message.";
+    private static final String SET_HARMFUL_APP_WARNING_COMMAND = String.format(
+            "cmd package set-harmful-app-warning %s \"" + WARNING_MESSAGE + "\"",
+            TEST_APP_PACKAGE_NAME);
+
+    private static final String CLEAR_HARMFUL_APP_WARNING_COMMAND = String.format(
+            "cmd package set-harmful-app-warning %s", TEST_APP_PACKAGE_NAME);
+
+    private static final String GET_HARMFUL_APP_WARNING_COMMAND = String.format(
+            "cmd package get-harmful-app-warning %s", TEST_APP_PACKAGE_NAME);
+
+    private ITestDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        installPackage("CtsHarmfulAppWarningSampleApp.apk");
+        mDevice = getDevice();
+        mDevice.clearLogcat();
+    }
+
+    private void runDeviceTest(String testName) throws DeviceNotAvailableException {
+        runDeviceTests("android.harmfulappwarning.testapp",
+                "android.harmfulappwarning.testapp.HarmfulAppWarningDeviceTest",
+                testName);
+    }
+
+    private void verifyHarmfulAppWarningSet() throws DeviceNotAvailableException {
+        String warning = getDevice().executeShellCommand(GET_HARMFUL_APP_WARNING_COMMAND);
+        assertEquals(WARNING_MESSAGE, warning.trim());
+    }
+
+    private void verifyHarmfulAppWarningUnset() throws DeviceNotAvailableException {
+        String warning = getDevice().executeShellCommand(GET_HARMFUL_APP_WARNING_COMMAND);
+        if (warning != null) {
+            warning = warning.trim();
+        }
+        assertTrue(warning == null || warning.length() == 0);
+    }
+
+    private void verifySampleAppUninstalled() throws DeviceNotAvailableException {
+        PackageInfo info = getDevice().getAppPackageInfo(TEST_APP_PACKAGE_NAME);
+        Assert.assertNull("Harmful application was not uninstalled", info);
+    }
+
+    private void verifySampleAppInstalled() throws DeviceNotAvailableException {
+        PackageInfo info = getDevice().getAppPackageInfo(TEST_APP_PACKAGE_NAME);
+        Assert.assertNotNull("Harmful application was uninstalled", info);
+    }
+
+    /**
+     * A basic smoke test to ensure that we're able to detect the launch of the activity when there
+     * is no warning.
+     */
+    @Test
+    public void testNormalLaunch() throws Exception {
+        runDeviceTest("testNormalLaunch");
+    }
+
+    /**
+     * Tests that when the user clicks "launch anyway" on the harmful app warning dialog, the
+     * warning is cleared and the activity is launched.
+     */
+    @Test
+    public void testLaunchAnyway() throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(SET_HARMFUL_APP_WARNING_COMMAND);
+        runDeviceTest("testLaunchAnyway");
+
+        verifyHarmfulAppWarningUnset();
+    }
+
+    /**
+     * Tests that when the user clicks "uninstall" on the harmful app warning dialog, the
+     * application is uninstalled.
+     */
+    @Test
+    public void testUninstall() throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(SET_HARMFUL_APP_WARNING_COMMAND);
+        runDeviceTest("testUninstall");
+        verifySampleAppUninstalled();
+    }
+
+    /**
+     * Tests that no action is taken when the user dismisses the harmful app warning
+     */
+    @Test
+    public void testDismissDialog() throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(SET_HARMFUL_APP_WARNING_COMMAND);
+        runDeviceTest("testDismissDialog");
+        verifyHarmfulAppWarningSet();
+        verifySampleAppInstalled();
+    }
+}
diff --git a/hostsidetests/harmfulappwarning/testapp/Android.mk b/hostsidetests/harmfulappwarning/testapp/Android.mk
new file mode 100644
index 0000000..3ab50c4
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/testapp/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 \
+    compatibility-device-util \
+    ub-uiautomator
+
+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 := CtsHarmfulAppWarningTestApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/harmfulappwarning/testapp/AndroidManifest.xml b/hostsidetests/harmfulappwarning/testapp/AndroidManifest.xml
new file mode 100644
index 0000000..4f6bc69
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/testapp/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.harmfulappwarning.testapp">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.harmfulappwarning.testapp" />
+
+</manifest>
diff --git a/hostsidetests/harmfulappwarning/testapp/src/android/harmfulappwarning/testapp/HarmfulAppWarningDeviceTest.java b/hostsidetests/harmfulappwarning/testapp/src/android/harmfulappwarning/testapp/HarmfulAppWarningDeviceTest.java
new file mode 100644
index 0000000..c81702d
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/testapp/src/android/harmfulappwarning/testapp/HarmfulAppWarningDeviceTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.harmfulappwarning.testapp;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.Instrumentation;
+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.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This is the device-side part of the tests for the harmful app launch warnings.
+ *
+ * <p>These tests are triggered by their counterparts in the {@link HarmfulAppWarningTest}
+ * host-side cts test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class HarmfulAppWarningDeviceTest {
+
+    private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
+
+    private static final String ACTION_ACTIVITY_STARTED =
+            "android.harmfulappwarning.sampleapp.ACTIVITY_STARTED";
+
+    private static final String TEST_APP_PACKAGE_NAME = "android.harmfulappwarning.sampleapp";
+    private static final String TEST_APP_ACTIVITY_CLASS_NAME =
+            "android.harmfulappwarning.sampleapp.SampleDeviceActivity";
+
+    private BlockingBroadcastReceiver mSampleActivityStartedReceiver;
+
+    protected static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    protected static UiDevice getUiDevice() {
+        return UiDevice.getInstance(getInstrumentation());
+    }
+
+    private static void launchActivity(String packageName, String activityClassName) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(packageName, activityClassName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getInstrumentation().getTargetContext().startActivity(intent, null);
+    }
+
+    private void verifyWarningShown() {
+        getUiDevice().wait(Until.findObject(By.res("android:id/message")), TIMEOUT_MILLIS);
+        UiObject2 obj = getUiDevice().findObject(By.res("android:id/message"));
+        Assert.assertEquals(obj.getText(), "This is a warning message.");
+        Assert.assertNotNull(obj);
+    }
+
+    private void verifySampleActivityLaunched() {
+        Assert.assertNotNull("Sample activity not launched.",
+                mSampleActivityStartedReceiver.awaitForBroadcast(TIMEOUT_MILLIS));
+    }
+
+    private void verifySampleActivityNotLaunched() {
+        Assert.assertNull("Sample activity was unexpectedly launched.",
+                mSampleActivityStartedReceiver.awaitForBroadcast(TIMEOUT_MILLIS));
+    }
+
+    private void clickLaunchAnyway() {
+        UiObject2 obj = getUiDevice().findObject(By.res("android:id/button2"));
+        Assert.assertNotNull(obj);
+        obj.click();
+    }
+
+    private void clickUninstall() {
+        UiObject2 obj = getUiDevice().findObject(By.res("android:id/button1"));
+        Assert.assertNotNull(obj);
+        obj.click();
+    }
+
+    @Before
+    public void setUp() {
+        mSampleActivityStartedReceiver = new BlockingBroadcastReceiver(
+                InstrumentationRegistry.getContext(), ACTION_ACTIVITY_STARTED);
+        mSampleActivityStartedReceiver.register();
+    }
+
+    @After
+    public void tearDown() {
+        mSampleActivityStartedReceiver.unregisterQuietly();
+    }
+
+    @Test
+    public void testNormalLaunch() throws Exception {
+        launchActivity(TEST_APP_PACKAGE_NAME, TEST_APP_ACTIVITY_CLASS_NAME);
+        verifySampleActivityLaunched();
+    }
+
+    @Test
+    public void testLaunchAnyway() throws Exception {
+        launchActivity(TEST_APP_PACKAGE_NAME, TEST_APP_ACTIVITY_CLASS_NAME);
+        verifyWarningShown();
+        clickLaunchAnyway();
+        verifySampleActivityLaunched();
+    }
+
+    @Test
+    public void testUninstall() throws Exception {
+        launchActivity(TEST_APP_PACKAGE_NAME, TEST_APP_ACTIVITY_CLASS_NAME);
+        verifyWarningShown();
+        clickUninstall();
+        verifySampleActivityNotLaunched();
+    }
+
+    @Test
+    public void testDismissDialog() throws Exception {
+        launchActivity(TEST_APP_PACKAGE_NAME, TEST_APP_ACTIVITY_CLASS_NAME);
+        verifyWarningShown();
+        getUiDevice().pressHome();
+        verifySampleActivityNotLaunched();
+    }
+}
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index f26092e..641b516 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/AndroidTest.xml
@@ -14,6 +14,7 @@
      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="metrics" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsIncidentHostTestCases.jar" />
diff --git a/hostsidetests/incident/apps/batterystatsapp/Android.mk b/hostsidetests/incident/apps/batterystatsapp/Android.mk
index 42c87f4..44bcd89 100644
--- a/hostsidetests/incident/apps/batterystatsapp/Android.mk
+++ b/hostsidetests/incident/apps/batterystatsapp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit org.apache.http.legacy
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
index 74dfd11..3da03df 100644
--- a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.READ_SYNC_STATS" />
diff --git a/hostsidetests/incident/apps/boundwidgetapp/Android.mk b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
index 152414f..35452f5 100644
--- a/hostsidetests/incident/apps/boundwidgetapp/Android.mk
+++ b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
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 630df5c..4b58cf5 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
@@ -68,6 +68,7 @@
                 "java.lang.RuntimeException: This is a test exception");
         Intent intent = new Intent();
         intent.setClass(mContext, ExceptionActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
 
         assertTrue(mResultsReceivedSignal.await(TIMEOUT_SECS, TimeUnit.SECONDS));
@@ -82,6 +83,7 @@
                 "Subject: Broadcast of Intent { act=android.intent.action.SCREEN_ON");
         Intent intent = new Intent();
         intent.setClass(mContext, ANRActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
 
         assertTrue(mResultsReceivedSignal.await(TIMEOUT_SECS, TimeUnit.SECONDS));
@@ -95,6 +97,7 @@
                 mContext.getPackageName() + ":TestProcess", "backtrace:");
         Intent intent = new Intent();
         intent.setClass(mContext, NativeActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
 
         assertTrue(mResultsReceivedSignal.await(TIMEOUT_SECS, TimeUnit.SECONDS));
diff --git a/hostsidetests/incident/apps/graphicsstatsapp/Android.mk b/hostsidetests/incident/apps/graphicsstatsapp/Android.mk
index c2812c0..e0cf120 100644
--- a/hostsidetests/incident/apps/graphicsstatsapp/Android.mk
+++ b/hostsidetests/incident/apps/graphicsstatsapp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
diff --git a/hostsidetests/incident/apps/netstatsapp/Android.mk b/hostsidetests/incident/apps/netstatsapp/Android.mk
index 83ae82f..e3ecd16 100644
--- a/hostsidetests/incident/apps/netstatsapp/Android.mk
+++ b/hostsidetests/incident/apps/netstatsapp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
diff --git a/hostsidetests/incident/apps/procstatsapp/Android.mk b/hostsidetests/incident/apps/procstatsapp/Android.mk
new file mode 100644
index 0000000..b284116
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/Android.mk
@@ -0,0 +1,39 @@
+# 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_PACKAGE_NAME := CtsProcStatsProtoApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml
new file mode 100644
index 0000000..a0cccb4
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.server.cts.procstats" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".SimpleActivity" android:exported="true" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.server.cts.procstats" />
+</manifest>
diff --git a/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java b/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java
new file mode 100644
index 0000000..69ff75e
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.cts.procstats;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+public class SimpleActivity extends Activity {
+
+    private static final String TAG = "ProcstatsAppRunningTest";
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Log.i(TAG, "Procstats app is running");
+    }
+}
+
diff --git a/hostsidetests/incident/apps/storagedapp/Android.mk b/hostsidetests/incident/apps/storagedapp/Android.mk
index 3028716..71ee14f 100644
--- a/hostsidetests/incident/apps/storagedapp/Android.mk
+++ b/hostsidetests/incident/apps/storagedapp/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
diff --git a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
new file mode 100644
index 0000000..655edf7
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import com.android.server.am.proto.ActiveServicesProto;
+import com.android.server.am.proto.ActiveServicesProto.ServicesByUser;
+import com.android.server.am.proto.ActivityManagerServiceDumpBroadcastsProto;
+import com.android.server.am.proto.ActivityManagerServiceDumpProcessesProto;
+import com.android.server.am.proto.ActivityManagerServiceDumpProcessesProto.LruProcesses;
+import com.android.server.am.proto.ActivityManagerServiceDumpServicesProto;
+import com.android.server.am.proto.BroadcastQueueProto;
+import com.android.server.am.proto.BroadcastQueueProto.BroadcastSummary;
+import com.android.server.am.proto.BroadcastRecordProto;
+import com.android.server.am.proto.ProcessRecordProto;
+import com.android.server.am.proto.ServiceRecordProto;
+import com.android.server.am.proto.UidRecordProto;
+
+/**
+ * Test to check that the activity manager service properly outputs its dump state.
+ *
+ * make -j32 CtsIncidentHostTestCases
+ * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
+ */
+public class ActivityManagerIncidentTest extends ProtoDumpTestCase {
+
+    private static final String TEST_BROADCAST = "com.android.mybroadcast";
+    private static final String SYSTEM_PROC = "system";
+    private static final int SYSTEM_UID = 1000;
+
+    /**
+     * Tests activity manager dumps broadcasts.
+     */
+    public void testDumpBroadcasts() throws Exception {
+        getDevice().executeShellCommand("am broadcast -a " + TEST_BROADCAST);
+        Thread.sleep(100);
+        final ActivityManagerServiceDumpBroadcastsProto dump = getDump(
+                ActivityManagerServiceDumpBroadcastsProto.parser(),
+                "dumpsys activity --proto broadcasts");
+
+        assertTrue(dump.getReceiverListCount() > 0);
+        assertTrue(dump.getBroadcastQueueCount() > 0);
+        assertTrue(dump.getStickyBroadcastsCount() > 0);
+
+        boolean found = false;
+mybroadcast:
+        for (BroadcastQueueProto queue : dump.getBroadcastQueueList()) {
+            for (BroadcastRecordProto record : queue.getHistoricalBroadcastsList()) {
+                if (record.getIntentAction().equals(TEST_BROADCAST)) {
+                    found = true;
+                    break mybroadcast;
+                }
+            }
+            for (BroadcastSummary summary : queue.getHistoricalBroadcastsSummaryList()) {
+                if (summary.getIntent().getAction().equals(TEST_BROADCAST)) {
+                    found = true;
+                    break mybroadcast;
+                }
+            }
+        }
+        assertTrue(found);
+        ActivityManagerServiceDumpBroadcastsProto.MainHandler mainHandler = dump.getHandler();
+        assertTrue(mainHandler.getHandler().contains(
+            "com.android.server.am.ActivityManagerService"));
+    }
+
+    /**
+     * Tests activity manager dumps services.
+     */
+    public void testDumpServices() throws Exception {
+        final ActivityManagerServiceDumpServicesProto dump = getDump(
+                ActivityManagerServiceDumpServicesProto.parser(),
+                "dumpsys activity --proto service");
+        ActiveServicesProto activeServices = dump.getActiveServices();
+        assertTrue(activeServices.getServicesByUsersCount() > 0);
+
+        for (ServicesByUser perUserServices : activeServices.getServicesByUsersList()) {
+            assertTrue(perUserServices.getServiceRecordsCount() > 0);
+            for (ServiceRecordProto service : perUserServices.getServiceRecordsList()) {
+                assertFalse(service.getShortName().isEmpty());
+                assertFalse(service.getHexHash().isEmpty());
+                assertFalse(service.getPackageName().isEmpty());
+                assertFalse(service.getProcessName().isEmpty());
+                assertFalse(service.getAppinfo().getBaseDir().isEmpty());
+                assertFalse(service.getAppinfo().getDataDir().isEmpty());
+            }
+        }
+    }
+
+    /**
+     * Tests activity manager dumps processes.
+     */
+    public void testDumpProcesses() throws Exception {
+        final ActivityManagerServiceDumpProcessesProto dump = getDump(
+                ActivityManagerServiceDumpProcessesProto.parser(),
+                "dumpsys activity --proto processes");
+
+        assertTrue(dump.getProcsCount() > 0);
+        boolean procFound = false;
+        for (ProcessRecordProto proc : dump.getProcsList()) {
+            if (proc.getProcessName().equals(SYSTEM_PROC) && proc.getUid() == SYSTEM_UID) {
+                procFound = true;
+                break;
+            }
+        }
+        assertTrue(procFound);
+
+        assertTrue(dump.getActiveUidsCount() > 0);
+        boolean uidFound = false;
+        for (UidRecordProto uid : dump.getActiveUidsList()) {
+            if (uid.getUid() == SYSTEM_UID) {
+                uidFound = true;
+                break;
+            }
+        }
+        assertTrue(uidFound);
+
+        LruProcesses lruProcs = dump.getLruProcs();
+        assertTrue(lruProcs.getSize() == lruProcs.getListCount());
+        assertTrue(dump.getUidObserversCount() > 0);
+        assertTrue(dump.getAdjSeq() > 0);
+        assertTrue(dump.getLruSeq() > 0);
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
new file mode 100644
index 0000000..2a08eb7
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import com.android.server.AlarmClockMetadataProto;
+import com.android.server.AlarmManagerServiceDumpProto;
+import com.android.server.AlarmProto;
+import com.android.server.BatchProto;
+import com.android.server.BroadcastStatsProto;
+import com.android.server.ConstantsProto;
+import com.android.server.FilterStatsProto;
+import com.android.server.ForceAppStandbyTrackerProto;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+import com.android.server.IdleDispatchEntryProto;
+import com.android.server.InFlightProto;
+import com.android.server.WakeupEventProto;
+import java.util.List;
+
+/**
+ * Test to check that the alarm manager service properly outputs its dump state.
+ */
+public class AlarmManagerIncidentTest extends ProtoDumpTestCase {
+    public void testAlarmManagerServiceDump() throws Exception {
+        final AlarmManagerServiceDumpProto dump =
+                getDump(AlarmManagerServiceDumpProto.parser(), "dumpsys alarm --proto");
+
+        verifyAlarmManagerServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyAlarmManagerServiceDumpProto(AlarmManagerServiceDumpProto dump, final int filterLevel) throws Exception {
+        // Times should be positive.
+        assertTrue(0 < dump.getCurrentTime());
+        assertTrue(0 < dump.getElapsedRealtime());
+        assertTrue(0 < dump.getLastTimeChangeClockTime());
+        assertTrue(0 < dump.getLastTimeChangeRealtime());
+
+        // ConstantsProto
+        ConstantsProto settings = dump.getSettings();
+        assertTrue(0 < settings.getMinFuturityDurationMs());
+        assertTrue(0 < settings.getMinIntervalDurationMs());
+        assertTrue(0 < settings.getListenerTimeoutDurationMs());
+        assertTrue(0 < settings.getAllowWhileIdleShortDurationMs());
+        assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
+        assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
+
+        // ForceAppStandbyTrackerProto
+        ForceAppStandbyTrackerProto forceAppStandbyTracker = dump.getForceAppStandbyTracker();
+        for (int uid : forceAppStandbyTracker.getForegroundUidsList()) {
+            // 0 is technically a valid UID.
+            assertTrue(0 <= uid);
+        }
+        for (int aid : forceAppStandbyTracker.getPowerSaveWhitelistAppIdsList()) {
+            assertTrue(0 <= aid);
+        }
+        for (int aid : forceAppStandbyTracker.getTempPowerSaveWhitelistAppIdsList()) {
+            assertTrue(0 <= aid);
+        }
+        for (RunAnyInBackgroundRestrictedPackages r : forceAppStandbyTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
+            assertTrue(0 <= r.getUid());
+        }
+
+        if (!dump.getIsInteractive()) {
+            // These are only valid if is_interactive is false.
+            assertTrue(0 < dump.getTimeSinceNonInteractiveMs());
+            assertTrue(0 < dump.getMaxWakeupDelayMs());
+            assertTrue(0 < dump.getTimeSinceLastDispatchMs());
+            // time_until_next_non_wakeup_delivery_ms could be negative if the delivery time is in the past.
+        }
+
+        assertTrue(0 < dump.getTimeUntilNextWakeupMs());
+        assertTrue(0 < dump.getTimeSinceLastWakeupMs());
+        assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
+        assertTrue(0 <= dump.getTimeChangeEventCount());
+
+        for (int aid : dump.getDeviceIdleUserWhitelistAppIdsList()) {
+            assertTrue(0 <= aid);
+        }
+
+        // AlarmClockMetadataProto
+        for (AlarmClockMetadataProto ac : dump.getNextAlarmClockMetadataList()) {
+            assertTrue(0 <= ac.getUser());
+            assertTrue(0 < ac.getTriggerTimeMs());
+        }
+
+        for (BatchProto b : dump.getPendingAlarmBatchesList()) {
+            final long start = b.getStartRealtime();
+            final long end = b.getEndRealtime();
+            assertTrue("Batch start time (" + start+ ") is negative", 0 <= start);
+            assertTrue("Batch end time (" + end + ") is negative", 0 <= end);
+            assertTrue("Batch start time (" + start + ") is after its end time (" + end + ")",
+                start <= end);
+            testAlarmProtoList(b.getAlarmsList(), filterLevel);
+        }
+
+        testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList(), filterLevel);
+
+        testAlarmProto(dump.getPendingIdleUntil(), filterLevel);
+
+        testAlarmProtoList(dump.getPendingWhileIdleAlarmsList(), filterLevel);
+
+        testAlarmProto(dump.getNextWakeFromIdle(), filterLevel);
+
+        testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList(), filterLevel);
+
+        assertTrue(0 <= dump.getDelayedAlarmCount());
+        assertTrue(0 <= dump.getTotalDelayTimeMs());
+        assertTrue(0 <= dump.getMaxDelayDurationMs());
+        assertTrue(0 <= dump.getMaxNonInteractiveDurationMs());
+
+        assertTrue(0 <= dump.getBroadcastRefCount());
+        assertTrue(0 <= dump.getPendingIntentSendCount());
+        assertTrue(0 <= dump.getPendingIntentFinishCount());
+        assertTrue(0 <= dump.getListenerSendCount());
+        assertTrue(0 <= dump.getListenerFinishCount());
+
+        for (InFlightProto f : dump.getOutstandingDeliveriesList())  {
+            assertTrue(0 <= f.getUid());
+            assertTrue(0 < f.getWhenElapsedMs());
+            testBroadcastStatsProto(f.getBroadcastStats());
+            testFilterStatsProto(f.getFilterStats(), filterLevel);
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(f.getTag().isEmpty());
+            }
+        }
+
+        for (AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
+            assertTrue(0 <= l.getUid());
+            assertTrue(0 < l.getTimeMs());
+        }
+
+        for (AlarmManagerServiceDumpProto.TopAlarm ta : dump.getTopAlarmsList()) {
+            assertTrue(0 <= ta.getUid());
+            testFilterStatsProto(ta.getFilter(), filterLevel);
+        }
+
+        for (AlarmManagerServiceDumpProto.AlarmStat as : dump.getAlarmStatsList()) {
+            testBroadcastStatsProto(as.getBroadcast());
+            for (FilterStatsProto f : as.getFiltersList()) {
+                testFilterStatsProto(f, filterLevel);
+            }
+        }
+
+        for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
+            assertTrue(0 <= id.getUid());
+            assertTrue(0 <= id.getEntryCreationRealtime());
+            assertTrue(0 <= id.getArgRealtime());
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(id.getTag().isEmpty());
+            }
+        }
+
+        for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
+            assertTrue(0 <= we.getUid());
+            assertTrue(0 <= we.getWhen());
+        }
+    }
+
+    private static void testAlarmProtoList(List<AlarmProto> alarms, final int filterLevel) throws Exception {
+        for (AlarmProto a : alarms) {
+            testAlarmProto(a, filterLevel);
+        }
+    }
+
+    private static void testAlarmProto(AlarmProto alarm, final int filterLevel) throws Exception {
+        assertNotNull(alarm);
+
+        if (filterLevel == PRIVACY_AUTO) {
+            assertTrue(alarm.getTag().isEmpty());
+            assertTrue(alarm.getListener().isEmpty());
+        }
+        // alarm.time_until_when_elapsed_ms can be negative if 'when' is in the past.
+        assertTrue(0 <= alarm.getWindowLengthMs());
+        assertTrue(0 <= alarm.getRepeatIntervalMs());
+        assertTrue(0 <= alarm.getCount());
+    }
+
+    private static void testBroadcastStatsProto(BroadcastStatsProto broadcast) throws Exception {
+        assertNotNull(broadcast);
+
+        assertTrue(0 <= broadcast.getUid());
+        assertTrue(0 <= broadcast.getTotalFlightDurationMs());
+        assertTrue(0 <= broadcast.getCount());
+        assertTrue(0 <= broadcast.getWakeupCount());
+        assertTrue(0 <= broadcast.getStartTimeRealtime());
+        // Nesting should be non-negative.
+        assertTrue(0 <= broadcast.getNesting());
+    }
+
+    private static void testFilterStatsProto(FilterStatsProto filter, final int filterLevel) throws Exception {
+        assertNotNull(filter);
+
+        if (filterLevel == PRIVACY_AUTO) {
+            assertTrue(filter.getTag().isEmpty());
+        }
+        assertTrue(0 <= filter.getLastFlightTimeRealtime());
+        assertTrue(0 <= filter.getTotalFlightDurationMs());
+        assertTrue(0 <= filter.getCount());
+        assertTrue(0 <= filter.getWakeupCount());
+        assertTrue(0 <= filter.getStartTimeRealtime());
+        // Nesting should be non-negative.
+        assertTrue(0 <= filter.getNesting());
+    }
+}
+
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
index 4b83b0a..368b574 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
@@ -16,35 +16,42 @@
 
 package com.android.server.cts;
 
+import android.os.BatteryHealthEnum;
+import android.os.BatteryPluggedStateEnum;
+import android.os.BatteryStatusEnum;
 import android.service.battery.BatteryServiceDumpProto;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 
 /** Test to check that the battery manager properly outputs its dump state. */
 public class BatteryIncidentTest extends ProtoDumpTestCase {
-    private final String LEANBACK_FEATURE = "android.software.leanback";
+    private static final String LEANBACK_FEATURE = "android.software.leanback";
 
     public void testBatteryServiceDump() throws Exception {
+        if (hasBattery(getDevice())) {
+            return;
+        }
+
         final BatteryServiceDumpProto dump =
                 getDump(BatteryServiceDumpProto.parser(), "dumpsys battery --proto");
 
+        verifyBatteryServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyBatteryServiceDumpProto(BatteryServiceDumpProto dump, final int filterLevel) {
         if (!dump.getIsPresent()) {
             /* If the battery isn't present, no need to run this test. */
             return;
         }
 
-        if (isLeanback()) {
-            /* Android TV reports that it has a battery, but it doesn't really. */
-            return;
-        }
-
+        assertTrue(dump.getPlugged() != BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS);
+        assertTrue(dump.getMaxChargingCurrent() >= 0);
+        assertTrue(dump.getMaxChargingVoltage() >= 0);
+        assertTrue(dump.getChargeCounter() >= 0);
         assertTrue(
-                dump.getPlugged()
-                        != BatteryServiceDumpProto.BatteryPlugged.BATTERY_PLUGGED_WIRELESS);
-        assertTrue(dump.getChargeCounter() > 0);
+                dump.getStatus() != BatteryStatusEnum.BATTERY_STATUS_INVALID);
         assertTrue(
-                dump.getStatus() != BatteryServiceDumpProto.BatteryStatus.BATTERY_STATUS_INVALID);
-        assertTrue(
-                dump.getHealth() != BatteryServiceDumpProto.BatteryHealth.BATTERY_HEALTH_INVALID);
+                dump.getHealth() != BatteryHealthEnum.BATTERY_HEALTH_INVALID);
         int scale = dump.getScale();
         assertTrue(scale > 0);
         int level = dump.getLevel();
@@ -53,8 +60,13 @@
         assertTrue(dump.getTemperature() > 0);
     }
 
-    private boolean isLeanback() throws DeviceNotAvailableException {
-        final String commandOutput = getDevice().executeShellCommand("pm list features");
+    static boolean hasBattery(ITestDevice device) throws DeviceNotAvailableException {
+        /* Android TV reports that it has a battery, but it doesn't really. */
+        return !isLeanback(device);
+    }
+
+    private static boolean isLeanback(ITestDevice device) throws DeviceNotAvailableException {
+        final String commandOutput = device.executeShellCommand("pm list features");
         return commandOutput.contains(LEANBACK_FEATURE);
     }
 }
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java
new file mode 100644
index 0000000..6a9168e
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java
@@ -0,0 +1,493 @@
+/*
+ * 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.server.cts;
+
+import android.os.BatteryStatsProto;
+import android.os.ControllerActivityProto;
+import android.os.SystemProto;
+import android.os.TimerProto;
+import android.os.UidProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+
+/**
+ * Test to BatteryStats proto dump.
+ */
+public class BatteryStatsIncidentTest extends ProtoDumpTestCase {
+
+    @Override
+    protected void tearDown() throws Exception {
+        batteryOffScreenOn();
+        super.tearDown();
+    }
+
+    protected void batteryOnScreenOff() throws Exception {
+        getDevice().executeShellCommand("dumpsys battery unplug");
+        getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+    }
+
+    protected void batteryOffScreenOn() throws Exception {
+        getDevice().executeShellCommand("dumpsys battery reset");
+        getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+    }
+
+    /**
+     * Tests that batterystats is dumped to proto with sane values.
+     */
+    public void testBatteryStatsServiceDump() throws Exception {
+        batteryOnScreenOff();
+        Thread.sleep(5000); // Allow some time for battery data to accumulate.
+
+        final BatteryStatsServiceDumpProto dump = getDump(BatteryStatsServiceDumpProto.parser(),
+                "dumpsys batterystats --proto");
+
+        verifyBatteryStatsServiceDumpProto(dump, PRIVACY_NONE);
+
+        batteryOffScreenOn();
+    }
+
+    static void verifyBatteryStatsServiceDumpProto(BatteryStatsServiceDumpProto dump, final int filterLevel) throws Exception {
+        final BatteryStatsProto bs = dump.getBatterystats();
+        assertNotNull(bs);
+
+        // Proto dumps were finalized when the batterystats report version was ~29 and the parcel
+        // version was ~172.
+        assertTrue(29 <= bs.getReportVersion());
+        assertTrue(172 <= bs.getParcelVersion());
+        assertNotNull(bs.getStartPlatformVersion());
+        assertFalse(bs.getStartPlatformVersion().isEmpty());
+        assertNotNull(bs.getEndPlatformVersion());
+        assertFalse(bs.getEndPlatformVersion().isEmpty());
+
+        for (UidProto u : bs.getUidsList()) {
+            testUidProto(u, filterLevel);
+        }
+
+        testSystemProto(bs.getSystem());
+    }
+
+    private static void testControllerActivityProto(ControllerActivityProto ca) throws Exception {
+        assertNotNull(ca);
+
+        assertTrue(0 <= ca.getIdleDurationMs());
+        assertTrue(0 <= ca.getRxDurationMs());
+        assertTrue(0 <= ca.getPowerMah());
+        for (ControllerActivityProto.TxLevel tx : ca.getTxList()) {
+            assertTrue(0 <= tx.getDurationMs());
+        }
+    }
+
+    private static void testBatteryLevelStep(SystemProto.BatteryLevelStep bls) throws Exception {
+        assertNotNull(bls);
+
+        assertTrue(0 < bls.getDurationMs());
+        assertTrue(0 <= bls.getLevel());
+        assertTrue(100 >= bls.getLevel());
+
+        assertTrue(SystemProto.BatteryLevelStep.DisplayState.getDescriptor().getValues()
+                .contains(bls.getDisplayState().getValueDescriptor()));
+        assertTrue(SystemProto.BatteryLevelStep.PowerSaveMode.getDescriptor().getValues()
+                .contains(bls.getPowerSaveMode().getValueDescriptor()));
+        assertTrue(SystemProto.BatteryLevelStep.IdleMode.getDescriptor().getValues()
+                .contains(bls.getIdleMode().getValueDescriptor()));
+    }
+
+    private static void testSystemProto(SystemProto s) throws Exception {
+        final long epsilon = 500; // Allow ~500 ms of error when comparing times.
+        assertNotNull(s);
+
+        SystemProto.Battery b = s.getBattery();
+        assertTrue(0 < b.getStartClockTimeMs());
+        assertTrue(0 <= b.getStartCount());
+        long totalRealtimeMs = b.getTotalRealtimeMs();
+        long totalUptimeMs = b.getTotalUptimeMs();
+        assertTrue(0 <= totalUptimeMs);
+        assertTrue(totalUptimeMs <= totalRealtimeMs + epsilon);
+        long batteryRealtimeMs = b.getBatteryRealtimeMs();
+        long batteryUptimeMs = b.getBatteryUptimeMs();
+        assertTrue(0 <= batteryUptimeMs);
+        assertTrue(batteryUptimeMs <= batteryRealtimeMs + epsilon);
+        assertTrue("Battery realtime (" + batteryRealtimeMs + ") is greater than total realtime (" + totalRealtimeMs + ")",
+            batteryRealtimeMs <= totalRealtimeMs + epsilon);
+        assertTrue(batteryUptimeMs <= totalUptimeMs + epsilon);
+        long screenOffRealtimeMs = b.getScreenOffRealtimeMs();
+        long screenOffUptimeMs = b.getScreenOffUptimeMs();
+        assertTrue(0 <= screenOffUptimeMs);
+        assertTrue(screenOffUptimeMs <= screenOffRealtimeMs + epsilon);
+        assertTrue(screenOffRealtimeMs <= totalRealtimeMs + epsilon);
+        assertTrue(screenOffUptimeMs <= totalUptimeMs + epsilon);
+        long screenDozeDurationMs = b.getScreenDozeDurationMs();
+        assertTrue(0 <= screenDozeDurationMs);
+        assertTrue(screenDozeDurationMs <= screenOffRealtimeMs + epsilon);
+        assertTrue(0 < b.getEstimatedBatteryCapacityMah());
+        long minLearnedCapacityUah = b.getMinLearnedBatteryCapacityUah();
+        long maxLearnedCapacityUah = b.getMaxLearnedBatteryCapacityUah();
+        assertTrue(0 <= minLearnedCapacityUah);
+        assertTrue(minLearnedCapacityUah <= maxLearnedCapacityUah);
+
+        SystemProto.BatteryDischarge bd = s.getBatteryDischarge();
+        int lowerBound = bd.getLowerBoundSinceCharge();
+        int upperBound = bd.getUpperBoundSinceCharge();
+        assertTrue(0 <= lowerBound);
+        assertTrue(lowerBound <= upperBound);
+        assertTrue(0 <= bd.getScreenOnSinceCharge());
+        int screenOff = bd.getScreenOffSinceCharge();
+        int screenDoze = bd.getScreenDozeSinceCharge();
+        assertTrue(0 <= screenDoze);
+        assertTrue(screenDoze <= screenOff);
+        long totalMah = bd.getTotalMah();
+        long totalMahScreenOff = bd.getTotalMahScreenOff();
+        long totalMahScreenDoze = bd.getTotalMahScreenDoze();
+        long totalMahLightDoze = bd.getTotalMahLightDoze();
+        long totalMahDeepDoze = bd.getTotalMahDeepDoze();
+        assertTrue(0 <= totalMahScreenDoze);
+        assertTrue(0 <= totalMahLightDoze);
+        assertTrue(0 <= totalMahDeepDoze);
+        assertTrue(totalMahScreenDoze <= totalMahScreenOff);
+        assertTrue(totalMahLightDoze <= totalMahScreenOff);
+        assertTrue(totalMahDeepDoze <= totalMahScreenOff);
+        assertTrue(totalMahScreenOff <= totalMah);
+
+        assertTrue(-1 <= s.getChargeTimeRemainingMs());
+        assertTrue(-1 <= s.getDischargeTimeRemainingMs());
+
+        for (SystemProto.BatteryLevelStep bls : s.getChargeStepList()) {
+            testBatteryLevelStep(bls);
+        }
+        for (SystemProto.BatteryLevelStep bls : s.getDischargeStepList()) {
+            testBatteryLevelStep(bls);
+        }
+
+        for (SystemProto.DataConnection dc : s.getDataConnectionList()) {
+            assertTrue(SystemProto.DataConnection.Name.getDescriptor().getValues()
+                    .contains(dc.getName().getValueDescriptor()));
+            testTimerProto(dc.getTotal());
+        }
+
+        testControllerActivityProto(s.getGlobalBluetoothController());
+        testControllerActivityProto(s.getGlobalModemController());
+        testControllerActivityProto(s.getGlobalWifiController());
+
+        SystemProto.GlobalNetwork gn = s.getGlobalNetwork();
+        assertTrue(0 <= gn.getMobileBytesRx());
+        assertTrue(0 <= gn.getMobileBytesTx());
+        assertTrue(0 <= gn.getWifiBytesRx());
+        assertTrue(0 <= gn.getWifiBytesTx());
+        assertTrue(0 <= gn.getMobilePacketsRx());
+        assertTrue(0 <= gn.getMobilePacketsTx());
+        assertTrue(0 <= gn.getWifiPacketsRx());
+        assertTrue(0 <= gn.getWifiPacketsTx());
+        assertTrue(0 <= gn.getBtBytesRx());
+        assertTrue(0 <= gn.getBtBytesTx());
+
+        SystemProto.GlobalWifi gw = s.getGlobalWifi();
+        assertTrue(0 <= gw.getOnDurationMs());
+        assertTrue(0 <= gw.getRunningDurationMs());
+
+        for (SystemProto.KernelWakelock kw : s.getKernelWakelockList()) {
+            testTimerProto(kw.getTotal());
+        }
+
+        SystemProto.Misc m = s.getMisc();
+        assertTrue(0 <= m.getScreenOnDurationMs());
+        assertTrue(0 <= m.getPhoneOnDurationMs());
+        assertTrue(0 <= m.getFullWakelockTotalDurationMs());
+        assertTrue(0 <= m.getPartialWakelockTotalDurationMs());
+        assertTrue(0 <= m.getMobileRadioActiveDurationMs());
+        assertTrue(0 <= m.getMobileRadioActiveAdjustedTimeMs());
+        assertTrue(0 <= m.getMobileRadioActiveCount());
+        assertTrue(0 <= m.getMobileRadioActiveUnknownDurationMs());
+        assertTrue(0 <= m.getInteractiveDurationMs());
+        assertTrue(0 <= m.getBatterySaverModeEnabledDurationMs());
+        assertTrue(0 <= m.getNumConnectivityChanges());
+        assertTrue(0 <= m.getDeepDozeEnabledDurationMs());
+        assertTrue(0 <= m.getDeepDozeCount());
+        assertTrue(0 <= m.getDeepDozeIdlingDurationMs());
+        assertTrue(0 <= m.getDeepDozeIdlingCount());
+        assertTrue(0 <= m.getLongestDeepDozeDurationMs());
+        assertTrue(0 <= m.getLightDozeEnabledDurationMs());
+        assertTrue(0 <= m.getLightDozeCount());
+        assertTrue(0 <= m.getLightDozeIdlingDurationMs());
+        assertTrue(0 <= m.getLightDozeIdlingCount());
+        assertTrue(0 <= m.getLongestLightDozeDurationMs());
+
+        for (SystemProto.PhoneSignalStrength pss : s.getPhoneSignalStrengthList()) {
+            testTimerProto(pss.getTotal());
+        }
+
+        for (SystemProto.PowerUseItem pui : s.getPowerUseItemList()) {
+            assertTrue(SystemProto.PowerUseItem.Sipper.getDescriptor().getValues()
+                    .contains(pui.getName().getValueDescriptor()));
+            assertTrue(0 <= pui.getUid());
+            assertTrue(0 <= pui.getComputedPowerMah());
+            assertTrue(0 <= pui.getScreenPowerMah());
+            assertTrue(0 <= pui.getProportionalSmearMah());
+        }
+
+        SystemProto.PowerUseSummary pus = s.getPowerUseSummary();
+        assertTrue(0 < pus.getBatteryCapacityMah());
+        assertTrue(0 <= pus.getComputedPowerMah());
+        double minDrained = pus.getMinDrainedPowerMah();
+        double maxDrained = pus.getMaxDrainedPowerMah();
+        assertTrue(0 <= minDrained);
+        assertTrue(minDrained <= maxDrained);
+
+        for (SystemProto.ResourcePowerManager rpm : s.getResourcePowerManagerList()) {
+            assertNotNull(rpm.getName());
+            assertFalse(rpm.getName().isEmpty());
+            testTimerProto(rpm.getTotal());
+            testTimerProto(rpm.getScreenOff());
+        }
+
+        for (SystemProto.ScreenBrightness sb : s.getScreenBrightnessList()) {
+            testTimerProto(sb.getTotal());
+        }
+
+        testTimerProto(s.getSignalScanning());
+
+        for (SystemProto.WakeupReason wr : s.getWakeupReasonList()) {
+            testTimerProto(wr.getTotal());
+        }
+
+        SystemProto.WifiMulticastWakelockTotal wmwl = s.getWifiMulticastWakelockTotal();
+        assertTrue(0 <= wmwl.getDurationMs());
+        assertTrue(0 <= wmwl.getCount());
+
+        for (SystemProto.WifiSignalStrength wss : s.getWifiSignalStrengthList()) {
+            testTimerProto(wss.getTotal());
+        }
+
+        for (SystemProto.WifiState ws : s.getWifiStateList()) {
+            assertTrue(SystemProto.WifiState.Name.getDescriptor().getValues()
+                    .contains(ws.getName().getValueDescriptor()));
+            testTimerProto(ws.getTotal());
+        }
+
+        for (SystemProto.WifiSupplicantState wss : s.getWifiSupplicantStateList()) {
+            assertTrue(SystemProto.WifiSupplicantState.Name.getDescriptor().getValues()
+                    .contains(wss.getName().getValueDescriptor()));
+            testTimerProto(wss.getTotal());
+        }
+    }
+
+    private static void testTimerProto(TimerProto t) throws Exception {
+        assertNotNull(t);
+
+        long duration = t.getDurationMs();
+        long curDuration = t.getCurrentDurationMs();
+        long maxDuration = t.getMaxDurationMs();
+        long totalDuration = t.getTotalDurationMs();
+        assertTrue(0 <= duration);
+        assertTrue(0 <= t.getCount());
+        // Not all TimerProtos will have max duration, current duration, or total duration
+        // populated, so must tread carefully. Regardless, they should never be negative.
+        assertTrue(0 <= curDuration);
+        assertTrue(0 <= maxDuration);
+        assertTrue(0 <= totalDuration);
+        if (maxDuration > 0) {
+            assertTrue(curDuration <= maxDuration);
+        }
+        if (totalDuration > 0) {
+            assertTrue(maxDuration <= totalDuration);
+            assertTrue("Duration " + duration + " is greater than totalDuration " + totalDuration,
+                    duration <= totalDuration);
+        }
+    }
+
+    private static void testByFrequency(UidProto.Cpu.ByFrequency bf) throws Exception {
+        assertNotNull(bf);
+
+        assertTrue(1 <= bf.getFrequencyIndex());
+        long total = bf.getTotalDurationMs();
+        long screenOff = bf.getScreenOffDurationMs();
+        assertTrue(0 <= screenOff);
+        assertTrue(screenOff <= total);
+    }
+
+    private static void testUidProto(UidProto u, final int filterLevel) throws Exception {
+        assertNotNull(u);
+
+        assertTrue(0 <= u.getUid());
+
+        for (UidProto.Package p : u.getPackagesList()) {
+            assertNotNull(p.getName());
+            assertFalse(p.getName().isEmpty());
+
+            for (UidProto.Package.Service s : p.getServicesList()) {
+                assertNotNull(s.getName());
+                assertFalse(s.getName().isEmpty());
+                assertTrue(0 <= s.getStartDurationMs());
+                assertTrue(0 <= s.getStartCount());
+                assertTrue(0 <= s.getLaunchCount());
+            }
+        }
+
+        testControllerActivityProto(u.getBluetoothController());
+        testControllerActivityProto(u.getModemController());
+        testControllerActivityProto(u.getWifiController());
+
+        UidProto.BluetoothMisc bm = u.getBluetoothMisc();
+        testTimerProto(bm.getApportionedBleScan());
+        testTimerProto(bm.getBackgroundBleScan());
+        testTimerProto(bm.getUnoptimizedBleScan());
+        testTimerProto(bm.getBackgroundUnoptimizedBleScan());
+        assertTrue(0 <= bm.getBleScanResultCount());
+        assertTrue(0 <= bm.getBackgroundBleScanResultCount());
+
+        UidProto.Cpu c = u.getCpu();
+        assertTrue(0 <= c.getUserDurationMs());
+        assertTrue(0 <= c.getSystemDurationMs());
+        for (UidProto.Cpu.ByFrequency bf : c.getByFrequencyList()) {
+            testByFrequency(bf);
+        }
+        for (UidProto.Cpu.ByProcessState bps : c.getByProcessStateList()) {
+            assertTrue(UidProto.Cpu.ProcessState.getDescriptor().getValues()
+                    .contains(bps.getProcessState().getValueDescriptor()));
+            for (UidProto.Cpu.ByFrequency bf : bps.getByFrequencyList()) {
+                testByFrequency(bf);
+            }
+        }
+
+        testTimerProto(u.getAudio());
+        testTimerProto(u.getCamera());
+        testTimerProto(u.getFlashlight());
+        testTimerProto(u.getForegroundActivity());
+        testTimerProto(u.getForegroundService());
+        testTimerProto(u.getVibrator());
+        testTimerProto(u.getVideo());
+
+        for (UidProto.Job j : u.getJobsList()) {
+            if (filterLevel >= PRIVACY_EXPLICIT) {
+                assertNotNull(j.getName());
+                assertFalse(j.getName().isEmpty());
+            } else {
+                assertTrue(j.getName().isEmpty());
+            }
+            testTimerProto(j.getTotal());
+            testTimerProto(j.getBackground());
+        }
+
+        for (UidProto.JobCompletion jc : u.getJobCompletionList()) {
+            if (filterLevel >= PRIVACY_EXPLICIT) {
+                assertNotNull(jc.getName());
+                assertFalse(jc.getName().isEmpty());
+            } else {
+                assertTrue(jc.getName().isEmpty());
+            }
+            for (UidProto.JobCompletion.ReasonCount rc : jc.getReasonCountList()) {
+                assertTrue(0 <= rc.getCount());
+            }
+        }
+
+        UidProto.Network n = u.getNetwork();
+        assertTrue(0 <= n.getMobileBytesRx());
+        assertTrue(0 <= n.getMobileBytesTx());
+        assertTrue(0 <= n.getWifiBytesRx());
+        assertTrue(0 <= n.getWifiBytesTx());
+        assertTrue(0 <= n.getBtBytesRx());
+        assertTrue(0 <= n.getBtBytesTx());
+        assertTrue(0 <= n.getMobilePacketsRx());
+        assertTrue(0 <= n.getMobilePacketsTx());
+        assertTrue(0 <= n.getWifiPacketsRx());
+        assertTrue(0 <= n.getWifiPacketsTx());
+        assertTrue(0 <= n.getMobileActiveDurationMs());
+        assertTrue(0 <= n.getMobileActiveCount());
+        assertTrue(0 <= n.getMobileWakeupCount());
+        assertTrue(0 <= n.getWifiWakeupCount());
+        assertTrue(0 <= n.getMobileBytesBgRx());
+        assertTrue(0 <= n.getMobileBytesBgTx());
+        assertTrue(0 <= n.getWifiBytesBgRx());
+        assertTrue(0 <= n.getWifiBytesBgTx());
+        assertTrue(0 <= n.getMobilePacketsBgRx());
+        assertTrue(0 <= n.getMobilePacketsBgTx());
+        assertTrue(0 <= n.getWifiPacketsBgRx());
+        assertTrue(0 <= n.getWifiPacketsBgTx());
+
+        UidProto.PowerUseItem pui = u.getPowerUseItem();
+        assertTrue(0 <= pui.getComputedPowerMah());
+        assertTrue(0 <= pui.getScreenPowerMah());
+        assertTrue(0 <= pui.getProportionalSmearMah());
+
+        for (UidProto.Process p : u.getProcessList()) {
+            assertNotNull(p.getName());
+            assertFalse(p.getName().isEmpty());
+            assertTrue(0 <= p.getUserDurationMs());
+            assertTrue(0 <= p.getSystemDurationMs());
+            assertTrue(0 <= p.getForegroundDurationMs());
+            assertTrue(0 <= p.getStartCount());
+            assertTrue(0 <= p.getAnrCount());
+            assertTrue(0 <= p.getCrashCount());
+        }
+
+        for (UidProto.StateTime st : u.getStatesList()) {
+            assertTrue(UidProto.StateTime.State.getDescriptor().getValues()
+                    .contains(st.getState().getValueDescriptor()));
+            assertTrue(0 <= st.getDurationMs());
+        }
+
+        for (UidProto.Sensor s : u.getSensorsList()) {
+            testTimerProto(s.getApportioned());
+            testTimerProto(s.getBackground());
+        }
+
+        for (UidProto.Sync s : u.getSyncsList()) {
+            if (filterLevel >= PRIVACY_EXPLICIT) {
+                assertFalse(s.getName().isEmpty());
+            } else {
+                assertTrue(s.getName().isEmpty());
+            }
+            testTimerProto(s.getTotal());
+            testTimerProto(s.getBackground());
+        }
+
+        for (UidProto.UserActivity ua : u.getUserActivityList()) {
+            assertTrue(0 <= ua.getCount());
+        }
+
+        UidProto.AggregatedWakelock aw = u.getAggregatedWakelock();
+        long awPartial = aw.getPartialDurationMs();
+        long awBgPartial = aw.getBackgroundPartialDurationMs();
+        assertTrue(0 <= awBgPartial);
+        assertTrue(awBgPartial <= awPartial);
+
+        for (UidProto.Wakelock w : u.getWakelocksList()) {
+            // Unfortunately, apps can legitimately pass an empty string as the wakelock name, so we
+            // can't guarantee that wakelock names will be non-empty for EXPLICIT+.
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(w.getName().isEmpty());
+            }
+            testTimerProto(w.getFull());
+            testTimerProto(w.getPartial());
+            testTimerProto(w.getBackgroundPartial());
+            testTimerProto(w.getWindow());
+        }
+
+        for (UidProto.WakeupAlarm wa : u.getWakeupAlarmList()) {
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(wa.getName().isEmpty());
+            }
+            assertTrue(0 <= wa.getCount());
+        }
+
+        UidProto.Wifi w = u.getWifi();
+        assertTrue(0 <= w.getFullWifiLockDurationMs());
+        assertTrue(0 <= w.getRunningDurationMs());
+        testTimerProto(w.getApportionedScan());
+        testTimerProto(w.getBackgroundScan());
+
+        testTimerProto(u.getWifiMulticastWakelock());
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 57d64bb..434c132 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -15,14 +15,9 @@
  */
 package com.android.server.cts;
 
-import com.android.ddmlib.IShellOutputReceiver;
 import com.android.tradefed.log.LogUtil;
 
-import com.google.common.base.Charsets;
-
 import java.util.Random;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Test for "dumpsys batterystats -c
@@ -53,9 +48,9 @@
 
     private static final int STATE_TIME_TOP_INDEX = 4;
     private static final int STATE_TIME_FOREGROUND_SERVICE_INDEX = 5;
-    private static final int STATE_TIME_FOREGROUND_INDEX = 7;
-    private static final int STATE_TIME_BACKGROUND_INDEX = 8;
-    private static final int STATE_TIME_CACHED_INDEX = 9;
+    private static final int STATE_TIME_FOREGROUND_INDEX = 6;
+    private static final int STATE_TIME_BACKGROUND_INDEX = 7;
+    private static final int STATE_TIME_CACHED_INDEX = 10;
 
     private static final long TIME_SPENT_IN_TOP = 2000;
     private static final long TIME_SPENT_IN_FOREGROUND = 2000;
@@ -272,7 +267,7 @@
                 } else if (keyguardStateLines && line.contains("showing=")) {
                     screenAwake &= line.trim().endsWith("false");
                 } else if (keyguardStateLines && line.contains("screenState=")) {
-                    screenAwake &= line.trim().endsWith("2");
+                    screenAwake &= line.trim().endsWith("SCREEN_STATE_ON");
                 }
             }
             Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
@@ -385,6 +380,9 @@
         }
         batteryOnScreenOff();
         installPackage(DEVICE_SIDE_TEST_APK, true);
+        // Make the test app standby-active so it can run jobs immediately
+        getDevice().executeShellCommand("am set-standby-bucket "
+                + DEVICE_SIDE_TEST_PACKAGE + " active");
 
         // Background test.
         executeBackground(ACTION_JOB_SCHEDULE, 60_000);
@@ -405,6 +403,9 @@
         }
         batteryOnScreenOff();
         installPackage(DEVICE_SIDE_TEST_APK, true);
+        // Make the test app standby-active so it can run syncs immediately
+        getDevice().executeShellCommand("am set-standby-bucket "
+                + DEVICE_SIDE_TEST_PACKAGE + " active");
 
         // Background test.
         executeBackground(ACTION_SYNC, 60_000);
@@ -710,64 +711,6 @@
     }
 
     /**
-    * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
-    * the given tag.
-    * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
-    * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
-    */
-    private void checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
-        IShellOutputReceiver receiver = new IShellOutputReceiver() {
-            private final StringBuilder mOutputBuffer = new StringBuilder();
-            private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
-
-            @Override
-            public void addOutput(byte[] data, int offset, int length) {
-                if (!isCancelled()) {
-                    synchronized (mOutputBuffer) {
-                        String s = new String(data, offset, length, Charsets.UTF_8);
-                        mOutputBuffer.append(s);
-                        if (checkBufferForText()) {
-                            mIsCanceled.set(true);
-                        }
-                    }
-                }
-            }
-
-            private boolean checkBufferForText() {
-                if (mOutputBuffer.indexOf(text) > -1) {
-                    return true;
-                } else {
-                    // delete all old data (except the last few chars) since they don't contain text
-                    // (presumably large chunks of data will be added at a time, so this is
-                    // sufficiently efficient.)
-                    int newStart = mOutputBuffer.length() - text.length();
-                    if (newStart > 0) {
-                        mOutputBuffer.delete(0, newStart);
-                    }
-                    return false;
-                }
-            }
-
-            @Override
-            public boolean isCancelled() {
-                return mIsCanceled.get();
-            }
-
-            @Override
-            public void flush() {
-            }
-        };
-
-        try {
-            // Wait for at most maxTimeMs for logcat to display the desired text.
-            getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
-                    receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
-        } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
-            System.err.println(e);
-        }
-    }
-
-    /**
      * Returns the bytes downloaded for the wifi transfer download tests.
      * @param requestCode the output of executeForeground() or executeBackground() to identify in
      *                    the logcat the line associated with the desired download information
diff --git a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
index 9e32e1c..3c26066 100644
--- a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
@@ -16,10 +16,12 @@
 
 package com.android.server.cts;
 
-import android.service.fingerprint.FingerprintActionStatsProto;
-import android.service.fingerprint.FingerprintServiceDumpProto;
-import android.service.fingerprint.FingerprintUserStatsProto;
+import com.android.server.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.fingerprint.FingerprintUserStatsProto;
+import com.android.server.fingerprint.PerformanceStatsProto;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 
 
@@ -27,39 +29,49 @@
  * Test to check that the fingerprint service properly outputs its dump state.
  */
 public class FingerprintIncidentTest extends ProtoDumpTestCase {
-    /**
-     * Test that no fingerprints are registered.
-     *
-     * @throws Exception
-     */
-    public void testNoneRegistered() throws Exception {
+    public void testFingerprintServiceDump() throws Exception {
         // If the device doesn't support fingerprints, then pass.
-        if (!getDevice().hasFeature("android.hardware.fingerprint")) {
-            CLog.d("Bypass as android.hardware.fingerprint is not supported.");
+        if (!supportsFingerprint(getDevice())) {
             return;
         }
 
         final FingerprintServiceDumpProto dump =
                 getDump(FingerprintServiceDumpProto.parser(), "dumpsys fingerprint --proto");
 
-        // One of them
-        assertEquals(1, dump.getUsersCount());
+        verifyFingerprintServiceDumpProto(dump, PRIVACY_NONE);
+    }
 
-        final FingerprintUserStatsProto userStats = dump.getUsers(0);
-        assertEquals(0, userStats.getUserId());
-        assertEquals(0, userStats.getNumFingerprints());
+    static void verifyFingerprintServiceDumpProto(FingerprintServiceDumpProto dump, int filterLevel) {
+        // There should be at least one user.
+        assertTrue(1 <= dump.getUsersCount());
 
-        final FingerprintActionStatsProto normal = userStats.getNormal();
-        assertEquals(0, normal.getAccept());
-        assertEquals(0, normal.getReject());
-        assertEquals(0, normal.getAcquire());
-        assertEquals(0, normal.getLockout());
+        for (int i = 0; i < dump.getUsersCount(); ++i) {
+            final FingerprintUserStatsProto userStats = dump.getUsers(i);
+            assertTrue(0 <= userStats.getUserId());
+            assertTrue(0 <= userStats.getNumFingerprints());
 
-        final FingerprintActionStatsProto crypto = userStats.getCrypto();
-        assertEquals(0, crypto.getAccept());
-        assertEquals(0, crypto.getReject());
-        assertEquals(0, crypto.getAcquire());
-        assertEquals(0, crypto.getLockout());
+            final PerformanceStatsProto normal = userStats.getNormal();
+            assertTrue(0 <= normal.getAccept());
+            assertTrue(0 <= normal.getReject());
+            assertTrue(0 <= normal.getAcquire());
+            assertTrue(0 <= normal.getLockout());
+            assertTrue(0 <= normal.getPermanentLockout());
+
+            final PerformanceStatsProto crypto = userStats.getCrypto();
+            assertTrue(0 <= crypto.getAccept());
+            assertTrue(0 <= crypto.getReject());
+            assertTrue(0 <= crypto.getAcquire());
+            assertTrue(0 <= crypto.getLockout());
+            assertTrue(0 <= crypto.getPermanentLockout());
+        }
+    }
+
+    static boolean supportsFingerprint(ITestDevice device) throws DeviceNotAvailableException {
+        if (!device.hasFeature("android.hardware.fingerprint")) {
+            CLog.d("Bypass as android.hardware.fingerprint is not supported.");
+            return false;
+        }
+        return true;
     }
 }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
index 2e04e80..4454591 100644
--- a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
@@ -41,6 +41,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+        turnScreenOn();
         // Ensure that we have a starting point for our stats
         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".SimpleDrawFrameTests",
                 "testDrawTenFrames");
@@ -48,6 +49,11 @@
         killTestApp();
     }
 
+    private void turnScreenOn() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().executeShellCommand("wm dismiss-keyguard");
+    }
+
     public void testBasicDrawFrame() throws Exception {
         GraphicsStatsProto[] results = runDrawTest("testDrawTenFrames");
         GraphicsStatsProto statsBefore = results[0];
@@ -127,6 +133,7 @@
         GraphicsStatsProto statsBefore = fetchStats();
         assertNotNull(statsBefore);
         killTestApp();
+        turnScreenOn();
         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".SimpleDrawFrameTests",  testName);
         killTestApp();
         GraphicsStatsProto statsAfter = fetchStats();
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.java
new file mode 100644
index 0000000..4161c2f
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdIsolatedTest.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 com.android.server.cts;
+
+import android.os.IncidentProto;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Tests incidentd works when system_server is crashed.
+ */
+public class IncidentdIsolatedTest extends ProtoDumpTestCase {
+    private static final String TAG = "IncidentdIsolatedTest";
+
+    private static final String SYSTEM_SERVER = "system_server";
+    private static final String CMD_TOP = "top -b -n 1 -o cmd";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (execCommandAndFind(CMD_TOP, SYSTEM_SERVER) != null) {
+            CLog.logAndDisplay(LogLevel.INFO, "stop server");
+            getDevice().executeShellCommand("stop");
+            Thread.sleep(3000); // wait for 3 seconds to stop.
+            assertTrue("system_server failed to stop",
+                    !execCommandAndGet(CMD_TOP).contains(SYSTEM_SERVER));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (!execCommandAndGet(CMD_TOP).contains(SYSTEM_SERVER)) {
+            CLog.logAndDisplay(LogLevel.INFO, "start server");
+            getDevice().executeShellCommand("start");
+            Thread.sleep(10000); // wait for 10 seconds to boot.
+            execCommandAndFind(CMD_TOP, SYSTEM_SERVER);
+        }
+    }
+
+    public void testFullReportParsable() throws Exception {
+        final IncidentProto dump = getDump(IncidentProto.parser(), "incident 2>/dev/null");
+        assertTrue(dump.toByteArray().length > 0);
+        assertTrue(dump.hasSystemProperties());
+        assertTrue(dump.hasEventLogTagMap());
+        assertTrue(dump.hasPageTypeInfo());
+        assertTrue(dump.hasKernelWakeSources());
+        assertTrue(dump.hasCpuInfo());
+        assertTrue(dump.hasCpuFreq());
+        assertTrue(dump.hasProcessesAndThreads());
+        assertTrue(dump.hasBatteryType());
+    }
+
+    public void testReportInPrivateDirectory() throws Exception {
+        assertTrue(execCommandAndGet("ls /data/misc/incidents").isEmpty());
+        assertTrue(execCommandAndGet("incident -d 1000 2>/dev/null").isEmpty());
+        Thread.sleep(5000); // wait for report to finish.
+        execCommandAndFind("ls /data/misc/incidents", "incident-");
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
new file mode 100644
index 0000000..dd0788a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts;
+
+import com.android.server.fingerprint.FingerprintServiceDumpProto;
+
+import android.os.IncidentProto;
+import android.os.SystemPropertiesProto;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Tests incidentd reports filters fields correctly based on its privacy tags.
+ */
+public class IncidentdTest extends ProtoDumpTestCase {
+    private static final String TAG = "IncidentdTest";
+
+    public void testIncidentReportDump(final int filterLevel, final String dest) throws Exception {
+        final String destArg = dest == null || dest.isEmpty() ? "" : "-p " + dest;
+        final IncidentProto dump = getDump(IncidentProto.parser(), "incident " + destArg + " 2>/dev/null");
+
+        if (FingerprintIncidentTest.supportsFingerprint(getDevice())) {
+            FingerprintIncidentTest.verifyFingerprintServiceDumpProto(dump.getFingerprint(), filterLevel);
+        }
+
+        SettingsIncidentTest.verifySettingsServiceDumpProto(dump.getSettings(), filterLevel);
+
+        BatteryStatsIncidentTest.verifyBatteryStatsServiceDumpProto(dump.getBatterystats(), filterLevel);
+
+        if (BatteryIncidentTest.hasBattery(getDevice())) {
+            BatteryIncidentTest.verifyBatteryServiceDumpProto(dump.getBattery(), filterLevel);
+        }
+
+        PackageIncidentTest.verifyPackageServiceDumpProto(dump.getPackage(), filterLevel);
+
+        PowerIncidentTest.verifyPowerManagerServiceDumpProto(dump.getPower(), filterLevel);
+
+        AlarmManagerIncidentTest.verifyAlarmManagerServiceDumpProto(dump.getAlarm(), filterLevel);
+
+        MemInfoIncidentTest.verifyMemInfoDumpProto(dump.getMeminfo(), filterLevel);
+
+        JobSchedulerIncidentTest.verifyJobSchedulerServiceDumpProto(dump.getJobscheduler(), filterLevel);
+    }
+
+    // Splitting these into separate methods to make debugging easier.
+
+    public void testIncidentReportDumpAuto() throws Exception {
+        testIncidentReportDump(PRIVACY_AUTO, "A");
+        testIncidentReportDump(PRIVACY_AUTO, "AUTO");
+        testIncidentReportDump(PRIVACY_AUTO, "AUTOMATIC");
+    }
+
+    public void testIncidentReportDumpExplicit() throws Exception {
+        testIncidentReportDump(PRIVACY_EXPLICIT, "E" );
+        testIncidentReportDump(PRIVACY_EXPLICIT, "EXPLICIT");
+    }
+
+    public void testIncidentReportDumpLocal() throws Exception {
+        testIncidentReportDump(PRIVACY_LOCAL, "L" );
+        testIncidentReportDump(PRIVACY_LOCAL, "LOCAL");
+    }
+
+    public void testSystemPropertiesLocal() throws Exception {
+        final IncidentProto dump = getDump(IncidentProto.parser(),
+                "incident -p LOCAL 1000 2>/dev/null");
+
+        SystemPropertiesProto properties = dump.getSystemProperties();
+        // check local tagged data show up
+        assertTrue(properties.getExtraPropertiesCount() >= 1);
+        // check explicit tagged data show up
+        assertFalse(properties.getDalvikVm().getHeapmaxfree().isEmpty());
+        // check automatic tagged data show up
+        assertTrue(properties.getRo().getBuild().getVersion().getIncremental()
+            .equals(mCtsBuild.getBuildId()));
+    }
+
+    public void testSystemPropertiesExplicit() throws Exception {
+        final IncidentProto dump = getDump(IncidentProto.parser(),
+                "incident -p EXPLICIT 1000 2>/dev/null");
+
+        SystemPropertiesProto properties = dump.getSystemProperties();
+        // check local tagged data must not show up
+        assertTrue(properties.getExtraPropertiesCount() == 0);
+        // check explicit tagged data show up
+        assertFalse(properties.getDalvikVm().getHeapmaxfree().isEmpty());
+        // check automatic tagged data show up
+        CLog.i(mCtsBuild.getBuildId());
+        CLog.i(properties.getRo().getBuild().getVersion().getIncremental());
+        assertTrue(properties.getRo().getBuild().getVersion().getIncremental()
+            .equals(mCtsBuild.getBuildId()));
+    }
+
+    public void testSystemPropertiesAutomatic() throws Exception {
+        final IncidentProto dump = getDump(IncidentProto.parser(),
+                "incident -p AUTO 1000 2>/dev/null");
+
+        SystemPropertiesProto properties = dump.getSystemProperties();
+        // check local tagged data must not show up
+        assertTrue(properties.getExtraPropertiesCount() == 0);
+        // check explicit tagged data must not show up
+        assertTrue(properties.getDalvikVm().getHeapmaxfree().isEmpty());
+        // check automatic tagged data show up
+        assertTrue(properties.getRo().getBuild().getVersion().getIncremental()
+            .equals(mCtsBuild.getBuildId()));
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
new file mode 100644
index 0000000..e6465f8
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -0,0 +1,339 @@
+/*
+ * 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.server.cts;
+
+import android.app.JobParametersProto;
+import android.net.NetworkCapabilitiesProto;
+import android.net.NetworkRequestProto;
+import com.android.server.job.ConstantsProto;
+import com.android.server.job.DataSetProto;
+import com.android.server.job.JobPackageHistoryProto;
+import com.android.server.job.JobPackageTrackerDumpProto;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.JobStatusDumpProto;
+import com.android.server.job.JobStatusShortInfoProto;
+import com.android.server.job.StateControllerProto;
+
+/** Test to check that the jobscheduler service properly outputs its dump state. */
+public class JobSchedulerIncidentTest extends ProtoDumpTestCase {
+    public void testJobSchedulerServiceDump() throws Exception {
+        final JobSchedulerServiceDumpProto dump =
+                getDump(JobSchedulerServiceDumpProto.parser(), "dumpsys jobscheduler --proto");
+
+        verifyJobSchedulerServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyJobSchedulerServiceDumpProto(JobSchedulerServiceDumpProto dump, final int filterLevel) throws Exception {
+        testConstantsProto(dump.getSettings());
+
+        for (int u : dump.getStartedUsersList()) {
+            assertTrue(0 <= u);
+        }
+
+        for (JobSchedulerServiceDumpProto.RegisteredJob rj : dump.getRegisteredJobsList()) {
+            testJobStatusShortInfoProto(rj.getInfo(), filterLevel);
+            testJobStatusDumpProto(rj.getDump());
+        }
+
+        for (StateControllerProto c : dump.getControllersList()) {
+            testStateControllerProto(c, filterLevel);
+        }
+
+        for (JobSchedulerServiceDumpProto.PriorityOverride po : dump.getPriorityOverridesList()) {
+            assertTrue(0 <= po.getUid());
+        }
+
+        for (int buu : dump.getBackingUpUidsList()) {
+            assertTrue(0 <= buu);
+        }
+
+        testJobPackageHistoryProto(dump.getHistory(), filterLevel);
+
+        testJobPackageTrackerDumpProto(dump.getPackageTracker());
+
+        for (JobSchedulerServiceDumpProto.PendingJob pj : dump.getPendingJobsList()) {
+            testJobStatusShortInfoProto(pj.getInfo(), filterLevel);
+            testJobStatusDumpProto(pj.getDump());
+            assertTrue(0 <= pj.getEnqueuedDurationMs());
+        }
+
+        for (JobSchedulerServiceDumpProto.ActiveJob aj : dump.getActiveJobsList()) {
+            JobSchedulerServiceDumpProto.ActiveJob.InactiveJob ajIj = aj.getInactive();
+            assertTrue(0 <= ajIj.getTimeSinceStoppedMs());
+
+            JobSchedulerServiceDumpProto.ActiveJob.RunningJob ajRj = aj.getRunning();
+            testJobStatusShortInfoProto(ajRj.getInfo(), filterLevel);
+            assertTrue(0 <= ajRj.getRunningDurationMs());
+            assertTrue(0 <= ajRj.getTimeUntilTimeoutMs());
+            testJobStatusDumpProto(ajRj.getDump());
+            assertTrue(0 <= ajRj.getTimeSinceMadeActiveMs());
+            assertTrue(0 <= ajRj.getPendingDurationMs());
+        }
+
+        assertTrue(0 <= dump.getMaxActiveJobs());
+    }
+
+    private static void testConstantsProto(ConstantsProto c) throws Exception {
+        assertNotNull(c);
+
+        assertTrue(0 <= c.getMinIdleCount());
+        assertTrue(0 <= c.getMinChargingCount());
+        assertTrue(0 <= c.getMinBatteryNotLowCount());
+        assertTrue(0 <= c.getMinStorageNotLowCount());
+        assertTrue(0 <= c.getMinConnectivityCount());
+        assertTrue(0 <= c.getMinContentCount());
+        assertTrue(0 <= c.getMinReadyJobsCount());
+        assertTrue(0 <= c.getHeavyUseFactor());
+        assertTrue(0 <= c.getModerateUseFactor());
+        assertTrue(0 <= c.getFgJobCount());
+        assertTrue(0 <= c.getBgNormalJobCount());
+        assertTrue(0 <= c.getBgModerateJobCount());
+        assertTrue(0 <= c.getBgLowJobCount());
+        assertTrue(0 <= c.getBgCriticalJobCount());
+        assertTrue(0 <= c.getMaxStandardRescheduleCount());
+        assertTrue(0 <= c.getMaxWorkRescheduleCount());
+        assertTrue(0 <= c.getMinLinearBackoffTimeMs());
+        assertTrue(0 <= c.getMinExpBackoffTimeMs());
+        assertTrue(0 <= c.getStandbyHeartbeatTimeMs());
+        for (int sb : c.getStandbyBeatsList()) {
+            assertTrue(0 <= sb);
+        }
+    }
+
+    private static void testDataSetProto(DataSetProto ds) throws Exception {
+        assertNotNull(ds);
+
+        assertTrue(0 <= ds.getStartClockTimeMs());
+        assertTrue(0 <= ds.getElapsedTimeMs());
+        assertTrue(0 <= ds.getPeriodMs());
+
+        for (DataSetProto.PackageEntryProto pe : ds.getPackageEntriesList()) {
+            assertTrue(0 <= pe.getUid());
+
+            assertTrue(0 <= pe.getPendingState().getDurationMs());
+            assertTrue(0 <= pe.getPendingState().getCount());
+            assertTrue(0 <= pe.getActiveState().getDurationMs());
+            assertTrue(0 <= pe.getActiveState().getCount());
+            assertTrue(0 <= pe.getActiveTopState().getDurationMs());
+            assertTrue(0 <= pe.getActiveTopState().getCount());
+
+            for (DataSetProto.PackageEntryProto.StopReasonCount src : pe.getStopReasonsList()) {
+                assertTrue(JobParametersProto.CancelReason.getDescriptor().getValues()
+                        .contains(src.getReason().getValueDescriptor()));
+                assertTrue(0 <= src.getCount());
+            }
+        }
+        assertTrue(0 <= ds.getMaxConcurrency());
+        assertTrue(0 <= ds.getMaxForegroundConcurrency());
+    }
+
+    private static void testJobPackageHistoryProto(JobPackageHistoryProto jph, int filterLevel) throws Exception {
+        assertNotNull(jph);
+
+        for (JobPackageHistoryProto.HistoryEvent he : jph.getHistoryEventList()) {
+            assertTrue(JobPackageHistoryProto.Event.getDescriptor().getValues()
+                    .contains(he.getEvent().getValueDescriptor()));
+            assertTrue(0 <= he.getTimeSinceEventMs()); // Should be positive.
+            assertTrue(0 <= he.getUid());
+            assertTrue(JobParametersProto.CancelReason.getDescriptor().getValues()
+                    .contains(he.getStopReason().getValueDescriptor()));
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(he.getTag().isEmpty());
+            }
+        }
+    }
+
+    private static void testJobPackageTrackerDumpProto(JobPackageTrackerDumpProto jptd) throws Exception {
+        assertNotNull(jptd);
+
+        for (DataSetProto ds : jptd.getHistoricalStatsList()) {
+            testDataSetProto(ds);
+        }
+        testDataSetProto(jptd.getCurrentStats());
+    }
+
+    private static void testJobStatusShortInfoProto(JobStatusShortInfoProto jssi, final int filterLevel) throws Exception {
+        assertNotNull(jssi);
+
+        assertTrue(0 <= jssi.getCallingUid());
+        if (filterLevel == PRIVACY_AUTO) {
+            assertTrue(jssi.getBatteryName().isEmpty());
+        }
+    }
+
+    private static void testJobStatusDumpProto(JobStatusDumpProto jsd) throws Exception {
+        assertNotNull(jsd);
+
+        assertTrue(0 <= jsd.getCallingUid());
+        assertTrue(0 <= jsd.getSourceUid());
+        assertTrue(0 <= jsd.getSourceUserId());
+
+        JobStatusDumpProto.JobInfo ji = jsd.getJobInfo();
+        if (ji.getIsPeriodic()) {
+            assertTrue(0 <= ji.getPeriodIntervalMs());
+            assertTrue(0 <= ji.getPeriodFlexMs());
+        }
+        assertTrue(0 <= ji.getTriggerContentUpdateDelayMs());
+        assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
+        testNetworkRequestProto(ji.getRequiredNetwork());
+        assertTrue(0 <= ji.getTotalNetworkBytes());
+        assertTrue(0 <= ji.getMinLatencyMs());
+        assertTrue(0 <= ji.getMaxExecutionDelayMs());
+        JobStatusDumpProto.JobInfo.Backoff bp = ji.getBackoffPolicy();
+        assertTrue(JobStatusDumpProto.JobInfo.Backoff.Policy.getDescriptor().getValues()
+                .contains(bp.getPolicy().getValueDescriptor()));
+        assertTrue(0 <= bp.getInitialBackoffMs());
+
+        for (JobStatusDumpProto.Constraint c : jsd.getRequiredConstraintsList()) {
+            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+                    .contains(c.getValueDescriptor()));
+        }
+        for (JobStatusDumpProto.Constraint c : jsd.getSatisfiedConstraintsList()) {
+            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+                    .contains(c.getValueDescriptor()));
+        }
+        for (JobStatusDumpProto.Constraint c : jsd.getUnsatisfiedConstraintsList()) {
+            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+                    .contains(c.getValueDescriptor()));
+        }
+
+        for (JobStatusDumpProto.TrackingController tc : jsd.getTrackingControllersList()) {
+            assertTrue(JobStatusDumpProto.TrackingController.getDescriptor().getValues()
+                    .contains(tc.getValueDescriptor()));
+        }
+
+        for (JobStatusDumpProto.JobWorkItem jwi : jsd.getPendingWorkList()) {
+            assertTrue(0 <= jwi.getDeliveryCount());
+        }
+        for (JobStatusDumpProto.JobWorkItem jwi : jsd.getExecutingWorkList()) {
+            assertTrue(0 <= jwi.getDeliveryCount());
+        }
+
+        assertTrue(JobStatusDumpProto.Bucket.getDescriptor().getValues()
+                .contains(jsd.getStandbyBucket().getValueDescriptor()));
+
+        assertTrue(0 <= jsd.getEnqueueDurationMs());
+
+        assertTrue(0 <= jsd.getNumFailures());
+
+        assertTrue(0 <= jsd.getLastSuccessfulRunTime());
+        assertTrue(0 <= jsd.getLastFailedRunTime());
+    }
+
+    private static void testNetworkRequestProto(NetworkRequestProto nr) throws Exception {
+        assertNotNull(nr);
+
+        assertTrue(NetworkRequestProto.Type.getDescriptor().getValues()
+                .contains(nr.getType().getValueDescriptor()));
+        testNetworkCapabilitesProto(nr.getNetworkCapabilities());
+    }
+
+    private static void testNetworkCapabilitesProto(NetworkCapabilitiesProto nc) throws Exception {
+        assertNotNull(nc);
+
+        for (NetworkCapabilitiesProto.Transport t : nc.getTransportsList()) {
+            assertTrue(NetworkCapabilitiesProto.Transport.getDescriptor().getValues()
+                .contains(t.getValueDescriptor()));
+        }
+        for (NetworkCapabilitiesProto.NetCapability c : nc.getCapabilitiesList()) {
+            assertTrue(NetworkCapabilitiesProto.NetCapability.getDescriptor().getValues()
+                .contains(c.getValueDescriptor()));
+        }
+
+        assertTrue(0 <= nc.getLinkUpBandwidthKbps());
+        assertTrue(0 <= nc.getLinkDownBandwidthKbps());
+    }
+
+    private static void testStateControllerProto(StateControllerProto sc, int filterLevel) throws Exception {
+        assertNotNull(sc);
+
+        StateControllerProto.AppIdleController aic = sc.getAppIdle();
+        for (StateControllerProto.AppIdleController.TrackedJob tj : aic.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.BackgroundJobsController bjc = sc.getBackground();
+        for (StateControllerProto.BackgroundJobsController.TrackedJob tj : bjc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.BatteryController bc = sc.getBattery();
+        for (StateControllerProto.BatteryController.TrackedJob tj : bc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.ConnectivityController cc = sc.getConnectivity();
+        for (StateControllerProto.ConnectivityController.TrackedJob tj : cc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+            testNetworkRequestProto(tj.getRequiredNetwork());
+        }
+        StateControllerProto.ContentObserverController coc = sc.getContentObserver();
+        for (StateControllerProto.ContentObserverController.TrackedJob tj : coc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        for (StateControllerProto.ContentObserverController.Observer o : coc.getObserversList()) {
+            assertTrue(0 <= o.getUserId());
+
+            for (StateControllerProto.ContentObserverController.Observer.TriggerContentData tcd : o.getTriggersList()) {
+                if (filterLevel == PRIVACY_AUTO) {
+                    assertTrue(tcd.getUri().isEmpty());
+                }
+                for (StateControllerProto.ContentObserverController.Observer.TriggerContentData.JobInstance ji : tcd.getJobsList()) {
+                    testJobStatusShortInfoProto(ji.getInfo(), filterLevel);
+
+                    assertTrue(0 <= ji.getSourceUid());
+                    assertTrue(0 <= ji.getTriggerContentUpdateDelayMs());
+                    assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
+
+                    if (filterLevel == PRIVACY_AUTO) {
+                        for (String ca : ji.getChangedAuthoritiesList()) {
+                            assertTrue(ca.isEmpty());
+                        }
+                        for (String ca : ji.getChangedUrisList()) {
+                            assertTrue(ca.isEmpty());
+                        }
+                    }
+                }
+            }
+        }
+        StateControllerProto.DeviceIdleJobsController dijc = sc.getDeviceIdle();
+        for (StateControllerProto.DeviceIdleJobsController.TrackedJob tj : dijc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.IdleController ic = sc.getIdle();
+        for (StateControllerProto.IdleController.TrackedJob tj : ic.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.StorageController scr = sc.getStorage();
+        for (StateControllerProto.StorageController.TrackedJob tj : scr.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.TimeController tc = sc.getTime();
+        assertTrue(0 <=  tc.getNowElapsedRealtime());
+        assertTrue(0 <= tc.getTimeUntilNextDelayAlarmMs());
+        assertTrue(0 <= tc.getTimeUntilNextDeadlineAlarmMs());
+        for (StateControllerProto.TimeController.TrackedJob tj : tc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo(), filterLevel);
+            assertTrue(0 <= tj.getSourceUid());
+        }
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
new file mode 100644
index 0000000..3461af8
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.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 com.android.server.cts;
+
+import com.android.server.am.proto.MemInfoDumpProto;
+import com.android.server.am.proto.MemInfoDumpProto.AppData;
+import com.android.server.am.proto.MemInfoDumpProto.MemItem;
+import com.android.server.am.proto.MemInfoDumpProto.ProcessMemory;
+
+/** Test to check that ActivityManager properly outputs meminfo data. */
+public class MemInfoIncidentTest extends ProtoDumpTestCase {
+
+    public void testMemInfoDump() throws Exception {
+        final MemInfoDumpProto dump =
+                getDump(MemInfoDumpProto.parser(), "dumpsys meminfo -a --proto");
+
+        verifyMemInfoDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyMemInfoDumpProto(MemInfoDumpProto dump, final int filterLevel) throws Exception {
+        assertTrue(dump.getUptimeDurationMs() >= 0);
+        assertTrue(dump.getElapsedRealtimeMs() >= 0);
+
+        for (ProcessMemory pm : dump.getNativeProcessesList()) {
+            testProcessMemory(pm);
+        }
+
+        for (AppData ad : dump.getAppProcessesList()) {
+            testAppData(ad);
+        }
+
+        for (MemItem mi : dump.getTotalPssByProcessList()) {
+            testMemItem(mi);
+        }
+        for (MemItem mi : dump.getTotalPssByOomAdjustmentList()) {
+            testMemItem(mi);
+        }
+        for (MemItem mi : dump.getTotalPssByCategoryList()) {
+            testMemItem(mi);
+        }
+
+        assertTrue(0 <= dump.getTotalRamKb());
+        assertTrue(0 <= dump.getCachedPssKb());
+        assertTrue(0 <= dump.getCachedKernelKb());
+        assertTrue(0 <= dump.getFreeKb());
+        assertTrue(0 <= dump.getUsedPssKb());
+        assertTrue(0 <= dump.getUsedKernelKb());
+
+        assertTrue(0 <= dump.getLostRamKb());
+
+        assertTrue(0 <= dump.getTotalZramKb());
+        assertTrue(0 <= dump.getZramPhysicalUsedInSwapKb());
+        assertTrue(0 <= dump.getTotalZramSwapKb());
+
+        assertTrue(0 <= dump.getKsmSharingKb());
+        assertTrue(0 <= dump.getKsmSharedKb());
+        assertTrue(0 <= dump.getKsmUnsharedKb());
+        assertTrue(0 <= dump.getKsmVolatileKb());
+
+        assertTrue("Tuning_mb (" + dump.getTuningMb() + ") is not positive", 0 < dump.getTuningMb());
+        assertTrue(0 < dump.getTuningLargeMb());
+
+        assertTrue(0 <= dump.getOomKb());
+
+        assertTrue(0 < dump.getRestoreLimitKb());
+    }
+
+    private static void testProcessMemory(ProcessMemory pm) throws Exception {
+        assertNotNull(pm);
+
+        assertTrue(0 < pm.getPid());
+        // On most Linux machines, the max pid value is 32768 (=2^15), but, it can be set to any
+        // value up to 4194304 (=2^22) if necessary.
+        assertTrue(4194304 >= pm.getPid());
+
+        testHeapInfo(pm.getNativeHeap());
+        testHeapInfo(pm.getDalvikHeap());
+
+        for (ProcessMemory.MemoryInfo mi : pm.getOtherHeapsList()) {
+            testMemoryInfo(mi);
+        }
+        testMemoryInfo(pm.getUnknownHeap());
+        testHeapInfo(pm.getTotalHeap());
+
+        for (ProcessMemory.MemoryInfo mi : pm.getDalvikDetailsList()) {
+            testMemoryInfo(mi);
+        }
+
+        ProcessMemory.AppSummary as = pm.getAppSummary();
+        assertTrue(0 <= as.getJavaHeapPssKb());
+        assertTrue(0 <= as.getNativeHeapPssKb());
+        assertTrue(0 <= as.getCodePssKb());
+        assertTrue(0 <= as.getStackPssKb());
+        assertTrue(0 <= as.getGraphicsPssKb());
+        assertTrue(0 <= as.getPrivateOtherPssKb());
+        assertTrue(0 <= as.getSystemPssKb());
+        assertTrue(0 <= as.getTotalSwapPss());
+        assertTrue(0 <= as.getTotalSwapKb());
+    }
+
+    private static void testMemoryInfo(ProcessMemory.MemoryInfo mi) throws Exception {
+        assertNotNull(mi);
+
+        assertTrue(0 <= mi.getTotalPssKb());
+        assertTrue(0 <= mi.getCleanPssKb());
+        assertTrue(0 <= mi.getSharedDirtyKb());
+        assertTrue(0 <= mi.getPrivateDirtyKb());
+        assertTrue(0 <= mi.getSharedCleanKb());
+        assertTrue(0 <= mi.getPrivateCleanKb());
+        assertTrue(0 <= mi.getDirtySwapKb());
+        assertTrue(0 <= mi.getDirtySwapPssKb());
+    }
+
+    private static void testHeapInfo(ProcessMemory.HeapInfo hi) throws Exception {
+        assertNotNull(hi);
+
+        testMemoryInfo(hi.getMemInfo());
+        assertTrue(0 <= hi.getHeapSizeKb());
+        assertTrue(0 <= hi.getHeapAllocKb());
+        assertTrue(0 <= hi.getHeapFreeKb());
+    }
+
+    private static void testAppData(AppData ad) throws Exception {
+        assertNotNull(ad);
+
+        testProcessMemory(ad.getProcessMemory());
+
+        AppData.ObjectStats os = ad.getObjects();
+        assertTrue(0 <= os.getViewInstanceCount());
+        assertTrue(0 <= os.getViewRootInstanceCount());
+        assertTrue(0 <= os.getAppContextInstanceCount());
+        assertTrue(0 <= os.getActivityInstanceCount());
+        assertTrue(0 <= os.getGlobalAssetCount());
+        assertTrue(0 <= os.getGlobalAssetManagerCount());
+        assertTrue(0 <= os.getLocalBinderObjectCount());
+        assertTrue(0 <= os.getProxyBinderObjectCount());
+        assertTrue(0 <= os.getParcelMemoryKb());
+        assertTrue(0 <= os.getParcelCount());
+        assertTrue(0 <= os.getBinderObjectDeathCount());
+        assertTrue(0 <= os.getOpenSslSocketCount());
+        assertTrue(0 <= os.getWebviewInstanceCount());
+
+        AppData.SqlStats ss = ad.getSql();
+        assertTrue(0 <= ss.getMemoryUsedKb());
+        assertTrue(0 <= ss.getPagecacheOverflowKb());
+        assertTrue(0 <= ss.getMallocSizeKb());
+        for (AppData.SqlStats.Database d : ss.getDatabasesList()) {
+            assertTrue(0 <= d.getPageSize());
+            assertTrue(0 <= d.getDbSize());
+            assertTrue(0 <= d.getLookasideB());
+        }
+    }
+
+    private static void testMemItem(MemItem mi) throws Exception {
+        assertNotNull(mi);
+
+        assertTrue(0 <= mi.getPssKb());
+        assertTrue(0 <= mi.getSwapPssKb());
+
+        for (MemItem smi : mi.getSubItemsList()) {
+            testMemItem(smi);
+        }
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
index f5c13c2..bdf7dcc 100644
--- a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
@@ -374,16 +374,14 @@
                 assertNotNegative("TX bytes", bucket.getTxBytes());
                 assertNotNegative("TX packets", bucket.getTxPackets());
 
-// 10 was still too big?                // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
-                final long FACTOR = 4;
                 assertTrue(
                         String.format("# of bytes %d too small for # of packets %d",
                                 bucket.getRxBytes(), bucket.getRxPackets()),
-                        bucket.getRxBytes() >= bucket.getRxPackets() * FACTOR);
+                        bucket.getRxBytes() >= bucket.getRxPackets());
                 assertTrue(
                         String.format("# of bytes %d too small for # of packets %d",
                                 bucket.getTxBytes(), bucket.getTxPackets()),
-                        bucket.getTxBytes() >= bucket.getTxPackets() * FACTOR);
+                        bucket.getTxBytes() >= bucket.getTxPackets());
             }
         }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java
new file mode 100644
index 0000000..1d7a9ff
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.server.cts;
+
+import android.app.PolicyProto;
+import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationServiceDumpProto;
+import android.service.notification.NotificationRecordProto.State;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
+import android.service.notification.ZenMode;
+import android.service.notification.ZenModeProto;
+
+/**
+ * Test to check that the notification service properly outputs its dump state.
+ *
+ * make -j32 CtsIncidentHostTestCases
+ * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
+ */
+public class NotificationIncidentTest extends ProtoDumpTestCase {
+    // Constants from android.app.NotificationManager
+    private static final int IMPORTANCE_UNSPECIFIED = -1000;
+    private static final int IMPORTANCE_NONE = 0;
+    private static final int IMPORTANCE_MAX = 5;
+    private static final int VISIBILITY_NO_OVERRIDE = -1000;
+    // Constants from android.app.Notification
+    private static final int PRIORITY_MIN = -2;
+    private static final int PRIORITY_MAX = 2;
+    private static final int VISIBILITY_SECRET = -1;
+    private static final int VISIBILITY_PUBLIC = 1;
+    // These constants are those in PackageManager.
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    /**
+     * Tests that at least one notification is posted, and verify its properties are plausible.
+     */
+    public void testNotificationRecords() throws Exception {
+        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+                "dumpsys notification --proto");
+
+        assertTrue(dump.getRecordsCount() > 0);
+        boolean found = false;
+        for (NotificationRecordProto record : dump.getRecordsList()) {
+            if (record.getKey().contains("android")) {
+                found = true;
+                assertEquals(State.POSTED, record.getState());
+                assertTrue(record.getImportance() > IMPORTANCE_NONE);
+
+                // Ensure these fields exist, at least
+                record.getFlags();
+                record.getChannelId();
+                record.getSound();
+                record.getAudioAttributes();
+                record.getCanVibrate();
+                record.getCanShowLight();
+                record.getGroupKey();
+            }
+            assertTrue(
+                NotificationRecordProto.State.getDescriptor()
+                        .getValues()
+                        .contains(record.getState().getValueDescriptor()));
+        }
+
+        assertTrue(found);
+    }
+
+    /** Test valid values from the RankingHelper. */
+    public void testRankingConfig() throws Exception {
+        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+                "dumpsys notification --proto");
+
+        RankingHelperProto rhProto = dump.getRankingConfig();
+        for (RecordProto rp : rhProto.getRecordsList()) {
+            verifyRecordProto(rp);
+        }
+        for (RecordProto rp : rhProto.getRecordsRestoredWithoutUidList()) {
+            verifyRecordProto(rp);
+        }
+    }
+
+    private void verifyRecordProto(RecordProto rp) throws Exception {
+        assertTrue(!rp.getPackage().isEmpty());
+        assertTrue(rp.getUid() == -10000 || rp.getUid() >= 0);
+        assertTrue(rp.getImportance() == IMPORTANCE_UNSPECIFIED ||
+                (rp.getImportance() >= IMPORTANCE_NONE && rp.getImportance() <= IMPORTANCE_MAX));
+        assertTrue(rp.getPriority() >= PRIORITY_MIN && rp.getPriority() <= PRIORITY_MAX);
+        assertTrue(rp.getVisibility() == VISIBILITY_NO_OVERRIDE ||
+                (rp.getVisibility() >= VISIBILITY_SECRET &&
+                 rp.getVisibility() <= VISIBILITY_PUBLIC));
+    }
+
+    // Tests default state: zen mode is a valid/expected value
+    public void testZenMode() throws Exception {
+        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+                "dumpsys notification --proto");
+        ZenModeProto zenProto = dump.getZen();
+
+        switch(zenProto.getZenMode()) {
+            case ZEN_MODE_OFF:
+            case ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+            case ZEN_MODE_NO_INTERRUPTIONS:
+            case ZEN_MODE_ALARMS:
+                break;
+            default:
+                fail("Unexpected ZenMode value");
+                break;
+        }
+
+        PolicyProto policy = zenProto.getPolicy();
+        for (PolicyProto.Category c : policy.getPriorityCategoriesList()) {
+            assertTrue(PolicyProto.Category.getDescriptor().getValues()
+                    .contains(c.getValueDescriptor()));
+        }
+        assertTrue(PolicyProto.Sender.getDescriptor().getValues()
+                .contains(policy.getPriorityCallSender().getValueDescriptor()));
+        assertTrue(PolicyProto.Sender.getDescriptor().getValues()
+                .contains(policy.getPriorityMessageSender().getValueDescriptor()));
+        for (PolicyProto.SuppressedVisualEffect sve : policy.getSuppressedVisualEffectsList()) {
+            assertTrue(PolicyProto.SuppressedVisualEffect.getDescriptor().getValues()
+                    .contains(sve.getValueDescriptor()));
+        }
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
deleted file mode 100644
index 74f634d..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
+++ /dev/null
@@ -1,84 +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.server.cts;
-
-import android.service.notification.NotificationRecordProto;
-import android.service.notification.NotificationServiceDumpProto;
-import android.service.notification.State;
-import android.service.notification.ZenMode;
-import android.service.notification.ZenModeProto;
-
-/**
- * Test to check that the notification service properly outputs its dump state.
- *
- * make -j32 CtsIncidentHostTestCases
- * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
- */
-public class NotificationTest extends ProtoDumpTestCase {
-    // These constants are those in PackageManager.
-    public static final String FEATURE_WATCH = "android.hardware.type.watch";
-
-    /**
-     * Tests that at least one notification is posted, and verify its properties are plausible.
-     */
-    public void testNotificationRecords() throws Exception {
-        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
-                "dumpsys notification --proto");
-
-        assertTrue(dump.getRecordsCount() > 0);
-        boolean found = false;
-        for (NotificationRecordProto record : dump.getRecordsList()) {
-            if (record.getKey().contains("android")) {
-                found = true;
-                assertEquals(State.POSTED, record.getState());
-                assertTrue(record.getImportance() > 0 /* NotificationManager.IMPORTANCE_NONE */);
-
-                // Ensure these fields exist, at least
-                record.getFlags();
-                record.getChannelId();
-                record.getSound();
-                record.getSoundUsage();
-                record.getCanVibrate();
-                record.getCanShowLight();
-                record.getGroupKey();
-            }
-            assertTrue(State.SNOOZED != record.getState());
-        }
-
-        assertTrue(found);
-    }
-
-    // Tests default state: zen mode is a valid/expected value
-    public void testZenMode() throws Exception {
-        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
-                "dumpsys notification --proto");
-        ZenModeProto zenProto = dump.getZen();
-
-        switch(zenProto.getZenMode()) {
-            case ZEN_MODE_OFF:
-            case ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-            case ZEN_MODE_NO_INTERRUPTIONS:
-            case ZEN_MODE_ALARMS:
-                break;
-            default:
-                fail("Unexpected ZenMode value");
-                break;
-        }
-
-        zenProto.getPolicy();
-    }
-}
diff --git a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
index 18efcb0..a9ae705 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
@@ -34,12 +34,12 @@
         super.tearDown();
     }
 
-    private void assertPositive(String name, long value) {
+    private static void assertPositive(String name, long value) {
         if (value > 0) return;
         fail(name + " expected to be positive, but was: " + value);
     }
 
-    private void assertNotNegative(String name, long value) {
+    private static void assertNotNegative(String name, long value) {
         if (value >= 0) return;
         fail(name + " expected to be zero or positive, but was: " + value);
     }
@@ -63,15 +63,6 @@
         final PackageServiceDumpProto dump =
                 getDump(PackageServiceDumpProto.parser(), "dumpsys package --proto");
 
-        assertNotNull(dump.getVerifierPackage().getName());
-        assertPositive("verifier_package uid", dump.getVerifierPackage().getUid());
-        assertNotNull(dump.getSharedLibraries(0).getName());
-        if (dump.getSharedLibraries(0).getIsJar()) {
-            assertNotNull(dump.getSharedLibraries(0).getPath());
-        } else {
-            assertNotNull(dump.getSharedLibraries(0).getApk());
-        }
-        assertNotNull(dump.getFeatures(0).getName());
         PackageProto testPackage = null;
         for (PackageProto pkg : dump.getPackagesList()) {
             if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
@@ -99,6 +90,19 @@
                         == PackageProto.UserInfoProto.EnabledState
                                 .COMPONENT_ENABLED_STATE_DISABLED_USER);
 
+        verifyPackageServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyPackageServiceDumpProto(PackageServiceDumpProto dump, final int filterLevel) throws Exception {
+        assertNotNull(dump.getVerifierPackage().getName());
+        assertNotNull(dump.getSharedLibraries(0).getName());
+        if (dump.getSharedLibraries(0).getIsJar()) {
+            assertNotNull(dump.getSharedLibraries(0).getPath());
+        } else {
+            assertNotNull(dump.getSharedLibraries(0).getApk());
+        }
+        assertNotNull(dump.getFeatures(0).getName());
+
         PackageServiceDumpProto.SharedUserProto systemUser = null;
         for (PackageServiceDumpProto.SharedUserProto user : dump.getSharedUsersList()) {
             if (user.getUserId() == 1000) {
@@ -107,6 +111,12 @@
             }
         }
         assertNotNull(systemUser);
-        assertEquals(systemUser.getName(), "android.uid.system");
+        assertEquals("android.uid.system", systemUser.getName());
+
+        if (filterLevel == PRIVACY_AUTO) {
+            for (String msg : dump.getMessagesList()) {
+                assertTrue(msg.isEmpty());
+            }
+        }
     }
 }
diff --git a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
index 77c0163..03ba7b5 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
@@ -16,28 +16,41 @@
 
 package com.android.server.cts;
 
+import android.app.ProcessStateEnum;
+import android.content.IntentProto;
+import android.os.BatteryPluggedStateEnum;
 import android.os.LooperProto;
-import android.service.power.PowerServiceDumpProto;
-import android.service.power.PowerServiceSettingsAndConfigurationDumpProto;
+import android.os.PowerManagerInternalProto;
+import android.os.PowerManagerProto;
+import com.android.server.power.PowerManagerServiceDumpProto;
+import com.android.server.power.PowerServiceSettingsAndConfigurationDumpProto;
+import com.android.server.power.WakeLockProto;
 
 /** Test to check that the power manager properly outputs its dump state. */
 public class PowerIncidentTest extends ProtoDumpTestCase {
     private static final int SYSTEM_UID = 1000;
 
     public void testPowerServiceDump() throws Exception {
-        final PowerServiceDumpProto dump =
-                getDump(PowerServiceDumpProto.parser(), "dumpsys power --proto");
+        final PowerManagerServiceDumpProto dump =
+                getDump(PowerManagerServiceDumpProto.parser(), "dumpsys power --proto");
+
+        verifyPowerManagerServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifyPowerManagerServiceDumpProto(PowerManagerServiceDumpProto dump, int filterLevel) {
+        assertTrue(dump.getBatteryLevel() >= 0);
+        assertTrue(dump.getBatteryLevel() <= 100);
 
         assertTrue(
-                PowerServiceDumpProto.Wakefulness.getDescriptor()
+                PowerManagerInternalProto.Wakefulness.getDescriptor()
                         .getValues()
                         .contains(dump.getWakefulness().getValueDescriptor()));
         assertTrue(
-                PowerServiceDumpProto.PlugType.getDescriptor()
+                BatteryPluggedStateEnum.getDescriptor()
                         .getValues()
                         .contains(dump.getPlugType().getValueDescriptor()));
         assertTrue(
-                PowerServiceDumpProto.DockState.getDescriptor()
+                IntentProto.DockState.getDescriptor()
                         .getValues()
                         .contains(dump.getDockState().getValueDescriptor()));
 
@@ -47,22 +60,33 @@
         assertTrue(settingsAndConfiguration.getMaximumScreenDimDurationConfigMs() >= 0);
         assertTrue(settingsAndConfiguration.getMaximumScreenDimRatioConfig() > 0);
         assertTrue(settingsAndConfiguration.getScreenOffTimeoutSettingMs() > 0);
+        // Default value is -1.
+        assertTrue(settingsAndConfiguration.getSleepTimeoutSettingMs() >= -1);
         assertTrue(settingsAndConfiguration.getMaximumScreenOffTimeoutFromDeviceAdminMs() > 0);
+        // -1 is used to disable, so is valid.
+        assertTrue(settingsAndConfiguration.getUserActivityTimeoutOverrideFromWindowManagerMs() >= -1);
         final PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
                 brightnessLimits = settingsAndConfiguration.getScreenBrightnessSettingLimits();
-        assertTrue(brightnessLimits.getSettingMaximum() > 0);
+        int settingMax = brightnessLimits.getSettingMaximum();
+        int settingMin = brightnessLimits.getSettingMinimum();
+        assertTrue(settingMin >= 0);
+        assertTrue(settingMax > 0);
+        assertTrue("Brightness limit max setting (" + settingMax + ") is less than min setting (" + settingMin + ")",
+                settingMax >= settingMin);
         assertTrue(brightnessLimits.getSettingDefault() > 0);
-        assertTrue(brightnessLimits.getSettingForVrDefault() > 0);
 
-        final PowerServiceDumpProto.UidProto uid = dump.getUids(0);
+        final PowerManagerServiceDumpProto.UidStateProto uid = dump.getUidStates(0);
         assertEquals(uid.getUid(), SYSTEM_UID);
         assertEquals(uid.getUidString(), Integer.toString(SYSTEM_UID));
         assertTrue(uid.getIsActive());
-        assertFalse(uid.getIsProcessStateUnknown());
-        assertTrue(
-                PowerServiceDumpProto.UidProto.ProcessState.getDescriptor()
-                        .getValues()
-                        .contains(uid.getProcessState().getValueDescriptor()));
+
+        for (PowerManagerServiceDumpProto.UidStateProto us : dump.getUidStatesList()) {
+            assertTrue(0 <= us.getUid());
+            assertTrue(0 <= us.getNumWakeLocks());
+            assertTrue(ProcessStateEnum.getDescriptor()
+                    .getValues()
+                    .contains(us.getProcessState().getValueDescriptor()));
+        }
 
         final LooperProto looper = dump.getLooper();
         assertNotNull(looper.getThreadName());
@@ -70,5 +94,30 @@
         assertTrue(looper.getIdentityHashCode() > 0);
 
         assertTrue(dump.getSuspendBlockersCount() > 0);
+
+        // Check that times/durations are not incorrectly negative.
+        assertTrue(dump.getNotifyLongScheduledMs() >= 0);
+        assertTrue(dump.getNotifyLongDispatchedMs() >= 0);
+        assertTrue(dump.getNotifyLongNextCheckMs() >= 0);
+        assertTrue(dump.getLastWakeTimeMs() >= 0);
+        assertTrue(dump.getLastSleepTimeMs() >= 0);
+        assertTrue(dump.getLastUserActivityTimeMs() >= 0);
+        assertTrue(dump.getLastUserActivityTimeNoChangeLightsMs() >= 0);
+        assertTrue(dump.getLastInteractivePowerHintTimeMs() >= 0);
+        assertTrue(dump.getLastScreenBrightnessBoostTimeMs() >= 0);
+        // -1 is a valid value.
+        assertTrue(dump.getSleepTimeoutMs() >= -1);
+        assertTrue(dump.getScreenOffTimeoutMs() >= 0);
+        assertTrue(dump.getScreenDimDurationMs() >= 0);
+
+        for (WakeLockProto wl : dump.getWakeLocksList()) {
+            if (filterLevel == PRIVACY_AUTO) {
+                // Tag may or may not be empty, but for AUTO, it should always be empty.
+                assertTrue(wl.getTag().isEmpty());
+            }
+            assertTrue(0 <= wl.getAcqMs());
+            assertTrue(0 <= wl.getUid());
+            assertTrue(0 <= wl.getPid());
+        }
     }
 }
diff --git a/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java b/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java
new file mode 100644
index 0000000..24f3d2a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.cts;
+
+import android.service.print.PrintServiceDumpProto;
+import android.service.print.PrintSpoolerStateProto;
+import android.service.print.PrintUserStateProto;
+
+import com.android.tradefed.log.LogUtil;
+
+/**
+ * Test proto dump of print
+ */
+public class PrintProtoTest extends ProtoDumpTestCase {
+    /**
+     * Test that print dump is reasonable
+     *
+     * @throws Exception
+     */
+    public void testDump() throws Exception {
+        // If the device doesn't support printing, then pass.
+        if (!getDevice().hasFeature("android.software.print")) {
+            LogUtil.CLog.d("Bypass as android.software.print is not supported.");
+            return;
+        }
+
+        PrintServiceDumpProto dump = getDump(PrintServiceDumpProto.parser(),
+                "dumpsys print --proto");
+
+        assertTrue(dump.getUserStatesCount() > 0);
+
+        PrintUserStateProto userState = dump.getUserStatesList().get(0);
+        assertEquals(0, userState.getUserId());
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java b/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java
new file mode 100644
index 0000000..640da22
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
+
+/**
+ * Test proto dump of procstats
+ *
+ * $ make -j32 CtsIncidentHostTestCases
+ * $ cts-tradefed run cts-dev -m CtsIncidentHostTestCases \
+ * -t com.android.server.cts.ProcStatsProtoTest
+ */
+public class ProcStatsProtoTest extends ProtoDumpTestCase {
+
+    private static final String DEVICE_SIDE_TEST_APK = "CtsProcStatsProtoApp.apk";
+    private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.procstats";
+    private static final String TEST_APP_TAG = "ProcstatsAppRunningTest";
+    private static final String TEST_APP_LOG = "Procstats app is running";
+    private static final int WAIT_MS = 1000;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Test that procstats dump is reasonable, it installs procstats app and
+     * starts the activity, then asserts the procstats dump contains the package.
+     *
+     * @throws Exception
+     */
+    public void testDump() throws Exception {
+        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+        int retries = 3;
+        do {
+            getDevice().executeShellCommand(
+                "am start -n com.android.server.cts.procstats/.SimpleActivity");
+        } while (!checkLogcatForText(TEST_APP_TAG, TEST_APP_LOG, WAIT_MS) && retries-- > 0);
+
+        final ProcessStatsServiceDumpProto dump = getDump(ProcessStatsServiceDumpProto.parser(),
+                "dumpsys procstats --proto");
+
+        int N = dump.getProcstatsNow().getProcessStatsCount();
+        boolean containsTestApp = false;
+        for (int i = 0; i < N; i++) {
+            ProcessStatsProto ps = dump.getProcstatsNow().getProcessStats(i);
+            if (DEVICE_SIDE_TEST_PACKAGE.equals(ps.getProcess())) {
+                containsTestApp = true;
+            }
+        }
+
+        assertTrue(N > 0);
+        assertTrue(containsTestApp); // found test app in procstats dump
+    }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 5739bf4..4acf5ba 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -17,25 +17,29 @@
 package com.android.server.cts;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
+import com.google.common.base.Charsets;
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.MessageLite;
 import com.google.protobuf.Parser;
 
 import java.io.FileNotFoundException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -44,6 +48,11 @@
 import javax.annotation.Nullable;
 
 public class ProtoDumpTestCase extends DeviceTestCase implements IBuildReceiver {
+    protected static final int PRIVACY_AUTO = 0;
+    protected static final int PRIVACY_EXPLICIT = 1;
+    protected static final int PRIVACY_LOCAL = 2;
+    /** No privacy filtering has been done. All fields should be present. */
+    protected static final int PRIVACY_NONE = 3;
 
     protected IBuildInfo mCtsBuild;
 
@@ -133,7 +142,7 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                     result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
@@ -146,14 +155,21 @@
     }
 
     /**
+     * Execute the given command, and returns the output.
+     */
+    protected String execCommandAndGet(String command) throws Exception {
+        final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        return receiver.getOutput();
+    }
+
+    /**
      * Execute the given command, and find the given pattern with given flags and return the
      * resulting {@link Matcher}.
      */
     protected Matcher execCommandAndFind(String command, String pattern, int patternFlags)
             throws Exception {
-        final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        getDevice().executeShellCommand(command, receiver);
-        final String output = receiver.getOutput();
+        final String output = execCommandAndGet(command);
         final Matcher matcher = Pattern.compile(pattern, patternFlags).matcher(output);
         assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
         return matcher;
@@ -176,4 +192,66 @@
         assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
         return matcher.group(1);
     }
+
+    /**
+     * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
+     * the given tag.
+     * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
+     * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
+     * Returns true means the desired log line is found.
+     */
+    protected boolean checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
+        IShellOutputReceiver receiver = new IShellOutputReceiver() {
+            private final StringBuilder mOutputBuffer = new StringBuilder();
+            private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
+
+            @Override
+            public void addOutput(byte[] data, int offset, int length) {
+                if (!isCancelled()) {
+                    synchronized (mOutputBuffer) {
+                        String s = new String(data, offset, length, Charsets.UTF_8);
+                        mOutputBuffer.append(s);
+                        if (checkBufferForText()) {
+                            mIsCanceled.set(true);
+                        }
+                    }
+                }
+            }
+
+            private boolean checkBufferForText() {
+                if (mOutputBuffer.indexOf(text) > -1) {
+                    return true;
+                } else {
+                    // delete all old data (except the last few chars) since they don't contain text
+                    // (presumably large chunks of data will be added at a time, so this is
+                    // sufficiently efficient.)
+                    int newStart = mOutputBuffer.length() - text.length();
+                    if (newStart > 0) {
+                        mOutputBuffer.delete(0, newStart);
+                    }
+                    return false;
+                }
+            }
+
+            @Override
+            public boolean isCancelled() {
+                return mIsCanceled.get();
+            }
+
+            @Override
+            public void flush() {
+            }
+        };
+
+        try {
+            // Wait for at most maxTimeMs for logcat to display the desired text.
+            getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
+                    receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
+            return receiver.isCancelled();
+        } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
+            System.err.println(e);
+        }
+        return false;
+    }
+
 }
diff --git a/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
index 7ea00e1..4c465a9 100644
--- a/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
@@ -44,6 +44,10 @@
         SettingsServiceDumpProto dump = getDump(SettingsServiceDumpProto.parser(),
                 "dumpsys settings --proto");
 
+        verifySettingsServiceDumpProto(dump, PRIVACY_NONE);
+    }
+
+    static void verifySettingsServiceDumpProto(SettingsServiceDumpProto dump, final int filterLevel) throws Exception {
         assertTrue(dump.getUserSettingsCount() > 0);
 
         UserSettingsProto userSettings = dump.getUserSettings(0);
@@ -55,10 +59,10 @@
         verifySettings(dump.getGlobalSettings());
     }
 
-    private void verifySettings(GeneratedMessage settings) throws Exception {
+    private static void verifySettings(GeneratedMessage settings) throws Exception {
         verifySettings(getSettingProtos(settings));
 
-        final List<SettingsOperationProto> ops = invoke(settings, "getHistoricalOpList");
+        final List<SettingsOperationProto> ops = invoke(settings, "getHistoricalOperationsList");
         for (SettingsOperationProto op : ops) {
             assertTrue(op.getTimestamp() >= 0);
             assertNotNull(op.getOperation());
@@ -101,7 +105,7 @@
         return c == Integer.class ? int.class : c;
     }
 
-    private void verifySettings(List<SettingProto> settings) throws Exception {
+    private static void verifySettings(List<SettingProto> settings) throws Exception {
         assertFalse(settings.isEmpty());
 
         for (SettingProto setting : settings) {
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
index 307693a..06a68f7 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
@@ -50,12 +50,6 @@
     public static final String EXTRA_EVENT_SENDER = "event_sender";
 
     /**
-     * Intent extra key for Event parameters like
-     * {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
-     */
-    public static final String EXTRA_EVENT_PARAMS = "event_params";
-
-    /**
      * Intent extra key for what type a device event is. Values are {@link DeviceEventType#name()}.
      *
      * @see android.content.Intent#putExtra(String,String)
@@ -73,29 +67,6 @@
     public static final String EXTRA_EVENT_TIME = "event_time";
 
     /**
-     * Parameter for {@link DeviceEventType}.
-     */
-    public enum DeviceEventTypeParam {
-
-        /**
-         *  Param for {@link DeviceEventType#ON_START_INPUT}. Represents if IME is restarting.
-         */
-        ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting");
-
-        private final DeviceEventType mType;
-        private final String mName;
-
-        DeviceEventTypeParam(DeviceEventType type, String name) {
-            mType = type;
-            mName = name;
-        }
-
-        public String getName() {
-            return mName;
-        }
-    }
-
-    /**
      * Types of device event, a value of {@link #EXTRA_EVENT_TYPE}.
      */
     public enum DeviceEventType {
@@ -105,6 +76,11 @@
         ON_CREATE,
 
         /**
+         * {@link android.inputmethodservice.InputMethodService#onBindInput()} callback.
+         */
+        ON_BIND_INPUT,
+
+        /**
          * {@link android.inputmethodservice.InputMethodService#onStartInput(android.view.inputmethod.EditorInfo,boolean) onStartInput(EditorInfo,boolean}
          * callback.
          */
@@ -116,6 +92,11 @@
         ON_START_INPUT_VIEW,
 
         /**
+         * {@link android.inputmethodservice.InputMethodService#onUnbindInput()} callback.
+         */
+        ON_UNBIND_INPUT,
+
+        /**
          * {@link android.inputmethodservice.InputMethodService#onFinishInputView(boolean) onFinishInputView(boolean)}
          * callback.
          */
@@ -135,15 +116,5 @@
         /** Test start and end event types. */
         TEST_START,
         TEST_END,
-
-        /**
-         * {@link android.view.inputmethod.InputMethod#showSoftInput}
-         */
-        SHOW_SOFT_INPUT,
-
-        /**
-         * {@link android.view.inputmethod.InputMethod#hideSoftInput}
-         */
-        HIDE_SOFT_INPUT,
     }
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java
new file mode 100644
index 0000000..f575c55
--- /dev/null
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.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.inputmethodservice.cts.common;
+
+public class EditTextAppConstants {
+    // This is constants holding class, can't instantiate.
+    private EditTextAppConstants() {}
+
+    public static final String PACKAGE = "android.inputmethodservice.cts.edittextapp";
+    public static final String CLASS =   PACKAGE + ".MainActivity";
+    public static final String APK = "EditTextApp.apk";
+}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
index 9288051..172e1a7 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
@@ -58,9 +58,6 @@
         // This is constants holding class, can't instantiate.
         private EventTableConstants() {}
 
-        /** Column name of the table that holds Event extras in json format. */
-        public static final String EXTRAS = "extras";
-
         /** Name of the table in content provider and database. */
         public static final String NAME = "events";
 
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
index 61d5fc9..31368b0 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
@@ -52,6 +52,24 @@
     public static final String COMMAND_SWITCH_INPUT_METHOD = "switchInputMethod";
 
     /**
+     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#setInputMethodAndSubtype(String, InputMethodSubtype)} InputMethodService#setInputMethodAndSubtype(String imeId, InputMethodSubtype subtype)}.
+     * <ul>
+     * <li>argument {@code imeId} needs to be specified by {@link #EXTRA_ARG_STRING1}.</li>
+     * </ul>
+     */
+    public static final String COMMAND_SET_INPUT_METHOD_AND_SUBTYPE = "setInputMethodAndSubtype";
+
+    /**
+     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchToNextInputMethod(boolean)} InputMethodService#switchToNextInputMethod(boolean onlyCurrentIme)}.
+     */
+    public static final String COMMAND_SWITCH_TO_NEXT_INPUT = "switchToNextInput";
+
+    /**
+     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchToLastInputMethod()} InputMethodService#switchToLastInputMethod()}.
+     */
+    public static final String COMMAND_SWITCH_TO_LAST_INPUT = "switchToLastInput";
+
+    /**
      * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} InputMethodService#requestHideSelf(int flags)}.
      * <ul>
      * <li>argument {@code flags} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
index aa90e11..dae6db5 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
@@ -42,11 +42,9 @@
            "android.inputmethodservice.cts.devicetest.InputMethodServiceDeviceTest";
     public static final String TEST_CREATE_IME1 = "testCreateIme1";
     public static final String TEST_SWITCH_IME1_TO_IME2 = "testSwitchIme1ToIme2";
-    public static final String TEST_IME1_IS_NOT_CURRENT_IME = "testIme1IsNotCurrentIme";
-    public static final String TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1
-            = "testSearchView_giveFocusShowIme1";
-    public static final String TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1
-            = "testSearchView_setQueryHideIme1";
-    public static final String TEST_ON_START_INPUT_CALLED_ONCE_IME1
-            = "testOnStartInputCalledOnceIme1";
+    public static final String TEST_SET_INPUTMETHOD_AND_SUBTYPE = "testSetInputMethodAndSubtype";
+    public static final String TEST_SWITCH_NEXT_INPUT = "testSwitchToNextInputMethod";
+    public static final String TEST_SWITCH_LAST_INPUT = "testSwitchToLastInputMethod";
+    public static final String TEST_INPUT_UNBINDS_ON_IME_STOPPED = "testInputUnbindsOnImeStopped";
+    public static final String TEST_INPUT_UNBINDS_ON_APP_STOPPED = "testInputUnbindsOnAppStopped";
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
index b984aa1..3d46894 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
@@ -29,11 +29,6 @@
     // Copied from android.content.pm.PackageManager#FEATURE_INPUT_METHODS.
     public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
 
-    /** Command to check whether system has specified {@code featureName} feature. */
-    public static String hasFeature(final String featureName) {
-        return "cmd package has-feature " + featureName;
-    }
-
     private static final String SETTING_DEFAULT_IME = "secure default_input_method";
 
     /** Command to get ID of current IME. */
@@ -56,11 +51,20 @@
         return "ime disable " + imeId;
     }
 
+    /** Command to reset currently selected/enabled IMEs to the default ones. */
+    public static String resetImes() {
+        return "ime reset";
+    }
+
     /** Command to delete all records of IME event provider. */
     public static String deleteContent(final String contentUri) {
         return "content delete --uri " + contentUri;
     }
 
+    public static String uninstallPackage(String packageName) {
+        return "pm uninstall " + packageName;
+    }
+
     /**
      * Command to send broadcast {@code Intent}.
      *
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
index db742bf..9582a92 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_JAVA_RESOURCE_DIR := res
-LOCAL_JAVA_LIBRARY := android.test.runner
+LOCAL_JAVA_LIBRARY := android.test.runner.stubs
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     hamcrest hamcrest-library \
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
index f0efb94..94ba557 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
@@ -25,14 +25,5 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:background="@android:drawable/editbox_background"/>
-    <SearchView
-        android:id="@+id/search_view"
-        android:layout_below="@id/text_entry"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:iconifiedByDefault="false"
-        android:queryHint="hint"
-        android:inputType="textCapCharacters"
-        android:imeOptions="actionDone" />
 
 </RelativeLayout>
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
index e5613ff..73410e4 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
@@ -16,49 +16,49 @@
 
 package android.inputmethodservice.cts.devicetest;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 import static android.inputmethodservice.cts.DeviceEvent.isFrom;
 import static android.inputmethodservice.cts.DeviceEvent.isNewerThan;
 import static android.inputmethodservice.cts.DeviceEvent.isType;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
+        .ON_BIND_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
+
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
+        .ON_UNBIND_INPUT;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.ACTION_IME_COMMAND;
+import static android.inputmethodservice.cts.common.ImeCommandConstants
+        .COMMAND_SET_INPUT_METHOD_AND_SUBTYPE;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD;
+import static android.inputmethodservice.cts.common.ImeCommandConstants
+        .COMMAND_SWITCH_TO_LAST_INPUT;
+import static android.inputmethodservice.cts.common.ImeCommandConstants
+        .COMMAND_SWITCH_TO_NEXT_INPUT;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_COMMAND;
 import static android.inputmethodservice.cts.devicetest.BusyWaitUtils.pollingCheck;
 import static android.inputmethodservice.cts.devicetest.MoreCollectors.startingFrom;
 
-import android.app.Activity;
 import android.inputmethodservice.cts.DeviceEvent;
 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
+import android.inputmethodservice.cts.common.EditTextAppConstants;
 import android.inputmethodservice.cts.common.Ime1Constants;
 import android.inputmethodservice.cts.common.Ime2Constants;
 import android.inputmethodservice.cts.common.test.DeviceTestConstants;
 import android.inputmethodservice.cts.common.test.ShellCommandUtils;
 import android.inputmethodservice.cts.devicetest.SequenceMatcher.MatchResult;
 import android.os.SystemClock;
-import android.widget.SearchView;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.IntFunction;
 import java.util.function.Predicate;
 import java.util.stream.Collector;
-import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceDeviceTest {
@@ -70,19 +70,16 @@
     public void testCreateIme1() throws Throwable {
         final TestHelper helper = new TestHelper(getClass(), DeviceTestConstants.TEST_CREATE_IME1);
 
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.onCreate is called");
-
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
 
         pollingCheck(() -> helper.queryAllEvents()
+                        .collect(startingFrom(helper.isStartOfTest()))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
+                TIMEOUT, "CtsInputMethod1.onCreate is called");
+        pollingCheck(() -> helper.queryAllEvents()
                         .filter(isNewerThan(startActivityTime))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                        .findAny().isPresent(),
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
                 TIMEOUT, "CtsInputMethod1.onStartInput is called");
     }
 
@@ -92,19 +89,16 @@
         final TestHelper helper = new TestHelper(
                 getClass(), DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
 
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.onCreate is called");
-
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
 
         pollingCheck(() -> helper.queryAllEvents()
+                        .collect(startingFrom(helper.isStartOfTest()))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
+                TIMEOUT, "CtsInputMethod1.onCreate is called");
+        pollingCheck(() -> helper.queryAllEvents()
                         .filter(isNewerThan(startActivityTime))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                        .findAny().isPresent(),
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
                 TIMEOUT, "CtsInputMethod1.onStartInput is called");
 
         helper.findUiObject(R.id.text_entry).click();
@@ -121,8 +115,7 @@
                 TIMEOUT, "CtsInputMethod2 is current IME");
         pollingCheck(() -> helper.queryAllEvents()
                         .filter(isNewerThan(switchImeTime))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY)))
-                        .findAny().isPresent(),
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY))),
                 TIMEOUT, "CtsInputMethod1.onDestroy is called");
         pollingCheck(() -> helper.queryAllEvents()
                         .filter(isNewerThan(switchImeTime))
@@ -133,95 +126,132 @@
                 "CtsInputMethod2.onCreate and onStartInput are called in sequence");
     }
 
-    /** Test to check CtsInputMethod1 isn't current IME. */
     @Test
-    public void testIme1IsNotCurrentIme() throws Throwable {
-        final TestHelper helper =
-                new TestHelper(getClass(), DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
-
+    public void testSetInputMethodAndSubtype() throws Throwable {
+        final TestHelper helper = new TestHelper(
+                getClass(), DeviceTestConstants.TEST_SET_INPUTMETHOD_AND_SUBTYPE);
+        final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onStartInput is called");
         helper.findUiObject(R.id.text_entry).click();
 
+        final long setImeTime = SystemClock.uptimeMillis();
+        // call setInputMethodAndSubtype(IME2, null)
+        helper.shell(ShellCommandUtils.broadcastIntent(
+                ACTION_IME_COMMAND, Ime1Constants.PACKAGE,
+                "-e", EXTRA_COMMAND, COMMAND_SET_INPUT_METHOD_AND_SUBTYPE,
+                "-e", EXTRA_ARG_STRING1, Ime2Constants.IME_ID));
+        pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme())
+                        .equals(Ime2Constants.IME_ID),
+                TIMEOUT, "CtsInputMethod2 is current IME");
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(setImeTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY))),
+                TIMEOUT, "CtsInputMethod1.onDestroy is called");
+    }
+
+    @Test
+    public void testSwitchToNextInputMethod() throws Throwable {
+        final TestHelper helper = new TestHelper(
+                getClass(), DeviceTestConstants.TEST_SWITCH_NEXT_INPUT);
+        final long startActivityTime = SystemClock.uptimeMillis();
+        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onStartInput is called");
+        helper.findUiObject(R.id.text_entry).click();
+
+        pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme())
+                        .equals(Ime1Constants.IME_ID),
+                TIMEOUT, "CtsInputMethod1 is current IME");
+        helper.shell(ShellCommandUtils.broadcastIntent(
+                ACTION_IME_COMMAND, Ime1Constants.PACKAGE,
+                "-e", EXTRA_COMMAND, COMMAND_SWITCH_TO_NEXT_INPUT));
         pollingCheck(() -> !helper.shell(ShellCommandUtils.getCurrentIme())
                         .equals(Ime1Constants.IME_ID),
-                TIMEOUT,
-                "CtsInputMethod1 is uninstalled or disabled, and current IME becomes other IME");
+                TIMEOUT, "CtsInputMethod1 shouldn't be current IME");
     }
 
     @Test
-    public void testSearchView_giveFocusShowIme1() throws Throwable {
+    public void testSwitchToLastInputMethod() throws Throwable {
         final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
-
-        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
-        helper.findUiObject(R.id.search_view).click();
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(SHOW_SOFT_INPUT)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.showSoftInput is called");
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.onStartInput is called");
-    }
-
-    @Test
-    public void testSearchView_setQueryHideIme1() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
-
-        final Activity activity = helper.launchActivitySync(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
-        final SearchView searchView = (SearchView) activity.findViewById(R.id.search_view);
-        helper.findUiObject(R.id.search_view).click();
-        // test SearchView.onSubmitQuery() closes IME. Alternatively, onCloseClicked() closes IME.
-        // submits the query, should dismiss the inputMethod.
-        activity.runOnUiThread(() -> searchView.setQuery("test", true /* submit */));
-
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_FINISH_INPUT)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.onFinishInput is called");
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(HIDE_SOFT_INPUT)))
-                        .findAny().isPresent(),
-                TIMEOUT, "CtsInputMethod1.hideSoftInput is called");
-    }
-
-    @Test
-    public void testOnStartInputCalledOnceIme1() throws Exception {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
-
+                getClass(), DeviceTestConstants.TEST_SWITCH_LAST_INPUT);
+        final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
         helper.findUiObject(R.id.text_entry).click();
 
-        // we should've only one onStartInput call.
+        final String initialIme = helper.shell(ShellCommandUtils.getCurrentIme());
+        helper.shell(ShellCommandUtils.setCurrentIme(Ime2Constants.IME_ID));
         pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                        .findAny()
-                        .isPresent(),
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_START_INPUT))),
+                TIMEOUT, "CtsInputMethod2.onStartInput is called");
+        helper.shell(ShellCommandUtils.broadcastIntent(
+                ACTION_IME_COMMAND, Ime2Constants.PACKAGE,
+                "-e", EXTRA_COMMAND, COMMAND_SWITCH_TO_LAST_INPUT));
+        pollingCheck(() -> helper.shell(ShellCommandUtils.getCurrentIme())
+                        .equals(initialIme),
+                TIMEOUT, initialIme + " is current IME");
+    }
+
+    @Test
+    public void testInputUnbindsOnImeStopped() throws Throwable {
+        final TestHelper helper = new TestHelper(
+                getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED);
+        final long startActivityTime = SystemClock.uptimeMillis();
+        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
+        helper.findUiObject(R.id.text_entry).click();
+
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
                 TIMEOUT, "CtsInputMethod1.onStartInput is called");
-        List<DeviceEvent> startInputEvents = helper.queryAllEvents()
-                .collect(startingFrom(helper.isStartOfTest()))
-                .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                .collect(Collectors.toList());
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_BIND_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onBindInput is called");
 
-        assertEquals("CtsInputMethod1.onStartInput is called exactly once",
-                startInputEvents.size(),
-                1);
+        final long imeForceStopTime = SystemClock.uptimeMillis();
+        helper.shell(ShellCommandUtils.uninstallPackage(Ime1Constants.PACKAGE));
 
-        // check if that single event didn't cause IME restart.
-        final DeviceEvent event = startInputEvents.get(0);
-        Boolean isRestarting = DeviceEvent.getEventParamBoolean(
-                        DeviceEventTypeParam.ON_START_INPUT_RESTARTING, event);
-        assertTrue(isRestarting != null);
-        assertFalse(isRestarting);
+        helper.shell(ShellCommandUtils.setCurrentIme(Ime2Constants.IME_ID));
+        helper.findUiObject(R.id.text_entry).click();
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(imeForceStopTime))
+                        .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_START_INPUT))),
+                TIMEOUT, "CtsInputMethod2.onStartInput is called");
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(imeForceStopTime))
+                        .anyMatch(isFrom(Ime2Constants.CLASS).and(isType(ON_BIND_INPUT))),
+                TIMEOUT, "CtsInputMethod2.onBindInput is called");
+    }
+
+    @Test
+    public void testInputUnbindsOnAppStopped() throws Throwable {
+        final TestHelper helper = new TestHelper(
+                getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED);
+        final long startActivityTime = SystemClock.uptimeMillis();
+        helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS);
+
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onStartInput is called");
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_BIND_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onBindInput is called");
+
+        helper.shell(ShellCommandUtils.uninstallPackage(EditTextAppConstants.PACKAGE));
+
+        pollingCheck(() -> helper.queryAllEvents()
+                        .filter(isNewerThan(startActivityTime))
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_UNBIND_INPUT))),
+                TIMEOUT, "CtsInputMethod1.onUnBindInput is called");
     }
 
     /**
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
index a935f0b..59692a6 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
@@ -1,3 +1,19 @@
+/*
+ * 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.inputmethodservice.cts.devicetest;
 
 import java.util.function.BiConsumer;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
index 36b711e..996dad9 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
@@ -1,3 +1,19 @@
+/*
+ * 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.inputmethodservice.cts.devicetest;
 
 import java.util.ArrayList;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
index 7f6ba2e..a4b1aae 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
@@ -20,12 +20,9 @@
 import static android.inputmethodservice.cts.DeviceEvent.isType;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
 
-import android.app.Instrumentation;
-import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.inputmethodservice.cts.DeviceEvent;
 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
@@ -57,7 +54,6 @@
     private final ContentResolver mResolver;
     private final Context mTargetContext;
     private final UiDevice mUiDevice;
-    private final Instrumentation mInstrumentation;
 
     /**
      * Construct a helper object of specified test method.
@@ -70,8 +66,7 @@
         mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(), testMethod);
         mResolver = testContext.getContentResolver();
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiDevice = UiDevice.getInstance(mInstrumentation);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
     }
 
     /**
@@ -110,22 +105,6 @@
     }
 
     /**
-     * Launch test activity synchronously.
-     *
-     * @param packageName activity's app package name.
-     * @param className   activity's class name.
-     * @return instance of Activity
-     */
-    Activity launchActivitySync(final String packageName, final String className) {
-        final Intent intent = new Intent()
-                .setAction(Intent.ACTION_MAIN)
-                .setClassName(packageName, className)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        return mInstrumentation.startActivitySync(intent);
-    }
-
-    /**
      * Return all device events as {@link Stream}
      * @return {@link Stream<DeviceEvent>} of all device events.
      */
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk b/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk
new file mode 100644
index 0000000..96e4e8c
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/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 := 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)
+LOCAL_JAVA_RESOURCE_DIR := res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    CtsInputMethodServiceCommon \
+    CtsInputMethodServiceLib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := EditTextApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
new file mode 100755
index 0000000..e88ec8e
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.inputmethodservice.cts.edittextapp">
+    <application
+        android:label="@string/app_name">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.inputmethodservice.cts.edittextapp" />
+</manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/res/layout/activity_inputmethod_test.xml b/hostsidetests/inputmethodservice/deviceside/edittextapp/res/layout/activity_inputmethod_test.xml
new file mode 100644
index 0000000..5c7091a
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/res/layout/activity_inputmethod_test.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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:padding="10px">
+
+    <EditText
+        android:id="@+id/edit_text_entry"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@android:drawable/editbox_background">
+        <requestFocus />
+    </EditText>
+
+</RelativeLayout>
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/res/values/strings.xml b/hostsidetests/inputmethodservice/deviceside/edittextapp/res/values/strings.xml
new file mode 100644
index 0000000..091901d
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/res/values/strings.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.
+-->
+
+<resources>
+    <string name="app_name" translatable="false">EditTextApp</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java b/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java
new file mode 100644
index 0000000..86e9e0a
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.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.inputmethodservice.cts.edittextapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.EditText;
+import android.os.Bundle;
+import android.view.inputmethod.InputMethodManager;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+
+public class MainActivity extends Activity {
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_inputmethod_test);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
index 9fdcec6..f30a85b 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
@@ -19,7 +19,6 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-annotations \
-    json \
     CtsInputMethodServiceCommon
 
 LOCAL_MODULE_TAGS := tests
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
index ff412ef..b6098f9 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -17,20 +17,17 @@
 package android.inputmethodservice.cts;
 
 import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_PARAMS;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
 
-
 import android.content.ContentValues;
 import android.content.Intent;
 import android.database.Cursor;
 import android.inputmethodservice.cts.common.DeviceEventConstants;
 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
 import android.inputmethodservice.cts.common.test.TestInfo;
 import android.inputmethodservice.cts.db.Entity;
@@ -38,16 +35,8 @@
 import android.inputmethodservice.cts.db.Table;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.json.stream.JsonReader;
-import com.android.json.stream.JsonWriter;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -62,85 +51,21 @@
 
     public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
 
-    public static IntentBuilder builder() {
-        return new IntentBuilder();
-    }
-
     /**
-     * Builder to create an intent to send a device event.
-     * The built intent that has event {@code sender}, {@code type}, {@code paramsString}, time from
-     * {@link SystemClock#uptimeMillis()}, and target component of event receiver.
-     *
+     * Create an intent to send a device event.
+     * @param sender an event sender.
+     * @param type an event type defined at {@link DeviceEventType}.
+     * @return an intent that has event {@code sender}, {@code type}, time from
+     *         {@link SystemClock#uptimeMillis()}, and target component of event receiver.
      */
-    public static final class IntentBuilder {
-        String mSender;
-        DeviceEventType mType;
-        JsonWriter mJsonWriter;
-        StringWriter mStringWriter;
-
-        /**
-         * @param type an event type defined at {@link DeviceEventType}.
-         */
-        public IntentBuilder setType(DeviceEventType type) {
-            mType = type;
-            return this;
-        }
-
-        /**
-         * @param sender an event sender.
-         */
-        public void setSender(String sender) {
-            mSender = sender;
-        }
-
-        public IntentBuilder with(DeviceEventTypeParam eventParam, boolean value) {
-            appendToJson(eventParam, value);
-            return this;
-        }
-
-        public Intent build() {
-            Intent intent = new Intent()
-                    .setAction(ACTION_DEVICE_EVENT)
-                    .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
-                    .putExtra(EXTRA_EVENT_SENDER, mSender)
-                    .putExtra(EXTRA_EVENT_TYPE, mType.name())
-                    .putExtra(EXTRA_EVENT_PARAMS, getJsonString())
-                    .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
-
-            mJsonWriter = null;
-            mStringWriter = null;
-            return intent;
-        }
-
-        private String getJsonString() {
-            if (mJsonWriter == null) {
-                return "";
-            }
-            try {
-                mJsonWriter.endObject();
-                mJsonWriter.flush();
-            } catch (IOException e) {
-                throw new RuntimeException("IntentBuilder.getJsonString() failed.", e);
-            }
-            return mStringWriter.toString();
-        }
-
-        private void appendToJson(DeviceEventTypeParam eventParam, boolean value) {
-            final String key = eventParam.getName();
-            if (TextUtils.isEmpty(key)) {
-                return;
-            }
-            try {
-                if (mJsonWriter == null) {
-                    mStringWriter = new StringWriter();
-                    mJsonWriter = new JsonWriter(mStringWriter);
-                    mJsonWriter.beginObject();
-                }
-                mJsonWriter.name(key).value(value);
-            } catch (IOException e) {
-                throw new RuntimeException("IntentBuilder.appendToJson() failed.", e);
-            }
-        }
+    public static Intent newDeviceEventIntent(@NonNull final String sender,
+            @NonNull final DeviceEventType type) {
+        return new Intent()
+                .setAction(ACTION_DEVICE_EVENT)
+                .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
+                .putExtra(EXTRA_EVENT_SENDER, sender)
+                .putExtra(EXTRA_EVENT_TYPE, type.name())
+                .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
     }
 
     /**
@@ -168,16 +93,7 @@
                     "Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
         }
 
-        String paramsString = intent.getStringExtra(DeviceEventConstants.EXTRA_EVENT_PARAMS);
-        if (paramsString == null) {
-            paramsString = "";
-        }
-
-        return new DeviceEvent(
-                sender,
-                type,
-                paramsString,
-                intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
+        return new DeviceEvent(sender, type, intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
     }
 
     /**
@@ -242,21 +158,13 @@
     public final DeviceEventType type;
 
     /**
-     * Event parameters formatted as json string.
-     * e.g. {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
-     */
-    public final String paramsString;
-
-    /**
      * Event time, value is from {@link SystemClock#uptimeMillis()}.
      */
     public final long time;
 
-    private DeviceEvent(
-            final String sender, final DeviceEventType type, String paramsString, final long time) {
+    private DeviceEvent(final String sender, final DeviceEventType type, final long time) {
         this.sender = sender;
         this.type = type;
-        this.paramsString = paramsString;
         this.time = time;
     }
 
@@ -266,37 +174,6 @@
     }
 
     /**
-     * @param eventParam {@link DeviceEventTypeParam} to look for.
-     * @param event {@link DeviceEvent} to look in.
-     * @return Event parameter for provided key. If key is not found in
-     * {@link DeviceEvent#paramsString}, null is returned.
-     *
-     * TODO: Support other primitive and custom types.
-     */
-    @Nullable
-    public static Boolean getEventParamBoolean(
-            DeviceEventTypeParam eventParam, final DeviceEvent event) {
-        StringReader stringReader = new StringReader(event.paramsString);
-        JsonReader reader = new JsonReader(stringReader);
-
-        try {
-            reader.beginObject();
-            while (reader.hasNext()) {
-                String name = reader.nextName();
-                if (name.equals(eventParam.getName())) {
-                    Boolean value = reader.nextBoolean();
-                    reader.endObject();
-                    return value;
-                }
-            }
-            reader.endObject();
-        } catch (IOException e) {
-            throw new RuntimeException("DeviceEvent.getEventParamBoolean() failed.", e);
-        }
-        return null;
-    }
-
-    /**
      * Abstraction of device event table in database.
      */
     private static final class DeviceEventTable extends Table<DeviceEvent> {
@@ -306,19 +183,16 @@
         private final Field SENDER;
         private final Field TYPE;
         private final Field TIME;
-        private final Field PARAMS;
 
         private DeviceEventTable(final String name) {
             super(name, new Entity.Builder<DeviceEvent>()
                     .addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
-                    .addField(EventTableConstants.EXTRAS, Cursor.FIELD_TYPE_STRING)
                     .build());
             SENDER = getField(EventTableConstants.SENDER);
             TYPE = getField(EventTableConstants.TYPE);
             TIME = getField(EventTableConstants.TIME);
-            PARAMS = getField(EventTableConstants.EXTRAS);
         }
 
         @Override
@@ -326,7 +200,6 @@
             final ContentValues values = new ContentValues();
             SENDER.putString(values, event.sender);
             TYPE.putString(values, event.type.name());
-            PARAMS.putString(values, event.paramsString);
             TIME.putLong(values, event.time);
             return values;
         }
@@ -341,7 +214,6 @@
                 final DeviceEvent event = new DeviceEvent(
                         SENDER.getString(cursor),
                         DeviceEventType.valueOf(TYPE.getString(cursor)),
-                        PARAMS.getString(cursor),
                         TIME.getLong(cursor));
                 builder.accept(event);
                 if (DEBUG_STREAM) {
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
index b37873c..7896b6e 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -16,21 +16,24 @@
 
 package android.inputmethodservice.cts.ime;
 
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
+        .ON_BIND_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
 
+
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
+        .ON_UNBIND_INPUT;
+
+import android.content.Intent;
 import android.inputmethodservice.InputMethodService;
 import android.inputmethodservice.cts.DeviceEvent;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
 import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
-
-import android.os.ResultReceiver;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -45,33 +48,13 @@
             new ImeCommandReceiver<>();
     private String mLogTag;
 
-    private class CtsInputMethodImpl extends InputMethodImpl {
-        @Override
-        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            sendEvent(DeviceEvent.builder().setType(SHOW_SOFT_INPUT));
-            if (DEBUG) {
-                Log.d(mLogTag, "showSoftInput called");
-            }
-            super.showSoftInput(flags, resultReceiver);
-        }
-
-        @Override
-        public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
-            sendEvent(DeviceEvent.builder().setType(HIDE_SOFT_INPUT));
-            if (DEBUG) {
-                Log.d(mLogTag, "hideSoftInput called");
-            }
-            super.hideSoftInput(flags, resultReceiver);
-        }
-    }
-
     @Override
     public void onCreate() {
-        mLogTag = getClass().getSimpleName();
+        mLogTag = "CtsBaseInputMethod";//getClass().getSimpleName();
         if (DEBUG) {
             Log.d(mLogTag, "onCreate:");
         }
-        sendEvent(DeviceEvent.builder().setType(ON_CREATE));
+        sendEvent(ON_CREATE);
 
         super.onCreate();
 
@@ -79,16 +62,23 @@
     }
 
     @Override
+    public void onBindInput() {
+        if (DEBUG) {
+            Log.d(mLogTag, "onBindInput");
+        }
+        sendEvent(ON_BIND_INPUT);
+        super.onBindInput();
+    }
+
+    @Override
     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
         if (DEBUG) {
             Log.d(mLogTag, "onStartInput:"
                     + " editorInfo=" + editorInfo
                     + " restarting=" + restarting);
         }
+        sendEvent(ON_START_INPUT, editorInfo, restarting);
 
-        sendEvent(DeviceEvent.builder()
-                .setType(ON_START_INPUT)
-                .with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting));
         super.onStartInput(editorInfo, restarting);
     }
 
@@ -99,18 +89,26 @@
                     + " editorInfo=" + editorInfo
                     + " restarting=" + restarting);
         }
-
-        sendEvent(DeviceEvent.builder().setType(ON_START_INPUT_VIEW));
+        sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
 
         super.onStartInputView(editorInfo, restarting);
     }
 
     @Override
+    public void onUnbindInput() {
+        super.onUnbindInput();
+        if (DEBUG) {
+            Log.d(mLogTag, "onUnbindInput");
+        }
+        sendEvent(ON_UNBIND_INPUT);
+    }
+
+    @Override
     public void onFinishInputView(boolean finishingInput) {
         if (DEBUG) {
             Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
         }
-        sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT_VIEW));
+        sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
 
         super.onFinishInputView(finishingInput);
     }
@@ -120,7 +118,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onFinishInput:");
         }
-        sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT));
+        sendEvent(ON_FINISH_INPUT);
 
         super.onFinishInput();
     }
@@ -130,23 +128,13 @@
         if (DEBUG) {
             Log.d(mLogTag, "onDestroy:");
         }
-        sendEvent(DeviceEvent.builder().setType(ON_DESTROY));
+        sendEvent(ON_DESTROY);
 
         super.onDestroy();
 
         unregisterReceiver(mImeCommandReceiver);
     }
 
-    @Override
-    public AbstractInputMethodImpl onCreateInputMethodInterface() {
-        final CtsInputMethodImpl inputMethod = new CtsInputMethodImpl();
-        if (DEBUG) {
-            Log.d(mLogTag, "onCreateInputMethodInterface");
-        }
-
-        return inputMethod;
-    }
-
     //
     // Implementations of {@link ImeCommandCallbacks}.
     //
@@ -178,8 +166,10 @@
         }
     }
 
-   private void sendEvent(final DeviceEvent.IntentBuilder intentBuilder) {
-        intentBuilder.setSender(getClass().getName());
-        sendBroadcast(intentBuilder.build());
+    private void sendEvent(final DeviceEventType type, final Object... args) {
+        final String sender = getClass().getName();
+        final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
+        // TODO: Send arbitrary {@code args} in {@code intent}.
+        sendBroadcast(intent);
     }
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
index 23e5555..c926b3c 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
@@ -86,6 +86,19 @@
                 mIme.commandRequestHideSelf(flags);
                 return;
             }
+            case ImeCommandConstants.COMMAND_SET_INPUT_METHOD_AND_SUBTYPE: {
+                final String imeId = getString1(intent);
+                mIme.setInputMethodAndSubtype(imeId, null);
+                return;
+            }
+            case ImeCommandConstants.COMMAND_SWITCH_TO_NEXT_INPUT: {
+                mIme.switchToNextInputMethod(false);
+                return;
+            }
+            case ImeCommandConstants.COMMAND_SWITCH_TO_LAST_INPUT: {
+                mIme.switchToLastInputMethod();
+                return;
+            }
             default: {
                 throw new UnsupportedOperationException("Unknown IME command: " + command);
             }
diff --git a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index bf7e9a7..1575659 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -16,7 +16,8 @@
 -->
 
 <configuration description="Config for CTS Input Method Service host test cases">
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="inputmethod" />
     <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/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index f309158..8badded 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -22,13 +22,10 @@
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_COMPONENT;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.inputmethodservice.cts.common.EditTextAppConstants;
 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
 import android.inputmethodservice.cts.common.Ime1Constants;
 import android.inputmethodservice.cts.common.Ime2Constants;
@@ -36,33 +33,34 @@
 import android.inputmethodservice.cts.common.test.ShellCommandUtils;
 import android.inputmethodservice.cts.common.test.TestInfo;
 
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
 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;
 
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class InputMethodServiceLifecycleTest extends CompatibilityHostTestBase {
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
-    private String mDefaultImeId;
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class InputMethodServiceLifecycleTest extends BaseHostJUnit4Test {
+
+    private static final long TIMEOUT = TimeUnit.MICROSECONDS.toMillis(20000);
+    private static final long POLLING_INTERVAL = TimeUnit.MICROSECONDS.toMillis(200);
 
     @Before
     public void setUp() throws Exception {
         // Skip whole tests when DUT has no android.software.input_methods feature.
-        assumeTrue(Boolean.parseBoolean(shell(
-                ShellCommandUtils.hasFeature(ShellCommandUtils.FEATURE_INPUT_METHODS))));
-        mDefaultImeId = shell(ShellCommandUtils.getCurrentIme());
+        assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
         cleanUpTestImes();
         shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI));
     }
 
     @After
     public void tearDown() throws Exception {
-        shell(ShellCommandUtils.setCurrentIme(mDefaultImeId));
-        cleanUpTestImes();
+        shell(ShellCommandUtils.resetImes());
     }
 
     @Test
@@ -81,79 +79,106 @@
 
     @Test
     public void testUninstallCurrentIme() throws Exception {
-        installAndSetIme1();
-
-        final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
-        sendTestStartEvent(testIme1IsNotCurrentIme);
-        uninstallPackageIfExists(Ime1Constants.PACKAGE);
-        assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
-
-        // There should be a new IME that is different from the IME1
-        final String newIme = shell(ShellCommandUtils.getCurrentIme());
-        assertNotNull(newIme);
-        assertFalse(newIme.isEmpty());
-        assertNotEquals(newIme, Ime1Constants.IME_ID);
-    }
-
-    @Test
-    public void testDisableCurrentIme() throws Exception {
-        installAndSetIme1();
-
-        final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
-        sendTestStartEvent(testIme1IsNotCurrentIme);
-        shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
-        assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
-
-        // There should be a new IME that is different from the IME1
-        final String newIme = shell(ShellCommandUtils.getCurrentIme());
-        assertNotNull(newIme);
-        assertFalse(newIme.isEmpty());
-        assertNotEquals(newIme, Ime1Constants.IME_ID);
-    }
-
-    @Test
-    public void testSearchView_giveFocusShowIme() throws Exception {
-        installAndSetIme1();
-
-        final TestInfo testGiveFocusShowIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
-        sendTestStartEvent(testGiveFocusShowIme1);
-        assertTrue(runDeviceTestMethod(testGiveFocusShowIme1));
-    }
-
-    @Test
-    public void testSearchView_setQueryHideIme() throws Exception {
-        installAndSetIme1();
-
-        final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
-        sendTestStartEvent(testSetQueryHideIme1);
-        assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
-    }
-
-    @Test
-    public void testOnStartInputCalledOnce() throws Exception {
-        installAndSetIme1();
-
-        final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
-        sendTestStartEvent(testSetQueryHideIme1);
-        assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
-    }
-
-    private void installAndSetIme1() throws Exception {
         final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-            DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
+                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
         sendTestStartEvent(testCreateIme1);
         installPackage(Ime1Constants.APK, "-r");
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
         shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
         assertTrue(runDeviceTestMethod(testCreateIme1));
+
+        uninstallPackageIfExists(Ime1Constants.PACKAGE);
+        assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, TIMEOUT);
+    }
+
+    @Test
+    public void testDisableCurrentIme() throws Exception {
+        final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
+                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
+        sendTestStartEvent(testCreateIme1);
+        installPackage(Ime1Constants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+        assertTrue(runDeviceTestMethod(testCreateIme1));
+
+        shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
+        assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, TIMEOUT);
+    }
+
+    @Test
+    public void testSetInputMethodAndSubtype() throws Exception {
+        final TestInfo testSetInputMethod = new TestInfo(
+                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_SET_INPUTMETHOD_AND_SUBTYPE);
+        sendTestStartEvent(testSetInputMethod);
+        installPackage(Ime1Constants.APK, "-r");
+        installPackage(Ime2Constants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+
+        assertTrue(runDeviceTestMethod(testSetInputMethod));
+    }
+
+    @Test
+    public void testSwitchToNextInput() throws Exception {
+        final TestInfo testSwitchInputs = new TestInfo(
+                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_SWITCH_NEXT_INPUT);
+        sendTestStartEvent(testSwitchInputs);
+        installPackage(Ime1Constants.APK, "-r");
+        installPackage(Ime2Constants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        // Make sure that there is at least one more IME that specifies
+        // supportsSwitchingToNextInputMethod="true"
+        shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+
+        assertTrue(runDeviceTestMethod(testSwitchInputs));
+    }
+
+    @Test
+    public void testSwitchToLastInput() throws Exception {
+        final TestInfo testSwitchInputs = new TestInfo(
+                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_SWITCH_LAST_INPUT);
+        sendTestStartEvent(testSwitchInputs);
+        installPackage(Ime1Constants.APK, "-r");
+        installPackage(Ime2Constants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+
+        assertTrue(runDeviceTestMethod(testSwitchInputs));
+    }
+
+    @Test
+    public void testInputUnbindsOnImeStopped() throws Exception {
+        final TestInfo testUnbind = new TestInfo(
+                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED);
+        sendTestStartEvent(testUnbind);
+        installPackage(Ime1Constants.APK, "-r");
+        installPackage(Ime2Constants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+
+        assertTrue(runDeviceTestMethod(testUnbind));
+    }
+
+    @Test
+    public void testInputUnbindsOnAppStop() throws Exception {
+        final TestInfo testUnbind = new TestInfo(
+                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED);
+        sendTestStartEvent(testUnbind);
+        installPackage(Ime1Constants.APK, "-r");
+        installPackage(EditTextAppConstants.APK, "-r");
+        shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+        shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+
+        assertTrue(runDeviceTestMethod(testUnbind));
     }
 
     private void sendTestStartEvent(final TestInfo deviceTest) throws Exception {
@@ -179,8 +204,28 @@
     }
 
     private void uninstallPackageIfExists(final String packageName) throws Exception {
-        if (isPackageInstalled(packageName)) {
-            uninstallPackage(packageName);
+        if (isPackageInstalled(getDevice(), packageName)) {
+            uninstallPackage(getDevice(), packageName);
+        }
+    }
+
+    /**
+     * Makes sure that the given IME is not in the stored in the secure settings as the current IME.
+     *
+     * @param imeId IME ID to be monitored
+     * @param timeout timeout in millisecond
+     */
+    private void assertImeNotSelectedInSecureSettings(String imeId, long timeout) throws Exception {
+        while (true) {
+            if (timeout < 0) {
+                throw new TimeoutException(imeId + " is still the current IME even after "
+                        + timeout + " msec.");
+            }
+            if (!imeId.equals(shell(ShellCommandUtils.getCurrentIme()))) {
+                break;
+            }
+            Thread.sleep(POLLING_INTERVAL);
+            timeout -= POLLING_INTERVAL;
         }
     }
 }
diff --git a/hostsidetests/jdwpsecurity/AndroidTest.xml b/hostsidetests/jdwpsecurity/AndroidTest.xml
index 4b23d19..f062508 100644
--- a/hostsidetests/jdwpsecurity/AndroidTest.xml
+++ b/hostsidetests/jdwpsecurity/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS JDWP host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsJdwpSecurityHostTestCases.jar" />
diff --git a/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
index 706bdef..21a5cc7 100644
--- a/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
+++ b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/attaching/host/AndroidTest.xml b/hostsidetests/jvmti/attaching/host/AndroidTest.xml
index 41c161d..710b751 100644
--- a/hostsidetests/jvmti/attaching/host/AndroidTest.xml
+++ b/hostsidetests/jvmti/attaching/host/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI Attaching test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/attaching/host/src/android/jvmti/cts/JvmtiAttachingHostTest.java b/hostsidetests/jvmti/attaching/host/src/android/jvmti/cts/JvmtiAttachingHostTest.java
index e2408fc..becabe1 100644
--- a/hostsidetests/jvmti/attaching/host/src/android/jvmti/cts/JvmtiAttachingHostTest.java
+++ b/hostsidetests/jvmti/attaching/host/src/android/jvmti/cts/JvmtiAttachingHostTest.java
@@ -16,9 +16,6 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.NullOutputReceiver;
-import com.android.ddmlib.testrunner.ITestRunListener;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.ITestDevice;
@@ -30,10 +27,8 @@
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.ZipUtil;
+
 import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.zip.ZipFile;
 
@@ -79,7 +74,7 @@
                 throw new RuntimeException("Failed bind-time attaching", e);
             }
         });
-    };
+    }
 
     public void testJvmtiAttachEarly() throws Exception {
         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
@@ -105,7 +100,7 @@
                 throw new RuntimeException("Failed pre-bind attaching", e);
             }
         });
-    };
+    }
 
     public void testJvmtiAgentAppInternal() throws Exception {
         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
@@ -125,7 +120,7 @@
                 throw new RuntimeException("Failed agent-app attaching", e);
             }
         });
-    };
+    }
 
     public void testJvmtiAgentAppExternal() throws Exception {
         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
@@ -162,7 +157,7 @@
                 throw new RuntimeException("Failed agent-app attaching", e);
             }
         });
-    };
+    }
 
     private void runJvmtiAgentLoadTest(TestRun runner) throws Exception {
         final ITestDevice device = getDevice();
diff --git a/hostsidetests/jvmti/base/app/Android.mk b/hostsidetests/jvmti/base/app/Android.mk
index cb1d8ba..1c9b276 100644
--- a/hostsidetests/jvmti/base/app/Android.mk
+++ b/hostsidetests/jvmti/base/app/Android.mk
@@ -20,7 +20,7 @@
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_SDK_VERSION := current
 LOCAL_DEX_PREOPT := false
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     compatibility-device-util \
diff --git a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
index ce2969c..f80492c 100644
--- a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
+++ b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
@@ -15,13 +15,13 @@
 package android.jvmti.cts;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.ITestRunListener;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestLifeCycleReceiver;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -29,6 +29,7 @@
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.ZipUtil;
+
 import java.io.File;
 import java.util.LinkedList;
 import java.util.List;
@@ -190,7 +191,7 @@
         }
     }
 
-    private static class TestResults implements ITestRunListener {
+    private static class TestResults implements ITestLifeCycleReceiver {
         private boolean mFailed = false;
         private boolean mStarted = false;
         private final Runnable mOnStart;
@@ -216,22 +217,22 @@
         }
 
         @Override
-        public void testAssumptionFailure(TestIdentifier arg0, String arg1) {
+        public void testAssumptionFailure(TestDescription arg0, String arg1) {
             mFailed = true;
             mErrors.add(arg0.toString() + " " + arg1);
         }
 
         @Override
-        public void testEnded(TestIdentifier arg0, Map<String, String> arg1) {}
+        public void testEnded(TestDescription arg0, Map<String, String> arg1) {}
 
         @Override
-        public void testFailed(TestIdentifier arg0, String arg1) {
+        public void testFailed(TestDescription arg0, String arg1) {
             mFailed = true;
             mErrors.add(arg0.toString() + " " + arg1);
         }
 
         @Override
-        public void testIgnored(TestIdentifier arg0) {}
+        public void testIgnored(TestDescription arg0) {}
 
         @Override
         public void testRunEnded(long arg0, Map<String, String> arg1) {}
@@ -253,7 +254,7 @@
         public void testRunStopped(long arg0) {}
 
         @Override
-        public void testStarted(TestIdentifier arg0) {
+        public void testStarted(TestDescription arg0) {
             mStarted = true;
         }
     }
diff --git a/hostsidetests/jvmti/base/run-test-based-app/Android.mk b/hostsidetests/jvmti/base/run-test-based-app/Android.mk
index 7c8b417..f1fa480 100644
--- a/hostsidetests/jvmti/base/run-test-based-app/Android.mk
+++ b/hostsidetests/jvmti/base/run-test-based-app/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_SDK_VERSION := current
 LOCAL_DEX_PREOPT := false
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 LOCAL_STATIC_JAVA_LIBRARIES := CtsJvmtiDeviceAppBase
 LOCAL_STATIC_JAVA_LIBRARIES += run-test-jvmti-java
 LOCAL_PROGUARD_ENABLED := disabled
diff --git a/hostsidetests/jvmti/redefining/AndroidTest.xml b/hostsidetests/jvmti/redefining/AndroidTest.xml
index b1f9b01..1d04cb9 100644
--- a/hostsidetests/jvmti/redefining/AndroidTest.xml
+++ b/hostsidetests/jvmti/redefining/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
index 69cf790..42e8698 100644
--- a/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
index 3bec8b1..4b33b2d 100644
--- a/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
index 577c202..57f6027 100644
--- a/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
index 96ef377..a0e2e05 100644
--- a/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
index 217c127..38d2abe 100644
--- a/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
index f6662a9..af587f3 100644
--- a/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
index 94cc519..c2feb1a 100644
--- a/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
index efefdf6..32f135f 100644
--- a/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
index c6a4565..1832662 100644
--- a/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
index 7cb82f0..4ba486e 100644
--- a/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
index 7dff2cf..a24a927 100644
--- a/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
index f6ca1e7..efbc392 100644
--- a/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
index b8e03bf..02239d7 100644
--- a/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
index 0098aef..5726e10 100644
--- a/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
index 78f0927..7d9b72a 100644
--- a/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
index d23c3fc..eb22191 100644
--- a/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
index ff574cc..9c6d09b 100644
--- a/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
index 6c9f7dc..c2e21f1 100644
--- a/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
index cae6301..f9c586a 100644
--- a/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
index 35bea67..fa355f2 100644
--- a/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
index ea30af1..a50a51d 100644
--- a/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
index f09082a..5c02f07 100644
--- a/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
index 7755ea0..bf6f02c 100644
--- a/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
index c2b8745..2599ea5 100644
--- a/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
index 349cc80..2f8a15d 100644
--- a/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
index c691b40..aa2b9db 100644
--- a/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
index fe8f6df..2f35ce7 100644
--- a/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
index f200441..bad7710 100644
--- a/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
index 97a69cd..d13dbc9 100644
--- a/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
index 3c76674..dc7254a 100644
--- a/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
index 1f7a004..cd5586e 100644
--- a/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
index ef9a6e7..844b5a4 100644
--- a/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
index 0470da1..102db35 100644
--- a/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
index febd68b..33eedb0 100644
--- a/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
index ff85148..684833d 100644
--- a/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
index 3205637..170193e 100644
--- a/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/tagging/AndroidTest.xml b/hostsidetests/jvmti/tagging/AndroidTest.xml
index 403ca28..be75c08 100644
--- a/hostsidetests/jvmti/tagging/AndroidTest.xml
+++ b/hostsidetests/jvmti/tagging/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/media/AndroidTest.xml b/hostsidetests/media/AndroidTest.xml
index 483d969..e5818b5 100644
--- a/hostsidetests/media/AndroidTest.xml
+++ b/hostsidetests/media/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsMediaHostTestCases.jar" />
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
new file mode 100644
index 0000000..f5e2bda
--- /dev/null
+++ b/hostsidetests/media/OWNERS
@@ -0,0 +1,5 @@
+rachad@google.com
+elaurent@google.com
+lajos@google.com
+marcone@google.com
+sungsoo@google.com
diff --git a/hostsidetests/media/app/MediaSessionTest/Android.mk b/hostsidetests/media/app/MediaSessionTest/Android.mk
index fb0fe40..7303147 100644
--- a/hostsidetests/media/app/MediaSessionTest/Android.mk
+++ b/hostsidetests/media/app/MediaSessionTest/Android.mk
@@ -29,7 +29,10 @@
     $(call all-java-files-under, ../../common)
 
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
index 5ae6f96..18060e1 100644
--- a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
@@ -21,6 +21,8 @@
 
     <application
         android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+
         <service
             android:name=".MediaSessionManagerTest"
             android:label="MediaSessionManagerTest"
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
index 1e29eca..70c48f9 100644
--- a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
@@ -20,6 +20,8 @@
     android:versionCode="1"
     android:versionName="1.0">
 
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+
     <application android:label="@string/label">
         <service android:name=".MediaSessionTestHelperService">
             <intent-filter>
diff --git a/hostsidetests/media/bitstreams/AndroidTest.xml b/hostsidetests/media/bitstreams/AndroidTest.xml
index 6a60a65..1621aa7 100644
--- a/hostsidetests/media/bitstreams/AndroidTest.xml
+++ b/hostsidetests/media/bitstreams/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Sample host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="media-download-only" value="true" />
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 3c1f14e..07b496c 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -16,13 +16,14 @@
 
 package android.media.cts.bitstreams;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.AndroidJUnitTest;
 import com.android.tradefed.util.FileUtil;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
@@ -117,12 +118,12 @@
     private class MediaBitstreamsListener implements ITestInvocationListener {
 
         @Override
-        public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        public void testEnded(TestDescription test, Map<String, String> metrics) {
             mMetrics.putAll(metrics);
         }
 
         @Override
-        public void testFailed(TestIdentifier test, String trace) {
+        public void testFailed(TestDescription test, String trace) {
             mFailureStackTrace = trace;
         }
 
@@ -140,6 +141,8 @@
         instrTest.addIncludeFilter(fullTestName);
         instrTest.setTestTimeout(testTimeout);
         instrTest.setShellTimeout(shellTimeout);
+        // disable rerun mode to avoid collecting tests first then running.
+        instrTest.setRerunMode(false);
         for (Entry<String, String> e : getArgs().entrySet()) {
             instrTest.addInstrumentationArg(e.getKey(), e.getValue());
         }
diff --git a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
index 3e44c4f..d505394 100644
--- a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
+++ b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
@@ -18,14 +18,14 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.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;
 
@@ -275,7 +275,7 @@
         if (result.hasFailedTests()) {
             // Build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                     result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
diff --git a/hostsidetests/monkey/AndroidTest.xml b/hostsidetests/monkey/AndroidTest.xml
index 6479915..ad1507a 100644
--- a/hostsidetests/monkey/AndroidTest.xml
+++ b/hostsidetests/monkey/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS monkey host tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsMonkeyTestCases.jar" />
diff --git a/hostsidetests/multiuser/Android.mk b/hostsidetests/multiuser/Android.mk
index 9827ca7..e4c654d 100644
--- a/hostsidetests/multiuser/Android.mk
+++ b/hostsidetests/multiuser/Android.mk
@@ -22,7 +22,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed platform-test-annotations-host
 
 LOCAL_CTS_TEST_PACKAGE := android.host.multiuser
 
diff --git a/hostsidetests/multiuser/AndroidTest.xml b/hostsidetests/multiuser/AndroidTest.xml
index 27a2073..3c359c0 100644
--- a/hostsidetests/multiuser/AndroidTest.xml
+++ b/hostsidetests/multiuser/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS multiuser host tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsMultiUserHostTestCases.jar" />
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
index e510cae..3fcbba9 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
@@ -15,22 +15,22 @@
  */
 package android.host.multiuser;
 
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.After;
+import org.junit.Before;
 
 import java.util.ArrayList;
 
 /**
  * Base class for multi user tests.
  */
-public class BaseMultiUserTest extends DeviceTestCase implements IBuildReceiver {
+public class BaseMultiUserTest implements IDeviceTest {
     protected static final int USER_SYSTEM = 0; // From the UserHandle class.
 
-    protected IBuildInfo mBuildInfo;
-
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
     protected boolean mIsSplitSystemUser;
@@ -38,16 +38,10 @@
     /** Users we shouldn't delete in the tests */
     private ArrayList<Integer> mFixedUsers;
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
+    private ITestDevice mDevice;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertNotNull(mBuildInfo); // ensure build has been set before test is run.
-
+    @Before
+    public void setUp() throws Exception {
         mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
         mIsSplitSystemUser = checkIfSplitSystemUser();
         mPrimaryUserId = getDevice().getPrimaryUserId();
@@ -60,14 +54,23 @@
         removeTestUsers();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (getDevice().getCurrentUser() != mPrimaryUserId) {
             CLog.w("User changed during test. Switching back to " + mPrimaryUserId);
             getDevice().switchUser(mPrimaryUserId);
         }
         removeTestUsers();
-        super.tearDown();
+    }
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
     }
 
     protected int createRestrictedProfile(int userId)
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 5d4b89e..9a4f829 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
@@ -16,27 +16,50 @@
 
 package android.host.multiuser;
 
+import android.platform.test.annotations.Presubmit;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertTrue;
 
 /**
  * Test verifies that users can be created/switched to without error dialogs shown to the user
+ * Run: atest CreateUsersNoAppCrashesTest
  */
+@RunWith(DeviceJUnit4ClassRunner.class)
 public class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
     private int mInitialUserId;
     private static final long LOGCAT_POLL_INTERVAL_MS = 5000;
-    private static final long BOOT_COMPLETED_TIMEOUT_MS = 120000;
+    private static final long USER_SWITCH_COMPLETE_TIMEOUT_MS = 180000;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Rule public AppCrashRetryRule appCrashRetryRule = new AppCrashRetryRule();
+
+    @Before
+    public void setUp() throws Exception {
+        CLog.e("setup_CreateUsersNoAppCrashesTest");
         super.setUp();
         mInitialUserId = getDevice().getCurrentUser();
     }
 
+    @Presubmit
+    @Test
     public void testCanCreateGuestUser() throws Exception {
         if (!mSupportsMultiUser) {
             return;
@@ -45,33 +68,135 @@
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 true /* guest */,
                 false /* ephemeral */);
-        getDevice().executeAdbCommand("logcat", "-c"); // Reset log
-        assertTrue("Couldn't switch to user " + userId, getDevice().switchUser(userId));
-        Set<String> appErrors = new LinkedHashSet<>();
-        assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors=" + appErrors,
-                waitForBootCompleted(appErrors, userId));
-        assertTrue("App error dialog(s) are present: " + appErrors, appErrors.isEmpty());
-        assertTrue("Couldn't switch to user " + userId, getDevice().switchUser(mInitialUserId));
+        assertSwitchToNewUser(userId);
+        assertSwitchToUser(userId, mInitialUserId);
+
     }
 
-    private boolean waitForBootCompleted(Set<String> appErrors, int targetUserId)
-            throws DeviceNotAvailableException, InterruptedException {
+    @Presubmit
+    @Test
+    public void testCanCreateSecondaryUser() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        int userId = getDevice().createUser(
+                "TestUser_" + System.currentTimeMillis() /* name */,
+                false /* guest */,
+                false /* ephemeral */);
+        assertSwitchToNewUser(userId);
+        assertSwitchToUser(userId, mInitialUserId);
+    }
+
+    private void assertSwitchToNewUser(int toUserId) throws Exception {
+        final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
+        final Set<String> appErrors = new LinkedHashSet<>();
+        getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
+        assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
+                + appErrors, result);
+        if (!appErrors.isEmpty()) {
+            throw new AppCrashOnBootError(appErrors);
+        }
+    }
+
+    private void assertSwitchToUser(int fromUserId, int toUserId) throws Exception {
+        final String exitString = "Continue user switch oldUser #" + fromUserId + ", newUser #"
+                + toUserId;
+        final Set<String> appErrors = new LinkedHashSet<>();
+        getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
+        assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
+        if (!appErrors.isEmpty()) {
+            throw new AppCrashOnBootError(appErrors);
+        }
+    }
+
+    private boolean waitForUserSwitchComplete(Set<String> appErrors, int targetUserId,
+            String exitString) throws DeviceNotAvailableException, InterruptedException {
+        boolean mExitFound = false;
         long ti = System.currentTimeMillis();
-        while (System.currentTimeMillis() - ti < BOOT_COMPLETED_TIMEOUT_MS) {
-            String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d");
+        while (System.currentTimeMillis() - ti < USER_SWITCH_COMPLETE_TIMEOUT_MS) {
+            String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
+                    "ActivityManager:D", "AndroidRuntime:E", "*:S");
             Scanner in = new Scanner(logs);
             while (in.hasNextLine()) {
                 String line = in.nextLine();
                 if (line.contains("Showing crash dialog for package")) {
                     appErrors.add(line);
-                } else if (line.contains("Finished processing BOOT_COMPLETED for u" + targetUserId)) {
-                    return true;
+                } else if (line.contains(exitString)) {
+                    // Parse all logs in case crashes occur as a result of onUserChange callbacks
+                    mExitFound = true;
+                } else if (line.contains("FATAL EXCEPTION IN SYSTEM PROCESS")) {
+                    throw new IllegalStateException("System process crashed - " + line);
                 }
-
             }
             in.close();
+            if (mExitFound) {
+                if (!appErrors.isEmpty()) {
+                    CLog.w("App crash dialogs found: " + appErrors);
+                }
+                return true;
+            }
             Thread.sleep(LOGCAT_POLL_INTERVAL_MS);
         }
         return false;
     }
+
+    static class AppCrashOnBootError extends AssertionError {
+        private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("package ([^\\s]+)");
+        private Set<String> errorPackages;
+
+        AppCrashOnBootError(Set<String> errorLogs) {
+            super("App error dialog(s) are present: " + errorLogs);
+            this.errorPackages = errorLogsToPackageNames(errorLogs);
+        }
+
+        private static Set<String> errorLogsToPackageNames(Set<String> errorLogs) {
+            Set<String> result = new HashSet<>();
+            for (String line : errorLogs) {
+                Matcher matcher = PACKAGE_NAME_PATTERN.matcher(line);
+                if (matcher.find()) {
+                    result.add(matcher.group(1));
+                } else {
+                    throw new IllegalStateException("Unrecognized line " + line);
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Rule that retries the test if it failed due to {@link AppCrashOnBootError}
+     */
+    public static class AppCrashRetryRule implements TestRule {
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    Set<String> errors = evaluateAndReturnAppCrashes(base);
+                    if (errors.isEmpty()) {
+                        return;
+                    }
+                    CLog.e("Retrying due to app crashes: " + errors);
+                    // Fail only if same apps are crashing in both runs
+                    errors.retainAll(evaluateAndReturnAppCrashes(base));
+                    assertTrue("App error dialog(s) are present after 2 attempts: " + errors,
+                            errors.isEmpty());
+                }
+            };
+        }
+
+        private static Set<String> evaluateAndReturnAppCrashes(Statement base) throws Throwable {
+            try {
+                base.evaluate();
+            } catch (AppCrashOnBootError e) {
+                return e.errorPackages;
+            }
+            return new HashSet<>();
+        }
+    }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index 9a43534..55fab16 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -18,9 +18,16 @@
 import static com.android.tradefed.log.LogUtil.CLog;
 
 import com.android.ddmlib.Log;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
 public class CreateUsersPermissionTest extends BaseMultiUserTest {
 
+    @Test
     public void testCanCreateGuestUser() throws Exception {
         if (!mSupportsMultiUser) {
             return;
@@ -31,6 +38,7 @@
                 false /* ephemeral */);
     }
 
+    @Test
     public void testCanCreateEphemeralUser() throws Exception {
         if (!mSupportsMultiUser || !mIsSplitSystemUser) {
             return;
@@ -41,6 +49,7 @@
                 true /* ephemeral */);
     }
 
+    @Test
     public void testCanCreateRestrictedUser() throws Exception {
         if (!mSupportsMultiUser) {
             return;
@@ -48,6 +57,7 @@
         createRestrictedProfile(mPrimaryUserId);
     }
 
+    @Test
     public void testCantSetUserRestriction() throws Exception {
         if (getDevice().isAdbRoot()) {
             CLog.logAndDisplay(Log.LogLevel.WARN,
@@ -56,9 +66,9 @@
         }
         final String setRestriction = "pm set-user-restriction no_fun ";
         final String output = getDevice().executeShellCommand(setRestriction + "1");
-        final boolean isErrorOutput = output.startsWith("Error")
-                && output.contains("SecurityException");
-        assertTrue("Trying to set user restriction should fail with SecurityException. "
+        final boolean isErrorOutput = output.contains("SecurityException")
+            && output.contains("You need MANAGE_USERS permission");
+        Assert.assertTrue("Trying to set user restriction should fail with SecurityException. "
                 + "command output: " + output, isErrorOutput);
     }
 }
diff --git a/hostsidetests/net/AndroidTest.xml b/hostsidetests/net/AndroidTest.xml
index 4a2e2e3..c96fea4 100644
--- a/hostsidetests/net/AndroidTest.xml
+++ b/hostsidetests/net/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS net host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
     <target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/net/app/Android.mk b/hostsidetests/net/app/Android.mk
index f094f3f..c03e70b 100644
--- a/hostsidetests/net/app/Android.mk
+++ b/hostsidetests/net/app/Android.mk
@@ -23,6 +23,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ub-uiautomator \
         CtsHostsideNetworkTestsAidl
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsHostsideNetworkTestsApp
diff --git a/hostsidetests/net/app/AndroidManifest.xml b/hostsidetests/net/app/AndroidManifest.xml
index 7466cb8..2553f47 100644
--- a/hostsidetests/net/app/AndroidManifest.xml
+++ b/hostsidetests/net/app/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
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 d2c0873..0e141c0 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
@@ -34,7 +34,7 @@
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
         removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
         setAppIdle(false);
-        turnBatteryOff();
+        turnBatteryOn();
 
         registerBroadcastReceiver();
     }
@@ -48,7 +48,7 @@
         try {
             tearDownMeteredNetwork();
         } finally {
-            turnBatteryOn();
+            turnBatteryOff();
             setAppIdle(false);
         }
     }
@@ -127,6 +127,19 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
+        if (!isSupported()) return;
+
+        setAppIdle(true);
+        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);
+    }
+
     public void testBackgroundNetworkAccess_disabled() throws Exception {
         if (!isSupported()) return;
 
@@ -142,9 +155,9 @@
         // Check that app is paroled when charging
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
-        turnBatteryOn();
-        assertBackgroundNetworkAccess(true);
         turnBatteryOff();
+        assertBackgroundNetworkAccess(true);
+        turnBatteryOn();
         assertBackgroundNetworkAccess(false);
 
         // Check that app is restricted when not idle but power-save is on
@@ -154,11 +167,11 @@
         assertBackgroundNetworkAccess(false);
         // Use setBatterySaverMode API to leave power-save mode instead of plugging in charger
         setBatterySaverMode(false);
-        turnBatteryOn();
+        turnBatteryOff();
         assertBackgroundNetworkAccess(true);
 
         // And when no longer charging, it still has network access, since it's not idle
-        turnBatteryOff();
+        turnBatteryOn();
         assertBackgroundNetworkAccess(true);
     }
 
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 ce56d25..2f36b93 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
@@ -49,8 +49,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.cts.net.hostside.INetworkStateObserver;
-
 /**
  * Superclass for tests related to background network restrictions.
  */
@@ -87,7 +85,7 @@
     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     private static final int SECOND_IN_MS = 1000;
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
-    private static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
+    private static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
     private static final int PROCESS_STATE_TOP = 2;
 
     private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
@@ -108,6 +106,8 @@
 
     private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
 
+    protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
+
     protected Context mContext;
     protected Instrumentation mInstrumentation;
     protected ConnectivityManager mCm;
@@ -138,6 +138,7 @@
             enableLocation();
         }
         mSupported = setUpActiveNetworkMeteringState();
+        setAppIdle(false);
 
         Log.i(TAG, "Apps status on " + getName() + ":\n"
                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
@@ -147,6 +148,7 @@
 
     @Override
     protected void tearDown() throws Exception {
+        batteryReset();
         if (!mIsLocationOn) {
             disableLocation();
         }
@@ -751,6 +753,12 @@
                 + ". Full list: " + uids);
     }
 
+    protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
+            throws Exception {
+        Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
+        executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
+    }
+
     protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
             throws Exception {
         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
@@ -800,15 +808,22 @@
         assertPowerSaveModeExceptIdleWhitelist(packageName, false); // Sanity check
     }
 
-    protected void turnBatteryOff() throws Exception {
+    protected void turnBatteryOn() throws Exception {
         executeSilentShellCommand("cmd battery unplug");
+        executeSilentShellCommand("cmd battery set status "
+                + BatteryManager.BATTERY_STATUS_NOT_CHARGING);
         assertBatteryState(false);
     }
 
-    protected void turnBatteryOn() throws Exception {
-        executeSilentShellCommand("cmd battery reset");
+    protected void turnBatteryOff() throws Exception {
+        executeSilentShellCommand("cmd battery set ac " + BatteryManager.BATTERY_PLUGGED_AC);
+        executeSilentShellCommand("cmd battery set status "
+                + BatteryManager.BATTERY_STATUS_CHARGING);
         assertBatteryState(true);
+    }
 
+    private void batteryReset() throws Exception {
+        executeSilentShellCommand("cmd battery reset");
     }
 
     private void assertBatteryState(boolean pluggedIn) throws Exception {
@@ -839,11 +854,11 @@
     protected void setBatterySaverMode(boolean enabled) throws Exception {
         Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
         if (enabled) {
-            turnBatteryOff();
+            turnBatteryOn();
             executeSilentShellCommand("cmd power set-mode 1");
         } else {
             executeSilentShellCommand("cmd power set-mode 0");
-            turnBatteryOn();
+            turnBatteryOff();
         }
     }
 
@@ -853,12 +868,12 @@
 
         Log.i(TAG, "Setting Doze Mode to " + enabled);
         if (enabled) {
-            turnBatteryOff();
+            turnBatteryOn();
             turnScreenOff();
             executeShellCommand("dumpsys deviceidle force-idle deep");
         } else {
             turnScreenOn();
-            turnBatteryOn();
+            turnBatteryOff();
             executeShellCommand("dumpsys deviceidle unforce");
         }
         // Sanity check.
@@ -1004,7 +1019,8 @@
     private Intent getIntentForComponent(int type) {
         final Intent intent = new Intent();
         if (type == TYPE_COMPONENT_ACTIVTIY) {
-            intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS));
+            intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         } else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
             intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
                     .setFlags(1);
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 5248255..76332be 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.net.hostside;
 
+import android.os.SystemClock;
 import android.util.Log;
 
 /**
@@ -271,4 +272,55 @@
             setDozeMode(false);
         }
     }
+
+    public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
+        if (!isSupported()) {
+            return;
+        }
+        if (!isDozeModeEnabled()) {
+            Log.i(TAG, "Skipping " + getClass() + "." + getName()
+                    + "() because device does not support Doze Mode");
+            return;
+        }
+
+        setDozeMode(true);
+        setAppIdle(true);
+
+        try {
+            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);
+        }
+    }
+
+    public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
+        if (!isSupported()) {
+            return;
+        }
+
+        setBatterySaverMode(true);
+        setAppIdle(true);
+
+        try {
+            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);
+        }
+    }
 }
diff --git a/hostsidetests/net/app2/AndroidManifest.xml b/hostsidetests/net/app2/AndroidManifest.xml
index adf0045..acd393d 100644
--- a/hostsidetests/net/app2/AndroidManifest.xml
+++ b/hostsidetests/net/app2/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="com.android.cts.net.hostside.app2" >
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET" />
 
     <!--
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
index c6df893..7c9ce8f 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -19,13 +19,13 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.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.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -150,7 +150,7 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                 result.getTestResults().entrySet()) {
                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index bf3fc08..fe9d36c 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -146,6 +146,11 @@
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    public void testAppIdleMetered_tempWhitelisted() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+                "testBackgroundNetworkAccess_tempWhitelisted");
+    }
+
     public void testAppIdleMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
@@ -166,6 +171,11 @@
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+                "testBackgroundNetworkAccess_tempWhitelisted");
+    }
+
     public void testAppIdleNonMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
@@ -261,6 +271,16 @@
                 "testDozeAndAppIdle_powerSaveWhitelists");
     }
 
+    public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+                "testAppIdleAndDoze_tempPowerSaveWhitelists");
+    }
+
+    public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+                "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
+    }
+
     /*******************
      * Helper methods. *
      *******************/
diff --git a/hostsidetests/numberblocking/AndroidTest.xml b/hostsidetests/numberblocking/AndroidTest.xml
index 9a6d805..6ebc522 100644
--- a/hostsidetests/numberblocking/AndroidTest.xml
+++ b/hostsidetests/numberblocking/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS number blocking test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="abuse" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/numberblocking/app/Android.mk b/hostsidetests/numberblocking/app/Android.mk
index cfbccbe..fc0f4a0 100644
--- a/hostsidetests/numberblocking/app/Android.mk
+++ b/hostsidetests/numberblocking/app/Android.mk
@@ -27,7 +27,9 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 261c29d..2e61a61 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 3b2e027..e36b1c7 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -26,12 +26,8 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.util.AbiUtils;
 
-import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Scanner;
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index ad27051..5097df6 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -18,12 +18,13 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 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;
 
@@ -494,9 +495,9 @@
         if (result.hasFailedTests()) {
             // build a meaningful error message
             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
                     result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestResult.TestStatus.PASSED)) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
                     errorBuilder.append(resultEntry.getKey().toString());
                     errorBuilder.append(":\n");
                     errorBuilder.append(resultEntry.getValue().getStackTrace());
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
index cbeb342..a1550a6 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <uses-static-library
                 android:name="foo.bar.lib"
                 android:version="1"
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
index dca3699..dbadeb9 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
@@ -187,7 +187,7 @@
         // Make sure we see the lib we depend on via getting installed packages
         List<PackageInfo> installedPackages = InstrumentationRegistry.getInstrumentation()
                 .getContext().getPackageManager().getInstalledPackages(0);
-        int usedLibraryVersionCode = -1;
+        long usedLibraryVersionCode = -1;
         for (PackageInfo installedPackage : installedPackages) {
             if (STATIC_LIB_PROVIDER_PKG.equals(installedPackage.packageName)) {
                 if (usedLibraryVersionCode != -1) {
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index c87969b..777479c 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
index f32c523..0fbf3b9 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
@@ -16,14 +16,14 @@
 
 package android.sample.cts;
 
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.runner.RunWith;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Test that collects test results from test package android.sample.cts.app2.
@@ -33,7 +33,7 @@
  * collected from the hostside and reported accordingly.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class SampleHostJUnit4DeviceTest extends CompatibilityHostTestBase {
+public class SampleHostJUnit4DeviceTest extends BaseHostJUnit4Test {
 
     private static final String TEST_PKG = "android.sample.cts.app2";
     private static final String TEST_CLASS = TEST_PKG + "." + "SampleDeviceTest";
@@ -65,7 +65,7 @@
 
     @After
     public void tearDown() throws Exception {
-        uninstallPackage(TEST_PKG);
+        uninstallPackage(getDevice(), TEST_PKG);
     }
 
 }
diff --git a/hostsidetests/seccomp/Android.mk b/hostsidetests/seccomp/Android.mk
new file mode 100644
index 0000000..2c1c077
--- /dev/null
+++ b/hostsidetests/seccomp/Android.mk
@@ -0,0 +1,32 @@
+# 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_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
+
+LOCAL_MODULE := CtsSeccompHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/seccomp/AndroidTest.xml b/hostsidetests/seccomp/AndroidTest.xml
new file mode 100644
index 0000000..cbfd1c4
--- /dev/null
+++ b/hostsidetests/seccomp/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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 Sseccomp host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSeccompDeviceApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsSeccompHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/seccomp/app/Android.mk b/hostsidetests/seccomp/app/Android.mk
new file mode 100644
index 0000000..4dd5ad2
--- /dev/null
+++ b/hostsidetests/seccomp/app/Android.mk
@@ -0,0 +1,52 @@
+# 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)
+
+# 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)
+
+# Include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+          android-support-test \
+          compatibility-device-util \
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+          libctsseccomp_jni \
+          libcts_jni \
+          libnativehelper_compat_libc++ \
+
+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 := CtsSeccompDeviceApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/seccomp/app/AndroidManifest.xml b/hostsidetests/seccomp/app/AndroidManifest.xml
new file mode 100644
index 0000000..b8e97e3
--- /dev/null
+++ b/hostsidetests/seccomp/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.seccomp.cts.app">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.seccomp.cts.app" />
+
+</manifest>
diff --git a/hostsidetests/seccomp/app/jni/Android.mk b/hostsidetests/seccomp/app/jni/Android.mk
new file mode 100644
index 0000000..45fb135
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/Android.mk
@@ -0,0 +1,35 @@
+# 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 := libctsseccomp_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+		CtsSeccompJniOnLoad.cpp \
+		android_seccomp_cts_app_SeccompDeviceTest.cpp \
+
+LOCAL_SDK_VERSION := current
+LOCAL_LDLIBS := -llog
+LOCAL_C_INCLUDES += ndk/sources/cpufeatures
+LOCAL_STATIC_LIBRARIES := cpufeatures
+
+LOCAL_CFLAGS := -Wall -Werror
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp b/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp
new file mode 100644
index 0000000..928b8c5
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_seccomp_cts_app_SeccompTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void * /*reserved*/) {
+    JNIEnv *env = NULL;
+
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+
+    if (register_android_seccomp_cts_app_SeccompTest(env)) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_4;
+}
diff --git a/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
new file mode 100644
index 0000000..2257232
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+
+#define LOG_TAG "SeccompTest"
+
+#include <android/log.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define ALOG(priority, tag, ...) ((void)__android_log_print(ANDROID_##priority, tag, __VA_ARGS__))
+
+#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+/*
+ * Function: testSyscallBlocked
+ * Purpose: test that the syscall listed is blocked by seccomp
+ * Parameters:
+ *        nr: syscall number
+ * Returns:
+ *        1 if blocked, else 0
+ * Exceptions: None
+ */
+static jboolean testSyscallBlocked(JNIEnv *, jobject, int nr) {
+    int pid = fork();
+    if (pid == 0) {
+        ALOGI("Calling syscall %d", nr);
+        syscall(nr);
+        return false;
+    } else {
+        int status;
+        int ret = waitpid(pid, &status, 0);
+        if (ret != pid) {
+            ALOGE("Unexpected return result from waitpid");
+            return false;
+        }
+
+        if (WIFEXITED(status)) {
+            ALOGE("syscall was not blocked");
+            return false;
+        }
+
+        if (WIFSIGNALED(status)) {
+            int signal = WTERMSIG(status);
+            if (signal == 31) {
+                ALOGI("syscall caused process termination");
+                return true;
+            }
+
+            ALOGE("Unexpected signal");
+            return false;
+        }
+
+        ALOGE("Unexpected status from syscall_exists");
+        return false;
+    }
+}
+
+static JNINativeMethod gMethods[] = {
+    { "testSyscallBlocked", "(I)Z",
+            (void*) testSyscallBlocked },
+};
+
+int register_android_seccomp_cts_app_SeccompTest(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/seccomp/cts/app/SeccompDeviceTest");
+
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
new file mode 100644
index 0000000..2a7bcb3
--- /dev/null
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.seccomp.cts.app;
+
+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;
+
+import android.support.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.CpuFeatures;
+
+/**
+ * Device-side tests for CtsSeccompHostTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SeccompDeviceTest {
+    static {
+        System.loadLibrary("ctsseccomp_jni");
+    }
+
+    @Test
+    public void testCTSSyscallBlocked() {
+        if (CpuFeatures.isArm64Cpu()) {
+            testBlocked(217); // __NR_add_key
+            testBlocked(219); // __NR_keyctl
+            testAllowed(56); // __NR_openat
+
+            // b/35034743 - do not remove test without reading bug
+            testAllowed(267); // __NR_fstatfs64
+        } else if (CpuFeatures.isArmCpu()) {
+            testBlocked(309); // __NR_add_key
+            testBlocked(311); // __NR_keyctl
+            testAllowed(322); // __NR_openat
+
+            // b/35906875 - do not remove test without reading bug
+            testAllowed(316); // __NR_inotify_init
+        } else if (CpuFeatures.isX86_64Cpu()) {
+            testBlocked(248); // __NR_add_key
+            testBlocked(250); // __NR_keyctl
+            testAllowed(257); // __NR_openat
+        } else if (CpuFeatures.isX86Cpu()) {
+            testBlocked(286); // __NR_add_key
+            testBlocked(288); // __NR_keyctl
+            testAllowed(295); // __NR_openat
+        } else if (CpuFeatures.isMips64Cpu()) {
+            testBlocked(5239); // __NR_add_key
+            testBlocked(5241); // __NR_keyctl
+            testAllowed(5247); // __NR_openat
+        } else if (CpuFeatures.isMipsCpu()) {
+            testBlocked(4280); // __NR_add_key
+            testBlocked(4282); // __NR_keyctl
+            testAllowed(4288); // __NR_openat
+        } else {
+            Assert.fail("Unsupported OS");
+        }
+    }
+
+    @Test
+    public void testCTSSwapOnOffBlocked() {
+        if (CpuFeatures.isArm64Cpu()) {
+            testBlocked(224); // __NR_swapon
+            testBlocked(225); // __NR_swapoff
+        } else if (CpuFeatures.isArmCpu()) {
+            testBlocked(87);  // __NR_swapon
+            testBlocked(115); // __NR_swapoff
+        } else if (CpuFeatures.isX86_64Cpu()) {
+            testBlocked(167); // __NR_swapon
+            testBlocked(168); // __NR_swapoff
+        } else if (CpuFeatures.isX86Cpu()) {
+            testBlocked(87);  // __NR_swapon
+            testBlocked(115); // __NR_swapoff
+        } else if (CpuFeatures.isMips64Cpu()) {
+            testBlocked(5162); // __NR_swapon
+            testBlocked(5163); // __NR_swapoff
+        } else if (CpuFeatures.isMipsCpu()) {
+            testBlocked(4087); // __NR_swapon
+            testBlocked(4115); // __NR_swapoff
+        } else {
+            Assert.fail("Unsupported OS");
+        }
+    }
+
+    private void testBlocked(int nr) {
+        Assert.assertTrue("Syscall " + nr + " not blocked", testSyscallBlocked(nr));
+    }
+
+    private void testAllowed(int nr) {
+        Assert.assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr));
+    }
+
+    private static final native boolean testSyscallBlocked(int nr);
+}
diff --git a/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
new file mode 100644
index 0000000..63258e3
--- /dev/null
+++ b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.seccomp.cts;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that collects test results from test package android.seccomp.cts.app.
+ *
+ * When this test builds, it also builds a support APK containing
+ * {@link android.seccomp.cts.app.SeccompDeviceTest}, the results of which are
+ * collected from the hostside and reported accordingly.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SeccompHostJUnit4DeviceTest extends BaseHostJUnit4Test {
+
+    private static final String TEST_PKG = "android.seccomp.cts.app";
+    private static final String TEST_CLASS = TEST_PKG + "." + "SeccompDeviceTest";
+    private static final String TEST_APP = "CtsSeccompDeviceApp.apk";
+
+    private static final String TEST_CTS_SYSCALL_BLOCKED = "testCTSSyscallBlocked";
+    private static final String TEST_CTS_SWAP_ON_OFF_BLOCKED = "testCTSSwapOnOffBlocked";
+
+    @Before
+    public void setUp() throws Exception {
+        installPackage(TEST_APP);
+    }
+
+    @Test
+    public void testCTSSyscallBlocked() throws Exception {
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SYSCALL_BLOCKED));
+    }
+
+    @Test
+    public void testCTSSwapOnOffBlocked() throws Exception {
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SWAP_ON_OFF_BLOCKED));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+}
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index 00c5742..79325e4 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -54,6 +54,7 @@
     $(HOST_OUT_EXECUTABLES)/checkseapp \
     $(HOST_OUT_EXECUTABLES)/checkfc \
     $(HOST_OUT_EXECUTABLES)/property_info_checker \
+    $(HOST_OUT_EXECUTABLES)/searchpolicy \
     $(HOST_OUT_EXECUTABLES)/sepolicy_tests \
     $(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests \
     $(HOST_OUT)/lib64/libsepolwrap.$(SHAREDLIB_EXT) \
diff --git a/hostsidetests/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index f4e9e96..ef9581b 100644
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS Security host tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CVE-2016-8460->/data/local/tmp/CVE-2016-8460" />
-        <option name="push" value="CVE-2017-0403->/data/local/tmp/CVE-2017-0403" />
-        <option name="push" value="CVE-2017-0404->/data/local/tmp/CVE-2017-0404" />
         <option name="push" value="CVE-2016-8482->/data/local/tmp/CVE-2016-8482" />
-        <option name="push" value="CVE-2017-0429->/data/local/tmp/CVE-2017-0429" />
         <option name="push" value="CVE-2016-6730->/data/local/tmp/CVE-2016-6730" />
         <option name="push" value="CVE-2016-6731->/data/local/tmp/CVE-2016-6731" />
         <option name="push" value="CVE-2016-6732->/data/local/tmp/CVE-2016-6732" />
@@ -39,7 +37,6 @@
         <option name="push" value="CVE-2016-8431->/data/local/tmp/CVE-2016-8431" />
         <option name="push" value="CVE-2016-8432->/data/local/tmp/CVE-2016-8432" />
         <option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
-        <option name="push" value="CVE-2016-2504->/data/local/tmp/CVE-2016-2504" />
 
         <!--__________________-->
         <!-- Bulletin 2017-01 -->
@@ -91,6 +88,7 @@
 
         <option name="append-bitness" value="true" />
     </target_preparer>
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSecurityHostTestCases.jar" />
         <option name="runtime-hint" value="32s" />
diff --git a/hostsidetests/security/securityPatch/CVE-2016-2504/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-2504/Android.mk
deleted file mode 100644
index 50a95fb..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-2504/Android.mk
+++ /dev/null
@@ -1,38 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-2504
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts sts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-
-LOCAL_CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-LOCAL_CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-LOCAL_CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes
-LOCAL_CFLAGS += -Iinclude -fPIE
-LOCAL_LDFLAGS += -fPIE -pie
-LDFLAGS += -rdynamic
-include $(BUILD_CTS_EXECUTABLE)
-
-$(CTS_TESTCASES_OUT)/CVE-2016-2504 : $(LOCAL_BUILT_MODULE) | $(ACP)
-	$(copy-file-to-target)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-2504/poc.c b/hostsidetests/security/securityPatch/CVE-2016-2504/poc.c
deleted file mode 100644
index 5d0fe9c..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-2504/poc.c
+++ /dev/null
@@ -1,156 +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.
-*/
-
-#define _GNU_SOURCE
-#include <errno.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-/* ioctls */
-#define KGSL_IOC_TYPE 0x09
-
-enum kgsl_user_mem_type {
-  KGSL_USER_MEM_TYPE_PMEM = 0x00000000,
-  KGSL_USER_MEM_TYPE_ASHMEM = 0x00000001,
-  KGSL_USER_MEM_TYPE_ADDR = 0x00000002,
-  KGSL_USER_MEM_TYPE_ION = 0x00000003,
-  KGSL_USER_MEM_TYPE_MAX = 0x00000007,
-};
-
-/*
- * Unfortunately, enum kgsl_user_mem_type starts at 0 which does not
- * leave a good value for allocated memory. In the flags we use
- * 0 to indicate allocated memory and thus need to add 1 to the enum
- * values.
- */
-#define KGSL_USERMEM_FLAG(x) (((x) + 1) << KGSL_MEMFLAGS_USERMEM_SHIFT)
-
-#define KGSL_MEMFLAGS_NOT_USERMEM 0
-#define KGSL_MEMFLAGS_USERMEM_PMEM KGSL_USERMEM_FLAG(KGSL_USER_MEM_TYPE_PMEM)
-#define KGSL_MEMFLAGS_USERMEM_ASHMEM                                           \
-  KGSL_USERMEM_FLAG(KGSL_USER_MEM_TYPE_ASHMEM)
-#define KGSL_MEMFLAGS_USERMEM_ADDR KGSL_USERMEM_FLAG(KGSL_USER_MEM_TYPE_ADDR)
-#define KGSL_MEMFLAGS_USERMEM_ION KGSL_USERMEM_FLAG(KGSL_USER_MEM_TYPE_ION)
-
-/* add a block of pmem, fb, ashmem or user allocated address
- * into the GPU address space */
-struct kgsl_map_user_mem {
-  int fd;
-  unsigned long gpuaddr; /*output param */
-  size_t len;
-  size_t offset;
-  unsigned long hostptr; /*input param */
-  enum kgsl_user_mem_type memtype;
-  unsigned int flags;
-};
-
-#define IOCTL_KGSL_MAP_USER_MEM                                                \
-  _IOWR(KGSL_IOC_TYPE, 0x15, struct kgsl_map_user_mem)
-
-/* remove memory from the GPU's address space */
-struct kgsl_sharedmem_free {
-  unsigned long gpuaddr;
-};
-
-#define IOCTL_KGSL_SHAREDMEM_FREE                                              \
-  _IOW(KGSL_IOC_TYPE, 0x21, struct kgsl_sharedmem_free)
-
-#define KGSL_MEMFLAGS_USERMEM_MASK 0x000000e0
-#define KGSL_MEMFLAGS_USERMEM_SHIFT 5
-
-#define TRUE 1
-
-struct kgsl_map_user_mem allocArg;
-struct kgsl_sharedmem_free freeArg;
-
-int fd;
-int thread_exit = 1;
-
-void *alloc_thread() {
-  while (thread_exit) {
-    allocArg.fd = -1;
-    allocArg.gpuaddr = 0x0;
-    allocArg.len = 4096;
-    allocArg.offset = 0;
-    allocArg.hostptr = (unsigned long)malloc(allocArg.len);
-    allocArg.memtype = KGSL_USER_MEM_TYPE_ADDR;
-    allocArg.flags = KGSL_MEMFLAGS_USERMEM_ADDR;
-
-    int ret = ioctl(fd, IOCTL_KGSL_MAP_USER_MEM, &allocArg);
-
-    if (ret < 0) {
-      printf("Error on IOCTL_KGSL_MAP_USER_MEM - Errno %d (%s)\n", errno,
-             strerror(errno));
-      return NULL;
-    } else if (!allocArg.gpuaddr) {
-      allocArg.gpuaddr = allocArg.hostptr;
-    }
-
-    volatile unsigned long *pGPU = &allocArg.gpuaddr;
-
-    while (*pGPU) {
-      if (thread_exit)
-        break;
-    }
-
-    free((void *)allocArg.hostptr);
-  }
-  return NULL;
-}
-
-void *free_thread() {
-  volatile unsigned long *pGPU = &allocArg.gpuaddr;
-  freeArg.gpuaddr = 0x0;
-
-  while (!freeArg.gpuaddr) {
-    freeArg.gpuaddr = *pGPU;
-  }
-
-  while (thread_exit) {
-    ioctl(fd, IOCTL_KGSL_SHAREDMEM_FREE, &freeArg);
-    *pGPU = 0x0;
-  }
-  return NULL;
-}
-
-void kgsl_poc() {
-  pthread_t allocTid, freeTid;
-  fd = open("/dev/kgsl-3d0", 0);
-
-  if (fd < 0) {
-    printf("Unable to open /dev/kgsl-3d0 - Errno %d (%s)\n", errno,
-           strerror(errno));
-    exit(-1);
-  }
-
-  pthread_create(&allocTid, NULL, alloc_thread, NULL);
-  pthread_create(&freeTid, NULL, free_thread, NULL);
-  pthread_join(allocTid, NULL);
-  pthread_join(freeTid, NULL);
-}
-int main() {
-  kgsl_poc();
-  return 0;
-}
diff --git a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
index d8b2816..d878342 100644
--- a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
@@ -1,12 +1,28 @@
 package android.cts.security;
 
+import static android.security.cts.SELinuxHostTest.copyResourceToTempFile;
+import static android.security.cts.SELinuxHostTest.getDevicePolicyFile;
+import static android.security.cts.SELinuxHostTest.isMac;
+
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceTestCase;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 public class FileSystemPermissionTest extends DeviceTestCase {
 
@@ -52,4 +68,184 @@
     private static String getInsecureDeviceAdbCommand(String path, String type) {
         return String.format(INSECURE_DEVICE_ADB_COMMAND, path, type);
     }
+
+    private static String HW_RNG_DEVICE = "/dev/hw_random";
+
+    public void testDevHwRandomPermissions() throws Exception {
+        // This test asserts that, if present, /dev/hw_random must:
+        // 1. Be owned by UID root and GID system
+        // 2. Have file permissions 0440 (only readable, and only by owner and group). The reason
+        //    for being not readable by all/other is to avoid apps reading from this device.
+        //    Firstly, /dev/hw_random is not public API for apps. Secondly, apps might erroneously
+        //    use the output of Hardware RNG as trusted random output. Android does not trust output
+        //    of /dev/hw_random. HW RNG output is only used for mixing into Linux RNG as untrusted
+        //    input.
+        // 3. Be a character device with major:minor 10:183 -- hwrng kernel driver is using MAJOR 10
+        //    and MINOR 183
+        // 4. Be openable and readable by system_server according to SELinux policy
+
+        if (!mDevice.doesFileExist(HW_RNG_DEVICE)) {
+            // Hardware RNG device is missing. This is OK because it is not required to be exposed
+            // on all devices.
+            return;
+        }
+
+        String command = "ls -l " + HW_RNG_DEVICE;
+        String output = mDevice.executeShellCommand(command).trim();
+        if (!output.endsWith(" " + HW_RNG_DEVICE)) {
+            fail("Unexpected output from " + command + ": \"" + output + "\"");
+        }
+        String[] outputWords = output.split("\\s");
+        assertEquals("Wrong file mode on " + HW_RNG_DEVICE, "cr--r-----", outputWords[0]);
+        assertEquals("Wrong owner of " + HW_RNG_DEVICE, "root", outputWords[2]);
+        assertEquals("Wrong group of " + HW_RNG_DEVICE, "system", outputWords[3]);
+        assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
+        assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
+
+        command = "ls -Z " + HW_RNG_DEVICE;
+        output = mDevice.executeShellCommand(command).trim();
+        assertEquals(
+                "Wrong SELinux label on " + HW_RNG_DEVICE,
+                "u:object_r:hw_random_device:s0 " + HW_RNG_DEVICE,
+                output);
+
+        File sepolicy = getDevicePolicyFile(mDevice);
+        output =
+                new String(
+                        execSearchPolicy(
+                                "--allow",
+                                "-s", "system_server",
+                                "-t", "hw_random_device",
+                                "-c", "chr_file",
+                                "-p", "open",
+                                sepolicy.getPath()));
+        if (output.trim().isEmpty()) {
+            fail("SELinux policy does not permit system_server to open " + HW_RNG_DEVICE);
+        }
+        output =
+                new String(
+                        execSearchPolicy(
+                                "--allow",
+                                "-s", "system_server",
+                                "-t", "hw_random_device",
+                                "-c", "chr_file",
+                                "-p", "read",
+                                sepolicy.getPath()));
+        if (output.trim().isEmpty()) {
+            fail("SELinux policy does not permit system_server to read " + HW_RNG_DEVICE);
+        }
+    }
+
+    /**
+     * Executes {@code searchpolicy} executable with the provided parameters and returns the
+     * contents of standard output.
+     *
+     * @throws IOException if execution of searchpolicy fails, returns non-zero error code, or
+     *         non-empty stderr
+     */
+    private static byte[] execSearchPolicy(String... args)
+            throws InterruptedException, IOException {
+        File tmpDir = Files.createTempDirectory("searchpolicy").toFile();
+        try {
+            String[] envp;
+            File libsepolwrap;
+            if (isMac()) {
+                libsepolwrap = copyResourceToTempFile("/libsepolwrap.dylib");
+                libsepolwrap =
+                        Files.move(
+                                libsepolwrap.toPath(),
+                                new File(tmpDir, "libsepolwrap.dylib").toPath()).toFile();
+                File libcpp = copyResourceToTempFile("/libc++.dylib");
+                Files.move(libcpp.toPath(), new File(tmpDir, "libc++.dylib").toPath());
+                envp = new String[] {"DYLD_LIBRARY_PATH=" + tmpDir.getAbsolutePath()};
+            } else {
+                libsepolwrap = copyResourceToTempFile("/libsepolwrap.so");
+                libsepolwrap =
+                        Files.move(
+                                libsepolwrap.toPath(),
+                                new File(tmpDir, "libsepolwrap.so").toPath()).toFile();
+                File libcpp = copyResourceToTempFile("/libc++.so");
+                Files.move(libcpp.toPath(), new File(tmpDir, "libc++.so").toPath());
+                envp = new String[] {"LD_LIBRARY_PATH=" + tmpDir.getAbsolutePath()};
+            }
+            File searchpolicy = copyResourceToTempFile("/searchpolicy");
+            searchpolicy =
+                    Files.move(
+                        searchpolicy.toPath(),
+                        new File(tmpDir, "searchpolicy").toPath()).toFile();
+            searchpolicy.setExecutable(true);
+            libsepolwrap.setExecutable(true);
+            List<String> cmd = new ArrayList<>(3 + args.length);
+            cmd.add(searchpolicy.getPath());
+            cmd.add("--libpath");
+            cmd.add(libsepolwrap.getPath());
+            for (String arg : args) {
+                cmd.add(arg);
+            }
+            return execAndCaptureOutput(cmd.toArray(new String[0]), envp);
+        } finally {
+            // Delete tmpDir
+            File[] files = tmpDir.listFiles();
+            if (files == null) {
+                files = new File[0];
+            }
+            for (File f : files) {
+                f.delete();
+            }
+            tmpDir.delete();
+        }
+    }
+
+    /**
+     * Executes the provided command and returns the contents of standard output.
+     *
+     * @throws IOException if execution fails, returns a non-zero error code, or non-empty stderr
+     */
+    private static byte[] execAndCaptureOutput(String[] cmd, String[] envp)
+            throws InterruptedException, IOException {
+        // Start process, read its stdout and stderr in two corresponding background threads, wait
+        // for process to terminate, throw if stderr is not empty or if return code != 0.
+        final Process p = Runtime.getRuntime().exec(cmd, envp);
+        ExecutorService executorService = null;
+        try {
+            executorService = Executors.newFixedThreadPool(2);
+            Future<byte[]> stdoutContentsFuture =
+                    executorService.submit(new DrainCallable(p.getInputStream()));
+            Future<byte[]> stderrContentsFuture =
+                    executorService.submit(new DrainCallable(p.getErrorStream()));
+            int errorCode = p.waitFor();
+            byte[] stderrContents = stderrContentsFuture.get();
+            if ((errorCode != 0)  || (stderrContents.length > 0)) {
+                throw new IOException(
+                        cmd[0] + " failed with error code " + errorCode
+                            + ": " + new String(stderrContents));
+            }
+            return stdoutContentsFuture.get();
+        } catch (ExecutionException e) {
+            throw new IOException("Failed to read stdout or stderr of " + cmd[0], e);
+        } finally {
+            if (executorService != null) {
+                executorService.shutdownNow();
+            }
+        }
+    }
+
+    private static class DrainCallable implements Callable<byte[]> {
+        private final InputStream mIn;
+
+        private DrainCallable(InputStream in) {
+            mIn = in;
+        }
+
+        @Override
+        public byte[] call() throws IOException {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+            byte[] buf = new byte[16384];
+            int chunkSize;
+            while ((chunkSize = mIn.read(buf)) != -1) {
+                result.write(buf, 0, chunkSize);
+            }
+            return result.toByteArray();
+        }
+    }
 }
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_08.java b/hostsidetests/security/src/android/security/cts/Poc16_08.java
deleted file mode 100644
index 3bc6c43..0000000
--- a/hostsidetests/security/src/android/security/cts/Poc16_08.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-
-public class Poc16_08 extends SecurityTestCase {
-  /**
-   *  b/28026365
-   */
-  @SecurityTest
-  public void testPocCVE_2016_2504() throws Exception {
-    if (containsDriver(getDevice(), "/dev/kgsl-3d0")) {
-        AdbUtils.runPoc("CVE-2016-2504", getDevice(), 60);
-    }
-  }
-}
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_01.java b/hostsidetests/security/src/android/security/cts/Poc17_01.java
index ff32086..4fd98b7 100644
--- a/hostsidetests/security/src/android/security/cts/Poc17_01.java
+++ b/hostsidetests/security/src/android/security/cts/Poc17_01.java
@@ -30,5 +30,4 @@
             AdbUtils.runPoc("CVE-2016-8482", getDevice(), 60);
         }
     }
-
 }
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_03.java b/hostsidetests/security/src/android/security/cts/Poc17_03.java
index df44937..690b277 100644
--- a/hostsidetests/security/src/android/security/cts/Poc17_03.java
+++ b/hostsidetests/security/src/android/security/cts/Poc17_03.java
@@ -58,5 +58,4 @@
             Thread.sleep(30000);
         }
     }
-
 }
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 1f0a567..3c6dd6da 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -108,8 +108,8 @@
         mDevice = device;
     }
 
-    private File copyResourceToTempFile(String resName) throws IOException {
-        InputStream is = this.getClass().getResourceAsStream(resName);
+    public static File copyResourceToTempFile(String resName) throws IOException {
+        InputStream is = SELinuxHostTest.class.getResourceAsStream(resName);
         File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
         FileOutputStream os = new FileOutputStream(tempFile);
         byte[] buf = new byte[1024];
@@ -666,7 +666,7 @@
                    + errorString, errorString.length() == 0);
     }
 
-    private boolean isMac() {
+    public static boolean isMac() {
         String os = System.getProperty("os.name").toLowerCase();
         return (os.startsWith("mac") || os.startsWith("darwin"));
     }
diff --git a/hostsidetests/services/activityandwindowmanager/Android.mk b/hostsidetests/services/activityandwindowmanager/Android.mk
deleted file mode 100644
index 178cb8a..0000000
--- a/hostsidetests/services/activityandwindowmanager/Android.mk
+++ /dev/null
@@ -1,17 +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.
-#
-
-include $(call all-subdir-makefiles)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk
deleted file mode 100644
index 1761ba6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-# Must match the package name in CtsTestCaseList.mk
-LOCAL_MODULE := CtsServicesHostTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-LOCAL_STATIC_JAVA_LIBRARIES := cts-amwm-util  \
-    cts-display-service-app-util \
-    platform-test-annotations-host
-
-LOCAL_CTS_TEST_PACKAGE := android.server
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-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/services/activityandwindowmanager/activitymanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
deleted file mode 100644
index 5ecde6b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Sample host test cases">
-    <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="CtsDeviceServicesTestApp.apk" />
-        <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
-        <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
-        <option name="test-file-name" value="CtsDeviceDebuggableApp.apk" />
-        <option name="test-file-name" value="CtsDeviceDisplaySizeApp.apk" />
-        <option name="test-file-name" value="CtsDisplayServiceApp.apk" />
-        <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
-        <option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
-    </target_preparer>
-    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="CtsServicesHostTestCases.jar" />
-        <option name="runtime-hint" value="4m44s" />
-    </test>
-</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/app/Android.mk
deleted file mode 100644
index 67f2248..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/Android.mk
+++ /dev/null
@@ -1,35 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-v4 \
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-                   ../../../../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceServicesTestApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
deleted file mode 100755
index 63f41d1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
+++ /dev/null
@@ -1,413 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.cts">
-
-    <!-- virtual display test permissions -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
-
-    <application>
-        <activity android:name=".TestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-        />
-        <activity android:name=".TestActivityWithSameAffinity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
-        <activity android:name=".TranslucentTestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:theme="@style/Theme.Transparent" />
-        <activity android:name=".VrTestActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
-        <activity android:name=".ResumeWhilePausingActivity"
-                android:allowEmbedded="true"
-                android:resumeWhilePausing="true"
-                android:taskAffinity=""
-                android:exported="true"
-        />
-        <activity android:name=".ResizeableActivity"
-                android:resizeableActivity="true"
-                android:allowEmbedded="true"
-                android:exported="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
-        />
-        <activity android:name=".NonResizeableActivity"
-                android:resizeableActivity="false"
-                android:exported="true"
-        />
-        <activity android:name=".DockedActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.DockedActivity"
-        />
-        <activity android:name=".TranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
-            android:resizeableActivity="true"
-            android:taskAffinity="nobody.but.TranslucentActivity"
-            android:exported="true"
-        />
-        <activity android:name=".DialogWhenLargeActivity"
-                android:exported="true"
-                android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
-        />
-        <activity android:name=".NoRelaunchActivity"
-                android:resizeableActivity="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
-                android:exported="true"
-                android:taskAffinity="nobody.but.NoRelaunchActivity"
-        />
-        <activity android:name=".SlowCreateActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
-        <activity android:name=".LaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
-        <activity android:name=".AltLaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
-        <activity android:name=".PipActivity"
-                android:resizeableActivity="false"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivity"
-        />
-        <activity android:name=".PipActivity2"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivity2"
-        />
-        <activity android:name=".PipOnStopActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipOnStopActivity"
-        />
-        <activity android:name=".PipActivityWithSameAffinity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
-        <activity android:name=".AlwaysFocusablePipActivity"
-                  android:theme="@style/Theme.Transparent"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
-        />
-        <activity android:name=".LaunchIntoPinnedStackPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
-        <activity android:name=".LaunchPipOnPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
-        <activity android:name=".LaunchEnterPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
-        <activity android:name=".FreeformActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="nobody.but.FreeformActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".TopLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
-                  <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="top|left"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
-        </activity>
-        <activity android:name=".TopRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
-                  <layout android:defaultWidth="25%"
-                          android:defaultHeight="35%"
-                          android:gravity="top|right"
-                          android:minWidth="90dp"
-                          android:minHeight="80dp"
-                  />
-        </activity>
-        <activity android:name=".BottomLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
-                  <layout android:defaultWidth="25%"
-                          android:defaultHeight="35%"
-                          android:gravity="bottom|left"
-                          android:minWidth="90dp"
-                          android:minHeight="80dp"
-                  />
-        </activity>
-        <activity android:name=".BottomRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
-                  <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="bottom|right"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
-        </activity>
-        <activity android:name=".TurnScreenOnActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".TurnScreenOnDismissKeyguardActivity"
-            android:exported="true"
-        />
-        <activity android:name=".SingleTaskActivity"
-            android:exported="true"
-            android:launchMode="singleTask"
-        />
-        <activity android:name=".SingleInstanceActivity"
-            android:exported="true"
-            android:launchMode="singleInstance"
-        />
-        <activity android:name=".TrampolineActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.NoDisplay"
-        />
-        <activity android:name=".BroadcastReceiverActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-        />
-        <activity-alias android:enabled="true"
-                android:exported="true"
-                android:name=".EntryPointAliasActivity"
-                android:targetActivity=".TrampolineActivity" >
-        </activity-alias>
-        <activity android:name=".BottomActivity"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
-        <activity android:name=".TopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
-        <activity android:name=".TranslucentTopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/TranslucentTheme"
-        />
-        <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
-             animation background being tested is not used in translucent activities. -->
-        <activity android:name=".AnimationTestActivity"
-                  android:theme="@style/OpaqueTheme"
-                  android:exported="true"
-        />
-        <activity android:name=".VirtualDisplayActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-        />
-        <activity android:name=".ShowWhenLockedActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".ShowWhenLockedWithDialogActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".ShowWhenLockedDialogActivity"
-            android:exported="true"
-            android:theme="@android:style/Theme.Material.Dialog"
-        />
-        <activity android:name=".ShowWhenLockedTranslucentActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.Translucent"
-        />
-        <activity android:name=".DismissKeyguardActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".DismissKeyguardMethodActivity"
-            android:exported="true"
-        />
-        <activity android:name=".WallpaperActivity"
-            android:exported="true"
-            android:theme="@style/WallpaperTheme"
-        />
-        <activity android:name=".KeyguardLockActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".LogConfigurationActivity"
-            android:exported="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-        />
-        <activity android:name=".PortraitOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="portrait"
-                  android:documentLaunchMode="always"
-        />
-        <activity android:name=".LandscapeOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="landscape"
-                  android:documentLaunchMode="always"
-        />
-        <activity android:name=".MoveTaskToBackActivity"
-                  android:exported="true"
-                  android:launchMode="singleInstance"
-        />
-        <activity android:name=".FinishableActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".NightModeActivity"
-                  android:exported="true"
-                  android:configChanges="uiMode"
-        />
-        <activity android:name=".FontScaleActivity"
-                  android:exported="true"
-        />
-        <activity android:name=".FontScaleNoRelaunchActivity"
-                  android:exported="true"
-                  android:configChanges="fontScale"
-        />
-        <receiver
-            android:name=".LaunchBroadcastReceiver"
-            android:enabled="true"
-            android:exported="true" >
-            <intent-filter>
-                <action android:name="android.server.cts.LAUNCH_BROADCAST_ACTION"/>
-            </intent-filter>
-        </receiver>
-
-        <activity android:name=".AssistantActivity"
-            android:exported="true" />
-        <activity android:name=".TranslucentAssistantActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Transparent" />
-        <activity android:name=".LaunchAssistantActivityFromSession"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
-            android:exported="true" />
-        <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
-            android:exported="true" />
-
-        <service android:name=".AssistantVoiceInteractionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true">
-            <meta-data android:name="android.voice_interaction"
-                       android:resource="@xml/interaction_service" />
-            <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
-            </intent-filter>
-        </service>
-
-        <service android:name=".AssistantVoiceInteractionSessionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true" />
-
-        <activity android:name=".SplashscreenActivity"
-            android:taskAffinity="nobody.but.SplashscreenActivity"
-            android:theme="@style/SplashscreenTheme"
-            android:exported="true" />
-
-
-        <activity android:name=".SwipeRefreshActivity"
-                  android:exported="true" />
-
-        <activity android:name=".NoHistoryActivity"
-                  android:noHistory="true"
-                  android:exported="true" />
-
-        <activity android:name=".ShowWhenLockedAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
-
-        <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
-
-        <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
-
-        <activity android:name=".TurnScreenOnAttrActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
-
-        <activity android:name=".TurnScreenOnShowOnLockActivity"
-                  android:showWhenLocked="true"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
-
-        <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
-
-        <activity android:name=".TurnScreenOnSingleTaskActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true"
-                  android:launchMode="singleTask" />
-
-        <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true"/>
-
-        <activity android:name=".TurnScreenOnWithRelayoutActivity"
-                  android:exported="true"/>
-
-        <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
-           <intent-filter>
-               <action android:name="android.service.vr.VrListenerService" />
-           </intent-filter>
-        </service>
-    </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml
deleted file mode 100644
index de52e61..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml
+++ /dev/null
@@ -1,21 +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
-  -->
-
-<android.server.cts.LifecycleLogView xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-</android.server.cts.LifecycleLogView>
\ No newline at end of file
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml
deleted file mode 100644
index 7cf92a0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
-    android:sessionService="android.server.cts.AssistantVoiceInteractionSessionService"
-    android:recognitionService="android.server.cts.AssistantVoiceInteractionSessionService"
-    android:supportsAssist="true" />
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java
deleted file mode 100644
index 9d29917..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
-
-public abstract class AbstractLifecycleLogActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        Log.i(getTag(), "onCreate");
-    }
-
-    @Override
-    protected void onStart() {
-        super.onResume();
-        Log.i(getTag(), "onStart");
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        Log.i(getTag(), "onResume");
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        Log.i(getTag(), "onConfigurationChanged");
-    }
-
-    @Override
-    public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
-        super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
-        Log.i(getTag(), "onMultiWindowModeChanged");
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
-            Configuration newConfig) {
-        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
-        Log.i(getTag(), "onPictureInPictureModeChanged");
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        Log.i(getTag(), "onPause");
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        Log.i(getTag(), "onStop");
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        Log.i(getTag(), "onDestroy");
-    }
-
-    protected abstract String getTag();
-
-    protected void dumpConfiguration(Configuration config) {
-        Log.i(getTag(), "Configuration: " + config);
-    }
-
-    protected void dumpDisplaySize(Configuration config) {
-        // Dump the display size as seen by this Activity.
-        final WindowManager wm = getSystemService(WindowManager.class);
-        final Display display = wm.getDefaultDisplay();
-        final Point point = new Point();
-        display.getSize(point);
-        final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
-        final String line = "config"
-                + " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp)
-                + " displaySize=" + buildCoordString(point.x, point.y)
-                + " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels)
-                + " smallestScreenWidth=" + config.smallestScreenWidthDp
-                + " densityDpi=" + config.densityDpi
-                + " orientation=" + config.orientation;
-
-        Log.i(getTag(), line);
-    }
-
-    protected static String buildCoordString(int x, int y) {
-        return "(" + x + "," + y + ")";
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java
deleted file mode 100644
index 7bf847e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * An additional launching activity used for alternating between two activities.
- */
-public class AltLaunchingActivity extends LaunchingActivity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java
deleted file mode 100644
index b6c0667..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-
-public class AlwaysFocusablePipActivity extends Activity {
-
-    static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
-        final Intent intent = new Intent(caller, AlwaysFocusablePipActivity.class);
-
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK);
-        if (newTask) {
-            intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        }
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchBounds(new Rect(0, 0, 500, 500));
-        options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
-        caller.startActivity(intent, options.toBundle());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java
deleted file mode 100644
index 5ae923e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class AnimationTestActivity extends Activity {
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        overridePendingTransition(R.anim.animation_with_background, -1);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java
deleted file mode 100644
index 18f290f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class AssistantActivity extends Activity {
-
-    // Launches the given activity in onResume
-    public static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
-    // Finishes this activity in onResume, this happens after EXTRA_LAUNCH_NEW_TASK
-    public static final String EXTRA_FINISH_SELF = "finish_self";
-    // Attempts to enter picture-in-picture in onResume
-    public static final String EXTRA_ENTER_PIP = "enter_pip";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // Set the layout
-        setContentView(R.layout.assistant);
-
-        // Launch the new activity if requested
-        if (getIntent().hasExtra(EXTRA_LAUNCH_NEW_TASK)) {
-            Intent i = new Intent();
-            i.setComponent(new ComponentName(this, getPackageName() + "."
-                    + getIntent().getStringExtra(EXTRA_LAUNCH_NEW_TASK)));
-            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-            startActivity(i);
-        }
-
-        // Enter pip if requested
-        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
-            try {
-                enterPictureInPictureMode();
-            } catch (IllegalStateException e) {
-                finish();
-                return;
-            }
-        }
-
-        // Finish this activity if requested
-        if (getIntent().hasExtra(EXTRA_FINISH_SELF)) {
-            finish();
-        }
-    }
-
-    /**
-     * Launches a new instance of the AssistantActivity directly into the assistant stack.
-     */
-    static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
-        final Intent intent = new Intent(caller, AssistantActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-        if (extras != null) {
-            intent.putExtras(extras);
-        }
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(6 /* ActivityManager.StackId.ASSISTANT_STACK_ID */);
-        caller.startActivity(intent, options.toBundle());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java
deleted file mode 100644
index 51c2348..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionService;
-import android.util.Log;
-
-public class AssistantVoiceInteractionService extends VoiceInteractionService {
-
-    private static final String TAG = AssistantVoiceInteractionService.class.getSimpleName();
-
-    private boolean mReady;
-
-    @Override
-    public void onReady() {
-        super.onReady();
-        mReady = true;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (!isActiveService(this, new ComponentName(this, getClass()))) {
-            Log.wtf(TAG, "**** Not starting AssistantVoiceInteractionService because" +
-                    " it is not set as the current voice interaction service");
-            stopSelf();
-            return START_NOT_STICKY;
-        }
-        if (mReady) {
-            Bundle extras = intent.getExtras() != null ? intent.getExtras() : new Bundle();
-            showSession(extras, 0);
-        }
-        return START_NOT_STICKY;
-    }
-
-    /**
-     * Starts the assistant voice interaction service, which initiates a new session that starts
-     * the assistant activity.
-     */
-    public static void launchAssistantActivity(Context context, Bundle extras) {
-        Intent i = new Intent(context, AssistantVoiceInteractionService.class);
-        if (extras != null) {
-            i.putExtras(extras);
-        }
-        context.startService(i);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java
deleted file mode 100644
index e711ac4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionSession;
-import android.service.voice.VoiceInteractionSessionService;
-
-public class AssistantVoiceInteractionSessionService extends VoiceInteractionSessionService {
-
-    @Override
-    public VoiceInteractionSession onNewSession(Bundle args) {
-        return new VoiceInteractionSession(this) {
-            @Override
-            public void onPrepareShow(Bundle args, int showFlags) {
-                setUiEnabled(false);
-            }
-
-            @Override
-            public void onShow(Bundle args, int showFlags) {
-                Intent i = new Intent(AssistantVoiceInteractionSessionService.this,
-                        AssistantActivity.class);
-                if (args != null) {
-                    i.putExtras(args);
-                }
-                startAssistantActivity(i);
-            }
-        };
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java
deleted file mode 100644
index 38a71f1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-
-public class BottomActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = BottomActivity.class.getSimpleName();
-
-    private int mStopDelay;
-    private View mFloatingWindow;
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
-        if (useWallpaper) {
-            setTheme(R.style.WallpaperTheme);
-        }
-        setContentView(R.layout.main);
-
-        // Delayed stop is for simulating a case where resume happens before
-        // activityStopped() is received by AM, and the transition starts without
-        // going through fully stopped state (see b/30255354).
-        // If enabled, we stall onStop() of BottomActivity, open TopActivity but make
-        // it finish before onStop() ends. This will cause BottomActivity to resume before
-        // it notifies AM of activityStopped(). We also add a second window of
-        // TYPE_BASE_APPLICATION, so that the transition animation could start earlier.
-        // Otherwise the main window has to relayout to visible first and the error won't occur.
-        // Note that if the test fails, we shouldn't try to change the app here to make
-        // it pass. The test app is artificially made to simulate an failure case, but
-        // it's not doing anything wrong.
-        mStopDelay = getIntent().getIntExtra("STOP_DELAY", 0);
-        if (mStopDelay > 0) {
-            LayoutInflater inflater = getLayoutInflater();
-            mFloatingWindow = inflater.inflate(R.layout.floating, null);
-
-            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
-            params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-            params.setTitle("Floating");
-            getWindowManager().addView(mFloatingWindow, params);
-        }
-    }
-
-    @Override
-    public void onResume() {
-        Log.d(TAG, "onResume() E");
-        super.onResume();
-
-        if (mStopDelay > 0) {
-            // Refresh floating window
-            Log.d(TAG, "Scheuling invalidate Floating Window in onResume()");
-            mFloatingWindow.invalidate();
-        }
-
-        Log.d(TAG, "onResume() X");
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        if (mStopDelay > 0) {
-            try {
-                Log.d(TAG, "Stalling onStop() by " + mStopDelay + " ms...");
-                Thread.sleep(mStopDelay);
-            } catch(InterruptedException e) {}
-
-            // Refresh floating window
-            Log.d(TAG, "Scheuling invalidate Floating Window in onStop()");
-            mFloatingWindow.invalidate();
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java
deleted file mode 100644
index 8de1cda..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class BottomLeftLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java
deleted file mode 100644
index 24fa2fc..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class BottomRightLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
deleted file mode 100644
index 24be43a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-
-/**
- * Activity that registers broadcast receiver .
- */
-public class BroadcastReceiverActivity extends Activity {
-
-    public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
-    private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
-
-    private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
-
-        registerReceiver(mBroadcastReceiver, broadcastFilter);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        unregisterReceiver(mBroadcastReceiver);
-    }
-
-    public class TestBroadcastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final Bundle extras = intent.getExtras();
-            Log.i(TAG, "onReceive: extras=" + extras);
-
-            if (extras == null) {
-                return;
-            }
-            if (extras.getBoolean("finish")) {
-                finish();
-            }
-            if (extras.getBoolean("moveToBack")) {
-                moveTaskToBack(true);
-            }
-            if (extras.containsKey("orientation")) {
-                setRequestedOrientation(extras.getInt("orientation"));
-            }
-            if (extras.getBoolean("dismissKeyguard")) {
-                getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
-            }
-            if (extras.getBoolean("dismissKeyguardMethod")) {
-                getSystemService(KeyguardManager.class).requestDismissKeyguard(
-                        BroadcastReceiverActivity.this, new KeyguardDismissLoggerCallback(context));
-            }
-
-            ActivityLauncher.launchActivityFromExtras(BroadcastReceiverActivity.this, extras);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java
deleted file mode 100644
index 139c648..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * Activity with DialogWhenLarge Theme.
- */
-public class DialogWhenLargeActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = DialogWhenLargeActivity.class.getSimpleName();
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java
deleted file mode 100644
index 726a756..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class DismissKeyguardActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java
deleted file mode 100644
index c833eb2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class DismissKeyguardMethodActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
-                new KeyguardDismissLoggerCallback(this));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java
deleted file mode 100644
index 007df5f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class DockedActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java
deleted file mode 100644
index d64b930..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.BroadcastReceiver;
-import android.os.Bundle;
-
-/**
- * This activity finishes when you send a broadcast with the following action from adb shell
- *  am broadcast -a 'android.server.cts.FinishableActivity.finish'
- */
-public class FinishableActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = FinishableActivity.class.getSimpleName();
-    private static final String ACTION_FINISH = "android.server.cts.FinishableActivity.finish";
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent != null && intent.getAction().equals(ACTION_FINISH)) {
-                finish();
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        IntentFilter intentFilter = new IntentFilter(ACTION_FINISH);
-        registerReceiver(mReceiver, intentFilter);
-    }
-
-    @Override
-    protected void onDestroy() {
-        unregisterReceiver(mReceiver);
-        super.onDestroy();
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java
deleted file mode 100644
index a5bdffe..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package android.server.cts;
-
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-public class FontScaleActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = FontScaleActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        dumpActivityDpi();
-        dumpFontSize();
-    }
-
-    // We're basically ensuring that no matter what happens to the resources underneath the
-    // Activity, any TypedArrays obtained from the pool have the correct DisplayMetrics.
-    protected void dumpFontSize() {
-        try (XmlResourceParser parser = getResources().getXml(R.layout.font_scale)) {
-            //noinspection StatementWithEmptyBody
-            while (parser.next() != XmlPullParser.START_TAG) { }
-
-            final AttributeSet attrs = Xml.asAttributeSet(parser);
-            TypedArray ta = getTheme().obtainStyledAttributes(attrs,
-                    new int[] { android.R.attr.textSize }, 0, 0);
-            try {
-                final int fontPixelSize = ta.getDimensionPixelSize(0, -1);
-                if (fontPixelSize == -1) {
-                    throw new AssertionError("android:attr/textSize not found");
-                }
-
-                Log.i(getTag(), "fontPixelSize=" + fontPixelSize);
-            } finally {
-                ta.recycle();
-            }
-        } catch (XmlPullParserException | IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    protected void dumpActivityDpi() {
-        final int fontActivityDpi = getResources().getDisplayMetrics().densityDpi;
-        Log.i(getTag(), "fontActivityDpi=" + fontActivityDpi);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java
deleted file mode 100644
index ea78a08..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package android.server.cts;
-
-import android.content.res.Configuration;
-
-public class FontScaleNoRelaunchActivity extends FontScaleActivity {
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpActivityDpi();
-        dumpFontSize();
-    }
-
-    @Override
-    protected String getTag() {
-        return FontScaleNoRelaunchActivity.class.getSimpleName();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.java
deleted file mode 100644
index f8c6d0c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.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.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.graphics.Rect;
-
-public class FreeformActivity extends Activity {
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        final Intent intent = new Intent(this, TestActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchBounds(new Rect(0, 0, 900, 900));
-        this.startActivity(intent, options.toBundle());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java
deleted file mode 100644
index 860f2ae..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.app.KeyguardManager.KeyguardDismissCallback;
-import android.content.Context;
-import android.util.Log;
-
-public class KeyguardDismissLoggerCallback extends KeyguardDismissCallback {
-
-    private final String TAG = "KeyguardDismissLoggerCallback";
-
-    private final Context mContext;
-
-    public KeyguardDismissLoggerCallback(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public void onDismissError() {
-        Log.i(TAG, "onDismissError");
-    }
-
-    @Override
-    public void onDismissSucceeded() {
-        if (mContext.getSystemService(KeyguardManager.class).isDeviceLocked()) {
-            // Device is still locked? What a fail. Don't print "onDismissSucceded" such that the
-            // log fails.
-            Log.i(TAG, "dismiss succedded was called but device is still locked.");
-        } else {
-            Log.i(TAG, "onDismissSucceeded");
-        }
-    }
-
-    @Override
-    public void onDismissCancelled() {
-        Log.i(TAG, "onDismissCancelled");
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java
deleted file mode 100644
index 352cf04..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class KeyguardLockActivity extends BroadcastReceiverActivity {
-
-    private KeyguardManager.KeyguardLock mKeyguardLock;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mKeyguardLock = getSystemService(KeyguardManager.class).newKeyguardLock("test");
-        mKeyguardLock.disableKeyguard();
-    }
-
-    @Override
-    protected void onDestroy() {
-        mKeyguardLock.reenableKeyguard();
-        super.onDestroy();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.java
deleted file mode 100644
index ffd5aee..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.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.server.cts;
-
-import android.content.res.Configuration;
-
-public class LandscapeOrientationActivity extends AbstractLifecycleLogActivity {
-    @Override
-    protected String getTag() {
-        return "LandscapeOrientationActivity";
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java
deleted file mode 100644
index 2d562ff..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchAssistantActivityFromSession extends Activity {
-    @Override
-    protected void onResume() {
-        super.onResume();
-        AssistantVoiceInteractionService.launchAssistantActivity(this, getIntent().getExtras());
-        finishAndRemoveTask();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java
deleted file mode 100644
index 62839a4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchAssistantActivityIntoAssistantStack extends Activity {
-
-    // Launches the translucent assist activity
-    public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (getIntent().hasExtra(EXTRA_IS_TRANSLUCENT) &&
-                Boolean.valueOf(getIntent().getStringExtra(EXTRA_IS_TRANSLUCENT))) {
-            TranslucentAssistantActivity.launchActivityIntoAssistantStack(this,
-                    getIntent().getExtras());
-        } else {
-            AssistantActivity.launchActivityIntoAssistantStack(this, getIntent().getExtras());
-        }
-        finishAndRemoveTask();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java
deleted file mode 100644
index 6e713ec..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java
+++ /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
- */
-
-package android.server.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-
-/** Broadcast receiver that can launch activities. */
-public class LaunchBroadcastReceiver extends BroadcastReceiver {
-    private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        try {
-            ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
-        } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
deleted file mode 100644
index e2b4786..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Bundle;
-
-public class LaunchEnterPipActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-        PipActivity.launchEnterPipActivity(this);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java
deleted file mode 100644
index 86c4834..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchIntoPinnedStackPipActivity extends Activity {
-    @Override
-    protected void onResume() {
-        super.onResume();
-        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java
deleted file mode 100644
index d0b47b0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.pm.PackageManager;
-
-public class LaunchPipOnPipActivity extends Activity {
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        super.onPictureInPictureModeChanged(isInPictureInPictureMode);
-        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this,
-            getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java
deleted file mode 100644
index b312c97..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.server.cts.tools.ActivityLauncher;
-
-/**
- * Activity that launches another activities when new intent is received.
- */
-public class LaunchingActivity extends Activity {
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java
deleted file mode 100644
index 66e0cf2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-public class LifecycleLogView extends View {
-    private final String TAG = "LifecycleLogView";
-
-    public LifecycleLogView(Context context) {
-        super(context);
-    }
-
-    public LifecycleLogView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
-    {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        Log.i(TAG, "onConfigurationChanged");
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java
deleted file mode 100644
index 61eb78c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.util.Log;
-
-/**
- * Activity that logs configuration changes.
- */
-public class LogConfigurationActivity extends Activity {
-
-    private static final String TAG = "LogConfigurationActivity";
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        Log.i(TAG, "Configuration changed: " + newConfig.screenWidthDp + ","
-                + newConfig.screenHeightDp);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
deleted file mode 100644
index 6c47fb7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Activity that finishes itself using "moveTaskToBack".
- */
-public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
-
-    private String mFinishPoint;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final Intent intent = getIntent();
-        mFinishPoint = intent.getExtras().getString("finish_point");
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        if (mFinishPoint.equals("on_pause")) {
-            moveTaskToBack(true /* nonRoot */);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        if (mFinishPoint.equals("on_stop")) {
-            moveTaskToBack(true /* nonRoot */);
-        }
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java
deleted file mode 100644
index e33141b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.Bundle;
-
-/** Activity that changes UI mode on creation and handles corresponding configuration change. */
-public class NightModeActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = NightModeActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        UiModeManager uiManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
-        // Switch the mode two times to make sure it is independent of the current setting.
-        uiManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
-        uiManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java
deleted file mode 100644
index 6a84602..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * An activity that has the noHistory flag set.
- */
-public class NoHistoryActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = NoHistoryActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java
deleted file mode 100644
index 9cc3b9d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-public class NoRelaunchActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = NoRelaunchActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java
deleted file mode 100644
index b871a8d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-public class NonResizeableActivity extends AbstractLifecycleLogActivity {
-
-     private static final String TAG = NonResizeableActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
deleted file mode 100644
index f968970..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.app.PictureInPictureParams;
-import android.content.res.Configuration;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.Rational;
-import android.view.WindowManager;
-
-public class PipActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = "PipActivity";
-
-    // Intent action that this activity dynamically registers to enter picture-in-picture
-    private static final String ACTION_ENTER_PIP = "android.server.cts.PipActivity.enter_pip";
-    // Intent action that this activity dynamically registers to move itself to the back
-    private static final String ACTION_MOVE_TO_BACK = "android.server.cts.PipActivity.move_to_back";
-    // Intent action that this activity dynamically registers to expand itself.
-    // If EXTRA_SET_ASPECT_RATIO_WITH_DELAY is set, it will also attempt to apply the aspect ratio
-    // after a short delay.
-    private static final String ACTION_EXPAND_PIP = "android.server.cts.PipActivity.expand_pip";
-    // Intent action that this activity dynamically registers to set requested orientation.
-    // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
-    private static final String ACTION_SET_REQUESTED_ORIENTATION =
-            "android.server.cts.PipActivity.set_requested_orientation";
-    // Intent action that will finish this activity
-    private static final String ACTION_FINISH = "android.server.cts.PipActivity.finish";
-
-    // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
-    private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-    // Calls enterPictureInPicture() on creation
-    private static final String EXTRA_ENTER_PIP = "enter_pip";
-    // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
-    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
-            "enter_pip_aspect_ratio_numerator";
-    // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
-    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
-            "enter_pip_aspect_ratio_denominator";
-    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
-    private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
-    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
-    private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
-    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
-    // fixed delay
-    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
-            "set_aspect_ratio_with_delay_numerator";
-    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
-    // fixed delay
-    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
-            "set_aspect_ratio_with_delay_denominator";
-    // Adds a click listener to finish this activity when it is clicked
-    private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-    // Calls requestAutoEnterPictureInPicture() with the value provided
-    private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
-    // Starts the activity (component name) provided by the value at the end of onCreate
-    private static final String EXTRA_START_ACTIVITY = "start_activity";
-    // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
-    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
-    // Calls enterPictureInPicture() again after onPictureInPictureModeChanged(false) is called
-    private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
-    // Shows this activity over the keyguard
-    private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
-    // Adds an assertion that we do not ever get onStop() before we enter picture in picture
-    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
-    // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
-    // is processed)
-    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
-
-    private boolean mEnteredPictureInPicture;
-
-    private Handler mHandler = new Handler();
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent != null) {
-                switch (intent.getAction()) {
-                    case ACTION_ENTER_PIP:
-                        enterPictureInPictureMode();
-                        break;
-                    case ACTION_MOVE_TO_BACK:
-                        moveTaskToBack(false /* nonRoot */);
-                        break;
-                    case ACTION_EXPAND_PIP:
-                        // Trigger the activity to expand
-                        Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
-                        startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-                        startActivity(startIntent);
-
-                        if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
-                                && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
-                            // Ugly, but required to wait for the startActivity to actually start
-                            // the activity...
-                            mHandler.postDelayed(() -> {
-                                final PictureInPictureParams.Builder builder =
-                                        new PictureInPictureParams.Builder();
-                                builder.setAspectRatio(getAspectRatio(intent,
-                                        EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
-                                        EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
-                                setPictureInPictureParams(builder.build());
-                            }, 100);
-                        }
-                        break;
-                    case ACTION_SET_REQUESTED_ORIENTATION:
-                        setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
-                                EXTRA_FIXED_ORIENTATION)));
-                        break;
-                    case ACTION_FINISH:
-                        finish();
-                        break;
-                }
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // Set the fixed orientation if requested
-        if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
-            final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
-            setRequestedOrientation(ori);
-        }
-
-        // Set the window flag to show over the keyguard
-        if (getIntent().hasExtra(EXTRA_SHOW_OVER_KEYGUARD)) {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-        }
-
-        // Enter picture in picture with the given aspect ratio if provided
-        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
-            if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
-                    && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
-                try {
-                    final PictureInPictureParams.Builder builder =
-                            new PictureInPictureParams.Builder();
-                    builder.setAspectRatio(getAspectRatio(getIntent(),
-                            EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
-                            EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
-                    enterPictureInPictureMode(builder.build());
-                } catch (Exception e) {
-                    // This call can fail intentionally if the aspect ratio is too extreme
-                }
-            } else {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-            }
-        }
-
-        // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
-        // to be called before setting the aspect ratio
-        if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
-                && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
-            final PictureInPictureParams.Builder builder =
-                    new PictureInPictureParams.Builder();
-            builder.setAspectRatio(getAspectRatio(getIntent(),
-                    EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
-            try {
-                setPictureInPictureParams(builder.build());
-            } catch (Exception e) {
-                // This call can fail intentionally if the aspect ratio is too extreme
-            }
-        }
-
-        // Enable tap to finish if necessary
-        if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
-            setContentView(R.layout.tap_to_finish_pip_layout);
-            findViewById(R.id.content).setOnClickListener(v -> {
-                finish();
-            });
-        }
-
-        // Launch a new activity if requested
-        String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
-        if (launchActivityComponent != null) {
-            Intent launchIntent = new Intent();
-            launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
-            startActivity(launchIntent);
-        }
-
-        // Register the broadcast receiver
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_ENTER_PIP);
-        filter.addAction(ACTION_MOVE_TO_BACK);
-        filter.addAction(ACTION_EXPAND_PIP);
-        filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
-        filter.addAction(ACTION_FINISH);
-        registerReceiver(mReceiver, filter);
-
-        // Dump applied display metrics.
-        Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-        dumpConfiguration(config);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        // Finish self if requested
-        if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
-            finish();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        // Pause if requested
-        if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
-            SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
-        }
-
-        // Enter PIP on move to background
-        if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
-            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
-            Log.w(TAG, "Unexpected onStop() called before entering picture-in-picture");
-            finish();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        unregisterReceiver(mReceiver);
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        super.onPictureInPictureModeChanged(isInPictureInPictureMode);
-
-        // Fail early if the activity state does not match the dispatched state
-        if (isInPictureInPictureMode() != isInPictureInPictureMode) {
-            Log.w(TAG, "Received onPictureInPictureModeChanged mode=" + isInPictureInPictureMode
-                    + " activityState=" + isInPictureInPictureMode());
-            finish();
-        }
-
-        // Mark that we've entered picture-in-picture so that we can stop checking for
-        // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
-        if (isInPictureInPictureMode) {
-            mEnteredPictureInPicture = true;
-        }
-
-        if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
-            // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
-            // checking that the stacks ever changed). Therefor, we need to delay here slightly to
-            // allow the tests to verify that the stacks have changed before re-entering.
-            mHandler.postDelayed(() -> {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-            }, 1000);
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
-        dumpConfiguration(newConfig);
-    }
-
-    /**
-     * Launches a new instance of the PipActivity directly into the pinned stack.
-     */
-    static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
-        final Intent intent = new Intent(caller, PipActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchBounds(bounds);
-        options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
-        caller.startActivity(intent, options.toBundle());
-    }
-
-    /**
-     * Launches a new instance of the PipActivity in the same task that will automatically enter
-     * PiP.
-     */
-    static void launchEnterPipActivity(Activity caller) {
-        final Intent intent = new Intent(caller, PipActivity.class);
-        intent.putExtra(EXTRA_ENTER_PIP, "true");
-        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
-        caller.startActivity(intent);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    /**
-     * @return a {@link Rational} aspect ratio from the given intent and extras.
-     */
-    private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
-        return new Rational(
-                Integer.valueOf(intent.getStringExtra(extraNum)),
-                Integer.valueOf(intent.getStringExtra(extraDenom)));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java
deleted file mode 100644
index 53b4f75..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * A secondary activity that has the same behavior as {@link PipActivity}.
- */
-public class PipActivity2 extends PipActivity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java
deleted file mode 100644
index e97dc0e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.PictureInPictureParams;
-
-/**
- * An activity that can enter PiP with a fixed affinity to match
- * {@link TestActivityWithSameAffinity}.
- */
-public class PipActivityWithSameAffinity extends Activity {
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java
deleted file mode 100644
index 1abc696..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * This activity will try and enter picture in picture when it is stopped.
- */
-public class PipOnStopActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.tap_to_finish_pip_layout);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        startActivity(new Intent(this, PipActivity.class));
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        try {
-            enterPictureInPictureMode();
-        } catch (RuntimeException e) {
-            // Known failure, we expect this call to throw an exception
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java
deleted file mode 100644
index 5fa1fc8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.res.Configuration;
-
-public class PortraitOrientationActivity extends AbstractLifecycleLogActivity {
-
-    @Override
-    protected String getTag() {
-        return "PortraitOrientationActivity";
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.java
deleted file mode 100644
index aad874b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.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.server.cts;
-
-import android.content.res.Configuration;
-import android.os.Bundle;
-
-public class ResizeableActivity extends AbstractLifecycleLogActivity {
-    @Override
-    protected String getTag() {
-        return "ResizeableActivity";
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setContentView(R.layout.resizeable_activity);
-        dumpDisplaySize(getResources().getConfiguration());
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
deleted file mode 100644
index 578d7e5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class ResumeWhilePausingActivity extends Activity {
-    // Empty
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java
deleted file mode 100644
index 6e3348a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedActivity extends BroadcastReceiverActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java
deleted file mode 100644
index c53c485..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class ShowWhenLockedAttrActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = ShowWhenLockedAttrActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java
deleted file mode 100644
index dbad34d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-public class ShowWhenLockedAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = ShowWhenLockedAttrRemoveAttrActivity.class.getSimpleName();
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        setShowWhenLocked(false);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java
deleted file mode 100644
index 136706a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedAttrWithDialogActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        new AlertDialog.Builder(this)
-                .setTitle("Dialog")
-                .show();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java
deleted file mode 100644
index 5b60139..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedDialogActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java
deleted file mode 100644
index 929c9f7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedTranslucentActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-        setContentView(R.layout.translucent);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java
deleted file mode 100644
index 58d5ac7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedWithDialogActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-        new AlertDialog.Builder(this)
-                .setTitle("Dialog")
-                .show();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java
deleted file mode 100644
index e0eaecf..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class SingleInstanceActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java
deleted file mode 100644
index 7ffde13..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class SingleTaskActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java
deleted file mode 100644
index a757165..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SlowCreateActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        try {
-            Thread.sleep(2000);
-        } catch(InterruptedException e) {}
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java
deleted file mode 100644
index 22391bd..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Activity that shows a custom splashscreen when being launched.
- */
-public class SplashscreenActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        // Make sure splash screen is visible. The test won't take 5 seconds because the condition
-        // such that we can dump the state will trigger much earlier and then the test will just
-        // kill us.
-        SystemClock.sleep(5000);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java
deleted file mode 100644
index 5f36abc..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.server.cts.SwipeRefreshLayout;
-
-
-/**
- * An activity containing a SwipeRefreshLayout which prevents activity idle.
- */
-public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = SwipeRefreshActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(new SwipeRefreshLayout(this));
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java
deleted file mode 100644
index a061d9e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * An extension of {@link android.support.v4.widget.SwipeRefreshLayout} that calls
- * {@link #setRefreshing} during construction, preventing activity idle.
- */
-public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {
-    public SwipeRefreshLayout(Context context) {
-        super(context);
-        initialize();
-    }
-
-    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        initialize();
-    }
-
-    private void initialize() {
-        setRefreshing(true);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
deleted file mode 100644
index f9a86c6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = TestActivity.class.getSimpleName();
-
-    // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
-    private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-
-    // Finishes the activity
-    private static final String ACTION_FINISH_SELF = "android.server.cts.TestActivity.finish_self";
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent != null && intent.getAction().equals(ACTION_FINISH_SELF)) {
-                finish();
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Set the fixed orientation if requested
-        if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
-            final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
-            setRequestedOrientation(ori);
-        }
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        registerReceiver(mReceiver, new IntentFilter(ACTION_FINISH_SELF));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-        dumpConfiguration(config);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        unregisterReceiver(mReceiver);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
-        dumpConfiguration(newConfig);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java
deleted file mode 100644
index db75dee..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.PictureInPictureParams;
-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.os.Bundle;
-import android.util.Log;
-
-public class TestActivityWithSameAffinity extends TestActivity {
-
-    private static final String TAG = TestActivityWithSameAffinity.class.getSimpleName();
-
-    // Calls enterPictureInPicture() on creation
-    private static final String EXTRA_ENTER_PIP = "enter_pip";
-    // Starts the activity (component name) provided by the value at the end of onCreate
-    private static final String EXTRA_START_ACTIVITY = "start_activity";
-    // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
-    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Enter picture in picture if requested
-        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
-            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-        }
-
-        // Launch a new activity if requested
-        String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
-        if (launchActivityComponent != null) {
-            Intent launchIntent = new Intent();
-            launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
-            startActivity(launchIntent);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        // Finish self if requested
-        if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
-            finish();
-        }
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java
deleted file mode 100644
index 6684999..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Handler;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TopActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = TopActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
-        if (useWallpaper) {
-            setTheme(R.style.WallpaperTheme);
-        }
-
-        final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
-        if (finishDelay > 0) {
-            Handler handler = new Handler();
-            handler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    Log.d(TAG, "Calling finish()");
-                    finish();
-                }
-            }, finishDelay);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java
deleted file mode 100644
index 2305582..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TopLeftLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java
deleted file mode 100644
index 701d3db..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TopRightLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java
deleted file mode 100644
index b3f9444..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.content.Intent;
-
-public class TrampolineActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = TrampolineActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        // Add a delay here to expose more easily a failure case where the real target
-        // activity is visible before it's launched, because its task is being brought
-        // to foreground. We need to verify that 'am start' is unblocked correctly.
-        try {
-            Thread.sleep(2000);
-        } catch(InterruptedException e) {}
-        Intent intent = new Intent(this, SingleTaskActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
-
-        startActivity(intent);
-        finish();
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java
deleted file mode 100644
index 96697aa..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TranslucentActivity extends Activity {
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java
deleted file mode 100644
index e979712..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class TranslucentAssistantActivity extends AssistantActivity {
-
-    /**
-     * Launches a new instance of the TranslucentAssistantActivity directly into the assistant
-     * stack.
-     */
-    static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
-        final Intent intent = new Intent(caller, TranslucentAssistantActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-        if (extras != null) {
-            intent.putExtras(extras);
-        }
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(6 /* ActivityManager.StackId.ASSISTANT_STACK_ID */);
-        caller.startActivity(intent, options.toBundle());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java
deleted file mode 100644
index 1d10a51..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-public class TranslucentTestActivity extends TestActivity {
-
-    private static final String TAG = TranslucentTestActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        setContentView(R.layout.task_overlay);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java
deleted file mode 100644
index 9d96898..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Handler;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TranslucentTopActivity extends AbstractLifecycleLogActivity {
-
-    private static final String TAG = TranslucentTopActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
-        if (useWallpaper) {
-            setTheme(R.style.TranslucentWallpaperTheme);
-        }
-
-        final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
-        if (finishDelay > 0) {
-            Handler handler = new Handler();
-            handler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    Log.d(TAG, "Calling finish()");
-                    finish();
-                }
-            }, finishDelay);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java
deleted file mode 100644
index 79e31b3..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class TurnScreenOnActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java
deleted file mode 100644
index 5e1f5d1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnAttrActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnAttrActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java
deleted file mode 100644
index b1b860f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class TurnScreenOnAttrDismissKeyguardActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnAttrDismissKeyguardActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        ((KeyguardManager) getSystemService(KEYGUARD_SERVICE))
-                .requestDismissKeyguard(this, new KeyguardDismissLoggerCallback(this));
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java
deleted file mode 100644
index 29911fe..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnAttrRemoveAttrActivity.class.getSimpleName();
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        setTurnScreenOn(false);
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java
deleted file mode 100644
index 487f785..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class TurnScreenOnDismissKeyguardActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
-                new KeyguardDismissLoggerCallback(this));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java
deleted file mode 100644
index 57ff4fb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnShowOnLockActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnShowOnLockActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java
deleted file mode 100644
index 29c71d0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnSingleTaskActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnSingleTaskActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java
deleted file mode 100644
index 14c2801..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-import android.view.WindowManager;
-
-public class TurnScreenOnWithRelayoutActivity extends AbstractLifecycleLogActivity {
-    private static final String TAG = TurnScreenOnWithRelayoutActivity.class.getSimpleName();
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // This is to force a relayout, specifically with insets changed. When the insets are
-        // changed, it will trigger a performShow that could turn the screen on.
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java
deleted file mode 100644
index dd12acb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.os.Bundle;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-
-import java.util.HashMap;
-
-/**
- * Activity that is able to create and destroy a virtual display.
- */
-public class VirtualDisplayActivity extends Activity implements SurfaceHolder.Callback {
-    private static final String TAG = "VirtualDisplayActivity";
-
-    private static final int DEFAULT_DENSITY_DPI = 160;
-    private static final String KEY_DENSITY_DPI = "density_dpi";
-    private static final String KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD
-            = "can_show_with_insecure_keyguard";
-    private static final String KEY_PUBLIC_DISPLAY = "public_display";
-    private static final String KEY_RESIZE_DISPLAY = "resize_display";
-    private static final String KEY_LAUNCH_TARGET_ACTIVITY = "launch_target_activity";
-    private static final String KEY_COUNT = "count";
-
-    private DisplayManager mDisplayManager;
-
-    // Container for details about a pending virtual display creation request.
-    private static class VirtualDisplayRequest {
-        public final SurfaceView surfaceView;
-        public final Bundle extras;
-
-        public VirtualDisplayRequest(SurfaceView surfaceView, Bundle extras) {
-            this.surfaceView = surfaceView;
-            this.extras = extras;
-        }
-    }
-
-    // Container to hold association between an active virtual display and surface view.
-    private static class VirtualDisplayEntry {
-        public final VirtualDisplay display;
-        public final SurfaceView surfaceView;
-        public final boolean resizeDisplay;
-        public final int density;
-
-        public VirtualDisplayEntry(VirtualDisplay display, SurfaceView surfaceView, int density,
-                boolean resizeDisplay) {
-            this.display = display;
-            this.surfaceView = surfaceView;
-            this.density = density;
-            this.resizeDisplay = resizeDisplay;
-        }
-    }
-
-    private HashMap<Surface, VirtualDisplayRequest> mPendingDisplayRequests;
-    private HashMap<Surface, VirtualDisplayEntry> mVirtualDisplays;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.virtual_display_layout);
-
-        mVirtualDisplays = new HashMap<>();
-        mPendingDisplayRequests = new HashMap<>();
-        mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        final Bundle extras = intent.getExtras();
-        if (extras == null) {
-            return;
-        }
-
-        String command = extras.getString("command");
-        switch (command) {
-            case "create_display":
-                createVirtualDisplay(extras);
-                break;
-            case "destroy_display":
-                destroyVirtualDisplays();
-                break;
-            case "resize_display":
-                resizeDisplay();
-                break;
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        destroyVirtualDisplays();
-    }
-
-    private void createVirtualDisplay(Bundle extras) {
-        final int requestedCount = extras.getInt(KEY_COUNT, 1);
-        Log.d(TAG, "createVirtualDisplays. requested count:" + requestedCount);
-
-        for (int displayCount = 0; displayCount < requestedCount; ++displayCount) {
-            final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
-            final SurfaceView surfaceView = new SurfaceView(this);
-            surfaceView.setLayoutParams(new ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-            surfaceView.getHolder().addCallback(this);
-            mPendingDisplayRequests.put(surfaceView.getHolder().getSurface(),
-                    new VirtualDisplayRequest(surfaceView, extras));
-            root.addView(surfaceView);
-        }
-    }
-
-    private void destroyVirtualDisplays() {
-        Log.d(TAG, "destroyVirtualDisplays");
-        final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
-
-        for (VirtualDisplayEntry entry : mVirtualDisplays.values()) {
-            Log.d(TAG, "destroying:" + entry.display);
-            entry.display.release();
-            root.removeView(entry.surfaceView);
-        }
-
-        mPendingDisplayRequests.clear();
-        mVirtualDisplays.clear();
-    }
-
-    @Override
-    public void surfaceCreated(SurfaceHolder surfaceHolder) {
-        final VirtualDisplayRequest entry =
-                mPendingDisplayRequests.remove(surfaceHolder.getSurface());
-
-        if (entry == null) {
-            return;
-        }
-
-        final int densityDpi = entry.extras.getInt(KEY_DENSITY_DPI, DEFAULT_DENSITY_DPI);
-        final boolean resizeDisplay = entry.extras.getBoolean(KEY_RESIZE_DISPLAY);
-        final String launchActivityName = entry.extras.getString(KEY_LAUNCH_TARGET_ACTIVITY);
-        final Surface surface = surfaceHolder.getSurface();
-
-        // Initially, the surface will not have a set width or height so rely on the parent.
-        // This should be accurate with match parent on both params.
-        final int width = surfaceHolder.getSurfaceFrame().width();
-        final int height = surfaceHolder.getSurfaceFrame().height();
-
-        int flags = 0;
-
-        final boolean canShowWithInsecureKeyguard
-                = entry.extras.getBoolean(KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD);
-        if (canShowWithInsecureKeyguard) {
-            flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
-        }
-
-        final boolean publicDisplay = entry.extras.getBoolean(KEY_PUBLIC_DISPLAY);
-        if (publicDisplay) {
-            flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-        }
-
-        Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: "
-                + densityDpi + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
-                + ", publicDisplay=" + publicDisplay);
-        try {
-            VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
-                    "VirtualDisplay" + mVirtualDisplays.size(), width,
-                    height, densityDpi, surface, flags);
-            mVirtualDisplays.put(surface,
-                    new VirtualDisplayEntry(virtualDisplay, entry.surfaceView, densityDpi,
-                            resizeDisplay));
-            if (launchActivityName != null) {
-                final int displayId = virtualDisplay.getDisplay().getDisplayId();
-                Log.d(TAG, "Launch activity after display created: activityName="
-                        + launchActivityName + ", displayId=" + displayId);
-                launchActivity(launchActivityName, displayId);
-            }
-        } catch (IllegalArgumentException e) {
-            final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
-            // This is expected when trying to create show-when-locked public display.
-            root.removeView(entry.surfaceView);
-        }
-
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
-        final VirtualDisplayEntry entry = mVirtualDisplays.get(surfaceHolder.getSurface());
-
-        if (entry != null && entry.resizeDisplay) {
-            entry.display.resize(width, height, entry.density);
-        }
-    }
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
-    }
-
-    /** Resize virtual display to half of the surface frame size. */
-    private void resizeDisplay() {
-        final VirtualDisplayEntry vd = (VirtualDisplayEntry) mVirtualDisplays.values().toArray()[0];
-        final SurfaceHolder surfaceHolder = vd.surfaceView.getHolder();
-        vd.display.resize(surfaceHolder.getSurfaceFrame().width() / 2,
-                surfaceHolder.getSurfaceFrame().height() / 2, vd.density);
-    }
-
-    private void launchActivity(String activityName, int displayId) {
-        final Bundle extras = new Bundle();
-        extras.putBoolean("launch_activity", true);
-        extras.putString("target_activity", activityName);
-        extras.putInt("display_id", displayId);
-        ActivityLauncher.launchActivityFromExtras(this, extras);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java
deleted file mode 100644
index 6ef25e2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.cts.verifier.vr.MockVrListenerService;
-
-/**
- * Activity that is able to create and destroy a virtual display.
- */
-public class VrTestActivity extends Activity {
-    private static final String TAG = "VrTestActivity";
-    private static final boolean DEBUG = false;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        if (DEBUG) Log.i(TAG, "onCreate called.");
-        super.onCreate(savedInstanceState);
-        try {
-            setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Could not set VR mode: " + e);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        if (DEBUG) Log.i(TAG, "onResume called.");
-        super.onResume();
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (DEBUG) Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
-        super.onWindowFocusChanged(hasFocus);
-    }
-
-    @Override
-    protected void onPause() {
-        if (DEBUG) Log.i(TAG, "onPause called.");
-        super.onPause();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java
deleted file mode 100644
index 63e11d5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class WallpaperActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
deleted file mode 100644
index 15b55f5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.tools;
-
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
-
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.server.cts.TestActivity;
-import android.util.Log;
-
-/** Utility class which contains common code for launching activities. */
-public class ActivityLauncher {
-    private static final String TAG = ActivityLauncher.class.getSimpleName();
-
-    public static void launchActivityFromExtras(final Context context, Bundle extras) {
-        if (extras == null || !extras.getBoolean("launch_activity")) {
-            return;
-        }
-
-        Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
-
-        final Intent newIntent = new Intent();
-        final String targetActivity = extras.getString("target_activity");
-        if (targetActivity != null) {
-            final String extraPackageName = extras.getString("package_name");
-            final String packageName = extraPackageName != null ? extraPackageName
-                    : context.getApplicationContext().getPackageName();
-            newIntent.setComponent(new ComponentName(packageName,
-                    packageName + "." + targetActivity));
-        } else {
-            newIntent.setClass(context, TestActivity.class);
-        }
-
-        if (extras.getBoolean("launch_to_the_side")) {
-            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
-            if (extras.getBoolean("random_data")) {
-                final Uri data = new Uri.Builder()
-                        .path(String.valueOf(System.currentTimeMillis()))
-                        .build();
-                newIntent.setData(data);
-            }
-        }
-        if (extras.getBoolean("multiple_task")) {
-            newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-        }
-        if (extras.getBoolean("new_task")) {
-            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        }
-
-        if (extras.getBoolean("reorder_to_front")) {
-            newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
-        }
-
-        ActivityOptions options = null;
-        final int displayId = extras.getInt("display_id", -1);
-        if (displayId != -1) {
-            options = ActivityOptions.makeBasic();
-            options.setLaunchDisplayId(displayId);
-            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        }
-
-        try {
-            context.startActivity(newIntent, options != null ? options.toBundle() : null);
-        } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
deleted file mode 100644
index ddb08a2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceDebuggableApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml
deleted file mode 100644
index c00f2b6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/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.server.cts.debuggable">
-
-    <!--
-     * Security policy requires that only debuggable processes can be profiled
-     * which is tested by ActivityManagerAmProfileTests.
-     -->
-    <application android:debuggable="true">
-        <activity android:name=".DebuggableAppActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true" />
-    </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java
deleted file mode 100644
index 504ffcf..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.debuggable;
-
-import android.app.Activity;
-
-public class DebuggableAppActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk
deleted file mode 100644
index 98feb61..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceDisplaySizeApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml
deleted file mode 100644
index 7727759..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.displaysize.app">
-
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-
-    <!-- Set a ridiculously high value for required smallest width. Hopefully
-         we never have to run CTS on Times Square display. -->
-    <supports-screens android:requiresSmallestWidthDp="100000" />
-
-    <application>
-        <activity android:name=".SmallestWidthActivity"
-                  android:exported="true" />
-    </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java
deleted file mode 100644
index 5da44c7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java
+++ /dev/null
@@ -1,38 +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.displaysize.app;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class SmallestWidthActivity extends Activity {
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-
-        final Bundle extras = intent.getExtras();
-        if (extras != null && extras.getBoolean("launch_another_activity")) {
-            Intent startIntent = new Intent();
-            startIntent.setComponent(
-                    new ComponentName("android.server.cts", "android.server.cts.TestActivity"));
-            startActivity(startIntent);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk
deleted file mode 100644
index 1456d22..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceServicesTestSecondApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml
deleted file mode 100644
index 53aa2d4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +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.server.cts.second">
-
-    <application>
-        <activity
-            android:name=".SecondActivity"
-            android:resizeableActivity="true"
-            android:allowEmbedded="true"
-            android:exported="true" />
-        <activity
-            android:name=".SecondActivityNoEmbedding"
-            android:resizeableActivity="true"
-            android:allowEmbedded="false"
-            android:exported="true" />
-        <receiver
-            android:name=".LaunchBroadcastReceiver"
-            android:enabled="true"
-            android:exported="true" >
-            <intent-filter>
-                <action android:name="android.server.cts.second.LAUNCH_BROADCAST_ACTION"/>
-            </intent-filter>
-        </receiver>
-    </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java
deleted file mode 100644
index a8ebb86..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.ActivityOptions;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-/** Broadcast receiver that can launch activities. */
-public class LaunchBroadcastReceiver extends BroadcastReceiver {
-    private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final Bundle extras = intent.getExtras();
-        final Intent newIntent = new Intent();
-
-        final String targetActivity = extras != null ? extras.getString("target_activity") : null;
-        if (targetActivity != null) {
-            String packageName = extras.getString("package_name");
-            newIntent.setComponent(new ComponentName(packageName,
-                    packageName + "." + targetActivity));
-        } else {
-            newIntent.setClass(context, SecondActivity.class);
-        }
-
-        ActivityOptions options = ActivityOptions.makeBasic();
-        int displayId = extras.getInt("display_id", -1);
-        if (displayId != -1) {
-            options.setLaunchDisplayId(displayId);
-            newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        } else {
-            newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-
-        try {
-            context.startActivity(newIntent, options.toBundle());
-        } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java
deleted file mode 100644
index cea9ed5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.Activity;
-
-public class SecondActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java
deleted file mode 100644
index 543e008..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.Activity;
-
-public class SecondActivityNoEmbedding extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk
deleted file mode 100644
index 733f7a9..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceServicesTestThirdApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml
deleted file mode 100644
index aed24c7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +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.server.cts.third">
-
-    <application>
-        <activity android:name=".ThirdActivity"
-                  android:resizeableActivity="true"
-                  android:allowEmbedded="true"
-                  android:exported="true" />
-    </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java
deleted file mode 100644
index eeddc3d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.third;
-
-import android.app.Activity;
-
-public class ThirdActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
deleted file mode 100644
index add7e42..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.StateLogger.log;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityAndWindowManagerOverrideConfigTests
- */
-public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
-    private static final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
-
-    private class ConfigurationChangeObserver {
-        private final Pattern mConfigurationChangedPattern =
-            Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
-
-        private ConfigurationChangeObserver() {
-        }
-
-        private boolean findConfigurationChange(String activityName, String logSeparator)
-                throws DeviceNotAvailableException, InterruptedException {
-            int tries = 0;
-            boolean observedChange = false;
-            while (tries < 5 && !observedChange) {
-                final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-                log("Looking at logcat");
-                for (int i = lines.length - 1; i >= 0; i--) {
-                    final String line = lines[i].trim();
-                    log(line);
-                    Matcher matcher = mConfigurationChangedPattern.matcher(line);
-                    if (matcher.matches()) {
-                        observedChange = true;
-                        break;
-                    }
-                }
-                tries++;
-                Thread.sleep(500);
-            }
-            return observedChange;
-        }
-    }
-
-    public void testReceiveOverrideConfigFromRelayout() throws Exception {
-        if (!supportsFreeform()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support freeform. Skipping test.");
-            return;
-        }
-
-        launchActivityInStack(TEST_ACTIVITY_NAME, FREEFORM_WORKSPACE_STACK_ID);
-
-        setDeviceRotation(0);
-        String logSeparator = clearLogcat();
-        resizeActivityTask(TEST_ACTIVITY_NAME, 0, 0, 100, 100);
-        ConfigurationChangeObserver c = new ConfigurationChangeObserver();
-        final boolean reportedSizeAfterResize = c.findConfigurationChange(TEST_ACTIVITY_NAME,
-                logSeparator);
-        assertTrue("Expected to observe configuration change when resizing",
-                reportedSizeAfterResize);
-
-        logSeparator = clearLogcat();
-        setDeviceRotation(2);
-        final boolean reportedSizeAfterRotation = c.findConfigurationChange(TEST_ACTIVITY_NAME,
-                logSeparator);
-        assertFalse("Not expected to observe configuration change after flip rotation",
-                reportedSizeAfterRotation);
-    }
-}
-
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
deleted file mode 100644
index c51f24a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.StateLogger.logE;
-
-import android.platform.test.annotations.Presubmit;
-
-import java.lang.Exception;
-import java.lang.String;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerActivityVisibilityTests
- */
-public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
-    private static final String TRANSLUCENT_ACTIVITY = "AlwaysFocusablePipActivity";
-    private static final String PIP_ON_PIP_ACTIVITY = "LaunchPipOnPipActivity";
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
-    private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
-    private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
-    private static final String MOVE_TASK_TO_BACK_ACTIVITY_NAME = "MoveTaskToBackActivity";
-    private static final String SWIPE_REFRESH_ACTIVITY = "SwipeRefreshActivity";
-
-    private static final String NOHISTORY_ACTIVITY = "NoHistoryActivity";
-    private static final String TURN_SCREEN_ON_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrActivity";
-    private static final String TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME = "TurnScreenOnShowOnLockActivity";
-    private static final String TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrRemoveAttrActivity";
-    private static final String TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME = "TurnScreenOnSingleTaskActivity";
-    private static final String TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
-            "TurnScreenOnWithRelayoutActivity";
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        tearDownLockCredentials();
-    }
-
-    public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
-        if (!supportsPip()) {
-            return;
-        }
-
-        executeShellCommand(getAmStartCmdOverHome(PIP_ON_PIP_ACTIVITY));
-        mAmWmState.waitForValidState(mDevice, PIP_ON_PIP_ACTIVITY);
-        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
-        // translucent activity.
-        executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
-
-        mAmWmState.computeState(mDevice, new String[] {PIP_ON_PIP_ACTIVITY, TRANSLUCENT_ACTIVITY});
-        mAmWmState.assertFrontStack("Pinned stack must be the front stack.", PINNED_STACK_ID);
-        mAmWmState.assertVisibility(PIP_ON_PIP_ACTIVITY, true);
-        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
-    }
-
-    /**
-     * Asserts that the home activity is visible when a translucent activity is launched in the
-     * fullscreen stack over the home activity.
-     */
-    public void testTranslucentActivityOnTopOfHome() throws Exception {
-        if (noHomeScreen()) {
-            return;
-        }
-
-        launchHomeActivity();
-        launchActivity(TRANSLUCENT_ACTIVITY);
-
-        mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
-        mAmWmState.assertFrontStack(
-                "Fullscreen stack must be the front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
-        mAmWmState.assertHomeActivityVisible(true);
-    }
-
-    /**
-     * Assert that the home activity is visible if a task that was launched from home is pinned
-     * and also assert the next task in the fullscreen stack isn't visible.
-     */
-    public void testHomeVisibleOnActivityTaskPinned() throws Exception {
-        if (!supportsPip()) {
-            return;
-        }
-
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY_NAME);
-        launchHomeActivity();
-        launchActivity(TRANSLUCENT_ACTIVITY);
-        executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
-
-        mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
-
-        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
-        mAmWmState.assertHomeActivityVisible(true);
-    }
-
-    public void testTranslucentActivityOverDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
-        launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME});
-        launchActivityInStack(TRANSLUCENT_ACTIVITY_NAME, DOCKED_STACK_ID);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME,
-                TRANSLUCENT_ACTIVITY_NAME}, false /* compareTaskAndStackBounds */);
-        mAmWmState.assertContainsStack("Must contain docked stack", DOCKED_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain fullscreen stack",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
-        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY_NAME, true);
-    }
-
-    @Presubmit
-    public void testTurnScreenOnActivity() throws Exception {
-        sleepDevice();
-        launchActivity(TURN_SCREEN_ON_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, true);
-    }
-
-    public void testFinishActivityInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        // Launch two activities in docked stack.
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        getLaunchActivityBuilder().setTargetActivityName(BROADCAST_RECEIVER_ACTIVITY).execute();
-        mAmWmState.computeState(mDevice, new String[] { BROADCAST_RECEIVER_ACTIVITY });
-        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
-        // Launch something to fullscreen stack to make it focused.
-        launchActivityInStack(TEST_ACTIVITY_NAME, 1);
-        mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
-        // Finish activity in non-focused (docked) stack.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-        mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
-    }
-
-    public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
-        performFinishActivityWithMoveTaskToBack("on_pause");
-    }
-
-    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
-        performFinishActivityWithMoveTaskToBack("on_stop");
-    }
-
-    private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
-        // Make sure home activity is visible.
-        launchHomeActivity();
-        if (!noHomeScreen()) {
-            mAmWmState.assertHomeActivityVisible(true /* visible */);
-        }
-
-        // Launch an activity that calls "moveTaskToBack" to finish itself.
-        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY_NAME, "finish_point", finishPoint);
-        mAmWmState.waitForValidState(mDevice, MOVE_TASK_TO_BACK_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, true);
-
-        // Launch a different activity on top.
-        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
-        mAmWmState.waitForActivityState(mDevice, BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
-        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, false);
-        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
-
-        // Finish the top-most activity.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
-        // Home must be visible.
-        if (!noHomeScreen()) {
-            mAmWmState.waitForHomeActivityVisible(mDevice);
-            mAmWmState.assertHomeActivityVisible(true /* visible */);
-        }
-    }
-
-    /**
-     * Asserts that launching between reorder to front activities exhibits the correct backstack
-     * behavior.
-     */
-    public void testReorderToFrontBackstack() throws Exception {
-        // Start with home on top
-        launchHomeActivity();
-        if (!noHomeScreen()) {
-            mAmWmState.assertHomeActivityVisible(true /* visible */);
-        }
-
-        // Launch the launching activity to the foreground
-        launchActivity(LAUNCHING_ACTIVITY);
-
-        // Launch the alternate launching activity from launching activity with reorder to front.
-        getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
-                .setReorderToFront(true).execute();
-
-        // Launch the launching activity from the alternate launching activity with reorder to
-        // front.
-        getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY)
-                .setLaunchingActivityName(ALT_LAUNCHING_ACTIVITY).setReorderToFront(true)
-                .execute();
-
-        // Press back
-        pressBackButton();
-
-        mAmWmState.waitForValidState(mDevice, ALT_LAUNCHING_ACTIVITY);
-
-        // Ensure the alternate launching activity is in focus
-        mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
-                ALT_LAUNCHING_ACTIVITY);
-    }
-
-    /**
-     * Asserts that the activity focus and history is preserved moving between the activity and
-     * home stack.
-     */
-    public void testReorderToFrontChangingStack() throws Exception {
-        // Start with home on top
-        launchHomeActivity();
-        if (!noHomeScreen()) {
-            mAmWmState.assertHomeActivityVisible(true /* visible */);
-        }
-
-        // Launch the launching activity to the foreground
-        launchActivity(LAUNCHING_ACTIVITY);
-
-        // Launch the alternate launching activity from launching activity with reorder to front.
-        getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
-                .setReorderToFront(true).execute();
-
-        // Return home
-        launchHomeActivity();
-        if (!noHomeScreen()) {
-            mAmWmState.assertHomeActivityVisible(true /* visible */);
-        }
-        // Launch the launching activity from the alternate launching activity with reorder to
-        // front.
-
-        // Bring launching activity back to the foreground
-        launchActivity(LAUNCHING_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, LAUNCHING_ACTIVITY);
-
-        // Ensure the alternate launching activity is still in focus.
-        mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
-                ALT_LAUNCHING_ACTIVITY);
-
-        pressBackButton();
-
-        mAmWmState.waitForValidState(mDevice, LAUNCHING_ACTIVITY);
-
-        // Ensure launching activity was brought forward.
-        mAmWmState.assertFocusedActivity("Launching Activity must be focused",
-                LAUNCHING_ACTIVITY);
-    }
-
-    /**
-     * Asserts that a nohistory activity is stopped and removed immediately after a resumed activity
-     * above becomes visible and does not idle.
-     */
-    public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
-        if (noHomeScreen()) {
-            return;
-        }
-
-        // Start with home on top
-        launchHomeActivity();
-
-        // Launch no history activity
-        launchActivity(NOHISTORY_ACTIVITY);
-
-        // Launch an activity with a swipe refresh layout configured to prevent idle.
-        launchActivity(SWIPE_REFRESH_ACTIVITY);
-
-        pressBackButton();
-        mAmWmState.waitForHomeActivityVisible(mDevice);
-        mAmWmState.assertHomeActivityVisible(true);
-    }
-
-    public void testTurnScreenOnAttrNoLockScreen() throws Exception {
-        wakeUpAndRemoveLock();
-        sleepDevice();
-        final String logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, true);
-        assertTrue(isDisplayOn());
-        assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
-    }
-
-    public void testTurnScreenOnAttrWithLockScreen() throws Exception {
-        if (!isHandheld()) {
-            // This test requires the ability to have a lock screen.
-            return;
-        }
-
-        setLockCredential();
-        sleepDevice();
-        final String logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
-        assertFalse(isDisplayOn());
-        assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
-    }
-
-    public void testTurnScreenOnShowOnLockAttr() throws Exception {
-        sleepDevice();
-        mAmWmState.waitForAllStoppedActivities(mDevice);
-        final String logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, true);
-        assertTrue(isDisplayOn());
-        assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, logSeparator);
-    }
-
-    public void testTurnScreenOnAttrRemove() throws Exception {
-        sleepDevice();
-        mAmWmState.waitForAllStoppedActivities(mDevice);
-        String logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {
-                TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME});
-        assertTrue(isDisplayOn());
-        assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
-
-        sleepDevice();
-        mAmWmState.waitForAllStoppedActivities(mDevice);
-        logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
-        assertFalse(isDisplayOn());
-        assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
-    }
-
-    public void testTurnScreenOnSingleTask() throws Exception {
-        sleepDevice();
-        String logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
-        assertTrue(isDisplayOn());
-        assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
-
-        sleepDevice();
-        logSeparator = clearLogcat();
-        launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
-        assertTrue(isDisplayOn());
-        assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
-    }
-
-    public void testTurnScreenOnActivity_withRelayout() throws Exception {
-        sleepDevice();
-        launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY });
-        mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
-
-        String logSeparator = clearLogcat();
-        sleepDevice();
-        mAmWmState.waitFor("Waiting for stopped state", () ->
-                lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
-
-        // Ensure there was an actual stop if the waitFor timed out.
-        assertTrue(lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
-        assertFalse(isDisplayOn());
-    }
-
-    private boolean lifecycleStopOccurred(String activityName, String logSeparator) {
-        try {
-            ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                    logSeparator);
-            return lifecycleCounts.mStopCount > 0;
-        } catch (DeviceNotAvailableException e) {
-            logE(e.toString());
-            return false;
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
deleted file mode 100644
index f462c60..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.google.common.io.Files;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.lang.StringBuilder;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmProfileTests
- *
- * Please talk to Android Studio team first if you want to modify or delete these tests.
- */
-public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
-
-    private static final String TEST_PACKAGE_NAME = "android.server.cts.debuggable";
-    private static final String TEST_ACTIVITY_NAME = "DebuggableAppActivity";
-    private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
-    private static final String FIRST_WORD_NO_STREAMING = "*version\n";
-    private static final String FIRST_WORD_STREAMING = "SLOW";  // Magic word set by runtime.
-
-    private ITestDevice mDevice;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mDevice = getDevice();
-        setComponentName(TEST_PACKAGE_NAME);
-    }
-
-    /**
-     * Test am profile functionality with the following 3 configurable options:
-     *    starting the activity before start profiling? yes;
-     *    sampling-based profiling? no;
-     *    using streaming output mode? no.
-     */
-    public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
-        // am profile start ... , and the same to the following 3 test methods.
-        testProfile(true, false, false);
-    }
-
-    /**
-     * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
-     * only different in the three configuration options.
-     */
-    public void testAmProfileStartNoSamplingStreaming() throws Exception {
-        testProfile(true, false, true);
-    }
-    public void testAmProfileStartSamplingNoStreaming() throws Exception {
-        testProfile(true, true, false);
-    }
-    public void testAmProfileStartSamplingStreaming() throws Exception {
-        testProfile(true, true, true);
-    }
-    public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
-        // am start --start-profiler ..., and the same to the following 3 test methods.
-        testProfile(false, false, false);
-    }
-    public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
-        testProfile(false, false, true);
-    }
-    public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
-        testProfile(false, true, false);
-    }
-    public void testAmStartStartProfilerSamplingStreaming() throws Exception {
-        testProfile(false, true, true);
-    }
-
-    private void testProfile(boolean startActivityFirst,
-                             boolean sampling, boolean streaming) throws Exception {
-        if (startActivityFirst) {
-            launchActivity(TEST_ACTIVITY_NAME);
-        }
-
-        String cmd = getStartCmd(TEST_ACTIVITY_NAME, startActivityFirst, sampling, streaming);
-        executeShellCommand(cmd);
-        // Go to home screen and then warm start the activity to generate some interesting trace.
-        pressHomeButton();
-        launchActivity(TEST_ACTIVITY_NAME);
-
-        cmd = "am profile stop " + componentName;
-        executeShellCommand(cmd);
-        // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
-        // file is complete.
-        try {
-            Thread.sleep(100);
-        } catch (InterruptedException e) {
-            //ignored
-        }
-        verifyOutputFileFormat(streaming);
-    }
-
-    private String getStartCmd(String activityName, boolean activityAlreadyStarted,
-                                        boolean sampling, boolean streaming) {
-        StringBuilder builder = new StringBuilder("am");
-        if (activityAlreadyStarted) {
-            builder.append(" profile start");
-        } else {
-            builder.append(String.format(" start -n %s/.%s -W -S --start-profiler %s",
-                                         componentName, activityName, OUTPUT_FILE_PATH));
-        }
-        if (sampling) {
-            builder.append(" --sampling 1000");
-        }
-        if (streaming) {
-            builder.append(" --streaming");
-        }
-        if (activityAlreadyStarted) {
-            builder.append(String.format(" %s %s", componentName, OUTPUT_FILE_PATH));
-        } else {
-
-        }
-        return builder.toString();
-    }
-
-    private void verifyOutputFileFormat(boolean streaming) throws Exception {
-        String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
-        byte[] data = readFileOnClient(OUTPUT_FILE_PATH);
-        assertTrue("data size=" + data.length, data.length >= expectedFirstWord.length());
-        String actualFirstWord = new String(data, 0, expectedFirstWord.length());
-        assertTrue("Unexpected first word: '" + actualFirstWord + "'",
-                   actualFirstWord.equals(expectedFirstWord));
-        // Clean up.
-        executeShellCommand("rm -f " + OUTPUT_FILE_PATH);
-    }
-
-    private byte[] readFileOnClient(String clientPath) throws Exception {
-        assertTrue("File not found on client: " + clientPath,
-                mDevice.doesFileExist(clientPath));
-        File copyOnHost = File.createTempFile("host", "copy");
-        try {
-            executeAdbCommand("pull", clientPath, copyOnHost.getPath());
-            return Files.toByteArray(copyOnHost);
-        } finally {
-            FileUtil.deleteFile(copyOnHost);
-        }
-    }
-
-    private String[] executeAdbCommand(String... command) throws DeviceNotAvailableException {
-        String output = mDevice.executeAdbCommand(command);
-        // "".split() returns { "" }, but we want an empty array
-        String[] lines = output.equals("") ? new String[0] : output.split("\n");
-        return lines;
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
deleted file mode 100644
index f6df4a8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmStartOptionsTests
- */
-public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
-
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String ENTRYPOINT_ACTIVITY_NAME = "EntryPointAliasActivity";
-    private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
-
-    public void testDashD() throws Exception {
-        final String activityComponentName =
-                ActivityManagerTestBase.getActivityComponentName(TEST_ACTIVITY_NAME);
-
-        final String[] waitForActivityRecords = new String[] {activityComponentName};
-
-        // Run at least 2 rounds to verify that -D works with an existing process.
-        // -D could fail in this case if the force stop of process is broken.
-        int prevProcId = -1;
-        for (int i = 0; i < 2; i++) {
-            executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME) + " -D");
-
-            mAmWmState.waitForDebuggerWindowVisible(mDevice, waitForActivityRecords);
-            int procId = mAmWmState.getAmState().getActivityProcId(activityComponentName);
-
-            assertTrue("Invalid ProcId.", procId >= 0);
-            if (i > 0) {
-                assertTrue("Run " + i + " didn't start new proc.", prevProcId != procId);
-            }
-            prevProcId = procId;
-        }
-    }
-
-    public void testDashW_Direct() throws Exception {
-        testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
-    }
-
-    public void testDashW_Indirect() throws Exception {
-        testDashW(ENTRYPOINT_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
-    }
-
-    private void testDashW(final String entryActivity, final String actualActivity)
-            throws Exception {
-        // Test cold start
-        startActivityAndVerifyResult(entryActivity, actualActivity, true);
-
-        // Test warm start
-        pressHomeButton();
-        startActivityAndVerifyResult(entryActivity, actualActivity, false);
-
-        // Test "hot" start (app already in front)
-        startActivityAndVerifyResult(entryActivity, actualActivity, false);
-    }
-
-    private void startActivityAndVerifyResult(final String entryActivity,
-            final String actualActivity, boolean shouldStart) throws Exception {
-        // See TODO below
-        // final String logSeparator = clearLogcat();
-
-        // Pass in different data only when cold starting. This is to make the intent
-        // different in subsequent warm/hot launches, so that the entrypoint alias
-        // activity is always started, but the actual activity is not started again
-        // because of the NEW_TASK and singleTask flags.
-        final String result = executeShellCommand(getAmStartCmd(entryActivity) + " -W"
-                + (shouldStart ? " -d about:blank" : ""));
-
-        // Verify shell command return value
-        verifyShellOutput(result, actualActivity, shouldStart);
-
-        // TODO: Disable logcat check for now.
-        // Logcat of WM or AM tag could be lost (eg. chatty if earlier events generated
-        // too many lines), and make the test look flaky. We need to either use event
-        // log or swith to other mechanisms. Only verify shell output for now, it should
-        // still catch most failures.
-
-        // Verify adb logcat log
-        //verifyLogcat(actualActivity, shouldStart, logSeparator);
-    }
-
-    private static final Pattern sNotStartedWarningPattern = Pattern.compile(
-            "Warning: Activity not started(.*)");
-    private static final Pattern sStatusPattern = Pattern.compile(
-            "Status: (.*)");
-    private static final Pattern sActivityPattern = Pattern.compile(
-            "Activity: (.*)");
-    private static final String sStatusOk = "ok";
-
-    private void verifyShellOutput(
-            final String result, final String activity, boolean shouldStart) {
-        boolean warningFound = false;
-        String status = null;
-        String reportedActivity = null;
-        String componentActivityName = getActivityComponentName(activity);
-
-        final String[] lines = result.split("\\n");
-        // Going from the end of logs to beginning in case if some other activity is started first.
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            Matcher matcher = sNotStartedWarningPattern.matcher(line);
-            if (matcher.matches()) {
-                warningFound = true;
-                continue;
-            }
-            matcher = sStatusPattern.matcher(line);
-            if (matcher.matches()) {
-                status = matcher.group(1);
-                continue;
-            }
-            matcher = sActivityPattern.matcher(line);
-            if (matcher.matches()) {
-                reportedActivity = matcher.group(1);
-                continue;
-            }
-        }
-
-        assertTrue("Status " + status + " is not ok", sStatusOk.equals(status));
-        assertTrue("Reported activity is " + reportedActivity + " not " + componentActivityName,
-                componentActivityName.equals(reportedActivity));
-
-        if (shouldStart && warningFound) {
-            fail("Should start new activity but brought something to front.");
-        } else if (!shouldStart && !warningFound){
-            fail("Should bring existing activity to front but started new activity.");
-        }
-    }
-
-    private static final Pattern sDisplayTimePattern =
-            Pattern.compile("(.+): Displayed (.*): (\\+{0,1})([0-9]+)ms(.*)");
-
-    void verifyLogcat(String actualActivityName, boolean shouldStart, String logSeparator)
-            throws DeviceNotAvailableException {
-        int displayCount = 0;
-        String activityName = null;
-
-        for (String line : getDeviceLogsForComponent("ActivityManager", logSeparator)) {
-            line = line.trim();
-
-            Matcher matcher = sDisplayTimePattern.matcher(line);
-            if (matcher.matches()) {
-                activityName = matcher.group(2);
-                // Ignore activitiy displays from other packages, we don't
-                // want some random activity starts to ruin our test.
-                if (!activityName.startsWith("android.server.cts")) {
-                    continue;
-                }
-                if (!shouldStart) {
-                    fail("Shouldn't display anything but displayed " + activityName);
-                }
-                displayCount++;
-            }
-        }
-        final String expectedActivityName = getActivityComponentName(actualActivityName);
-        if (shouldStart) {
-            if (displayCount != 1) {
-                fail("Should display exactly one activity but displayed " + displayCount);
-            } else if (!expectedActivityName.equals(activityName)) {
-                fail("Should display " + expectedActivityName +
-                        " but displayed " + activityName);
-            }
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
deleted file mode 100644
index ffa03ba..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ /dev/null
@@ -1,620 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-
-import android.platform.test.annotations.Presubmit;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.server.cts.ActivityAndWindowManagersState.dpToPx;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAppConfigurationTests
- */
-public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
-    private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String PORTRAIT_ACTIVITY_NAME = "PortraitOrientationActivity";
-    private static final String LANDSCAPE_ACTIVITY_NAME = "LandscapeOrientationActivity";
-    private static final String NIGHT_MODE_ACTIVITY = "NightModeActivity";
-    private static final String DIALOG_WHEN_LARGE_ACTIVITY = "DialogWhenLargeActivity";
-
-    private static final String TRANSLUCENT_ACTIVITY =
-            "android.server.translucentapp.TranslucentLandscapeActivity";
-    private static final String TRANSLUCENT_SDK_26_PACKAGE = "android.server.translucentapp26";
-    private static final String TRANSLUCENT_CURRENT_PACKAGE = "android.server.translucentapp";
-
-    private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
-
-    private static final int SMALL_WIDTH_DP = 426;
-    private static final int SMALL_HEIGHT_DP = 320;
-
-    /**
-     * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
-     * has an updated size when the Activity is resized from fullscreen to docked state.
-     *
-     * The Activity handles configuration changes, so it will not be restarted between resizes.
-     * On Configuration changes, the Activity logs the Display size and Configuration width
-     * and heights. The values reported in fullscreen should be larger than those reported in
-     * docked state.
-     */
-    public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        String logSeparator = clearLogcat();
-        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        logSeparator = clearLogcat();
-        moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        assertSizesAreSane(fullscreenSizes, dockedSizes);
-    }
-
-    /**
-     * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
-     * from docked state to fullscreen (reverse).
-     */
-    // TODO: Flaky, add to presubmit when b/63404575 is fixed.
-    public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        String logSeparator = clearLogcat();
-        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        logSeparator = clearLogcat();
-        moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        assertSizesAreSane(fullscreenSizes, dockedSizes);
-    }
-
-    /**
-     * Tests whether the Display sizes change when rotating the device.
-     */
-    public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
-        if (!supportsRotation()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no rotation support");
-            return;
-        }
-        setDeviceRotation(0);
-        final String logSeparator = clearLogcat();
-        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        rotateAndCheckSizes(initialSizes);
-    }
-
-    /**
-     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
-     * is in the docked stack.
-     */
-    // TODO: Flaky, add to presubmit when b/63404575 is fixed.
-    public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        setDeviceRotation(0);
-        final String logSeparator = clearLogcat();
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        // Launch our own activity to side in case Recents (or other activity to side) doesn't
-        // support rotation.
-        getLaunchActivityBuilder().setToSide(true).setTargetActivityName(TEST_ACTIVITY_NAME)
-                .execute();
-        // Launch target activity in docked stack.
-        getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
-        final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        rotateAndCheckSizes(initialSizes);
-    }
-
-    /**
-     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
-     * is launched to side from docked stack.
-     */
-    public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        setDeviceRotation(0);
-
-        final String logSeparator = clearLogcat();
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-
-        getLaunchActivityBuilder().setToSide(true).setTargetActivityName(RESIZEABLE_ACTIVITY_NAME)
-                .execute();
-        final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-
-        rotateAndCheckSizes(initialSizes);
-    }
-
-    private void rotateAndCheckSizes(ReportedSizes prevSizes) throws Exception {
-        for (int rotation = 3; rotation >= 0; --rotation) {
-            final String logSeparator = clearLogcat();
-            final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
-                    RESIZEABLE_ACTIVITY_NAME).mStackId;
-            final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
-            setDeviceRotation(rotation);
-            final int newDeviceRotation = getDeviceRotation(displayId);
-            if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
-                CLog.logAndDisplay(LogLevel.WARN, "Got an invalid device rotation value. "
-                        + "Continuing the test despite of that, but it is likely to fail.");
-            } else if (rotation != newDeviceRotation) {
-                CLog.logAndDisplay(LogLevel.INFO, "This device doesn't support locked user "
-                        + "rotation mode. Not continuing the rotation checks.");
-                return;
-            }
-
-            final ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
-                    logSeparator);
-            assertSizesRotate(prevSizes, rotatedSizes);
-            prevSizes = rotatedSizes;
-        }
-    }
-
-    /**
-     * Tests when activity moved from fullscreen stack to docked and back. Activity will be
-     * relaunched twice and it should have same config as initial one.
-     */
-    public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
-        moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
-    }
-
-    /**
-     * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
-     */
-    @Presubmit
-    public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
-        moveActivityFullSplitFull(RESIZEABLE_ACTIVITY_NAME);
-    }
-
-    /**
-     * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
-     * Last operation is done in a way which simulates split-screen divider movement maximizing
-     * docked stack size and then moving task to fullscreen stack - the same way it is done when
-     * user long-presses overview/recents button to exit split-screen.
-     * Asserts that initial and final reported sizes in fullscreen stack are the same.
-     */
-    private void moveActivityFullSplitFull(String activityName) throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        // Launch to fullscreen stack and record size.
-        String logSeparator = clearLogcat();
-        launchActivityInStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
-                logSeparator);
-        final Rectangle displayRect = getDisplayRect(activityName);
-
-        // Move to docked stack.
-        logSeparator = clearLogcat();
-        moveActivityToStack(activityName, DOCKED_STACK_ID);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
-        assertSizesAreSane(initialFullscreenSizes, dockedSizes);
-        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
-        // will come up.
-        launchActivityInStack(activityName, DOCKED_STACK_ID);
-        mAmWmState.computeState(mDevice, new String[] { activityName },
-                false /* compareTaskAndStackBounds */);
-
-        // Resize docked stack to fullscreen size. This will trigger activity relaunch with
-        // non-empty override configuration corresponding to fullscreen size.
-        logSeparator = clearLogcat();
-        runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
-                + displayRect.width + " " + displayRect.height);
-        // Move activity back to fullscreen stack.
-        moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
-                logSeparator);
-
-        // After activity configuration was changed twice it must report same size as original one.
-        assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
-    }
-
-    /**
-     * Tests when activity moved from docked stack to fullscreen and back. Activity will be
-     * relaunched twice and it should have same config as initial one.
-     */
-    public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
-        moveActivitySplitFullSplit(TEST_ACTIVITY_NAME);
-    }
-
-    /**
-     * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
-     */
-    public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
-        moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY_NAME);
-    }
-
-    /**
-     * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
-     * screen.
-     */
-    @Presubmit
-    public void testDialogWhenLargeSplitSmall() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        launchActivityInStack(DIALOG_WHEN_LARGE_ACTIVITY, DOCKED_STACK_ID);
-        final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
-                .getStackById(DOCKED_STACK_ID);
-        final WindowManagerState.Display display =
-                mAmWmState.getWmState().getDisplay(stack.mDisplayId);
-        final int density = display.getDpi();
-        final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
-        final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
-
-        runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
-                + smallWidthPx + " " + smallHeightPx);
-        mAmWmState.waitForValidState(mDevice, DIALOG_WHEN_LARGE_ACTIVITY, DOCKED_STACK_ID);
-    }
-
-    /**
-     * Test that device handles consequent requested orientations and displays the activities.
-     */
-    @Presubmit
-    public void testFullscreenAppOrientationRequests() throws Exception {
-        String logSeparator = clearLogcat();
-        launchActivity(PORTRAIT_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
-        ReportedSizes reportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
-        assertEquals("portrait activity should be in portrait",
-                1 /* portrait */, reportedSizes.orientation);
-        logSeparator = clearLogcat();
-
-        launchActivity(LANDSCAPE_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
-        reportedSizes =
-                getLastReportedSizesForActivity(LANDSCAPE_ACTIVITY_NAME, logSeparator);
-        assertEquals("landscape activity should be in landscape",
-                2 /* landscape */, reportedSizes.orientation);
-        logSeparator = clearLogcat();
-
-        launchActivity(PORTRAIT_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
-        reportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
-        assertEquals("portrait activity should be in portrait",
-                1 /* portrait */, reportedSizes.orientation);
-        logSeparator = clearLogcat();
-    }
-
-    public void testNonfullscreenAppOrientationRequests() throws Exception {
-        String logSeparator = clearLogcat();
-        launchActivity(PORTRAIT_ACTIVITY_NAME);
-        final ReportedSizes initialReportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
-        assertEquals("portrait activity should be in portrait",
-                1 /* portrait */, initialReportedSizes.orientation);
-        logSeparator = clearLogcat();
-
-        launchActivityInComponent(TRANSLUCENT_SDK_26_PACKAGE, TRANSLUCENT_ACTIVITY);
-
-        assertEquals("Legacy non-fullscreen activity requested landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
-        // TODO(b/36897968): uncomment once we can suppress unsupported configurations
-        // final ReportedSizes updatedReportedSizes =
-        //      getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
-        // assertEquals("portrait activity should not have moved from portrait",
-        //         1 /* portrait */, updatedReportedSizes.orientation);
-    }
-
-    // TODO(b/70870253): This test seems malfunction.
-    @Ignore("b/70870253")
-    @Test
-    public void testNonFullscreenActivityProhibited() throws Exception {
-        setComponentName(TRANSLUCENT_CURRENT_PACKAGE);
-
-        // We do not wait for the activity as it should not launch based on the restrictions around
-        // specifying orientation. We instead start an activity known to launch immediately after
-        // so that we can ensure processing the first activity occurred.
-        launchActivityNoWait(TRANSLUCENT_ACTIVITY);
-        setDefaultComponentName();
-        launchActivity(PORTRAIT_ACTIVITY_NAME);
-
-        assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
-                mAmWmState.getAmState().containsActivity(
-                        ActivityManagerTestBase.getActivityComponentName(
-                                TRANSLUCENT_ACTIVITY, TRANSLUCENT_ACTIVITY)));
-    }
-
-    public void testNonFullscreenActivityPermitted() throws Exception {
-        setComponentName(TRANSLUCENT_CURRENT_PACKAGE);
-        setDeviceRotation(0);
-
-        launchActivity(TRANSLUCENT_ACTIVITY);
-        mAmWmState.assertResumedActivity(
-                "target SDK non-fullscreen activity should be allowed to launch",
-                TRANSLUCENT_ACTIVITY);
-        assertEquals("non-fullscreen activity requested landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-    }
-
-    /**
-     * Test that device handles moving between two tasks with different orientations.
-     */
-    public void testTaskCloseRestoreOrientation() throws Exception {
-        // Start landscape activity.
-        launchActivity(LANDSCAPE_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
-        assertEquals("Fullscreen app requested landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
-        // Start another activity in a different task.
-        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-
-        // Request portrait
-        executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
-        mAmWmState.waitForRotation(mDevice, 1);
-
-        // Finish activity
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
-        // Verify that activity brought to front is in originally requested orientation.
-        mAmWmState.computeState(mDevice, new String[]{LANDSCAPE_ACTIVITY_NAME});
-        assertEquals("Should return to app in landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-    }
-
-    /**
-     * Test that device handles moving between two tasks with different orientations.
-     */
-    public void testTaskMoveToBackOrientation() throws Exception {
-        // Start landscape activity.
-        launchActivity(LANDSCAPE_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
-        assertEquals("Fullscreen app requested landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
-        // Start another activity in a different task.
-        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-
-        // Request portrait
-        executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
-        mAmWmState.waitForRotation(mDevice, 1);
-
-        // Finish activity
-        executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
-
-        // Verify that activity brought to front is in originally requested orientation.
-        mAmWmState.waitForValidState(mDevice, LANDSCAPE_ACTIVITY_NAME);
-        assertEquals("Should return to app in landscape orientation",
-                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-    }
-
-    /**
-     * Test that device doesn't change device orientation by app request while in multi-window.
-     */
-    public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-          CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-          return;
-        }
-        requestOrientationInSplitScreen(1 /* portrait */, LANDSCAPE_ACTIVITY_NAME);
-    }
-
-    /**
-     * Test that device doesn't change device orientation by app request while in multi-window.
-     */
-    public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-          CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-          return;
-        }
-        requestOrientationInSplitScreen(0 /* landscape */, PORTRAIT_ACTIVITY_NAME);
-    }
-
-    /**
-     * Rotate the device and launch specified activity in split-screen, checking if orientation
-     * didn't change.
-     */
-    private void requestOrientationInSplitScreen(int orientation, String activity)
-            throws Exception {
-        // Set initial orientation.
-        setDeviceRotation(orientation);
-
-        // Launch activities that request orientations and check that device doesn't rotate.
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-
-        getLaunchActivityBuilder().setToSide(true).setMultipleTask(true)
-                .setTargetActivityName(activity).execute();
-        mAmWmState.computeState(mDevice, new String[] {activity});
-        mAmWmState.assertVisibility(activity, true /* visible */);
-        assertEquals("Split-screen apps shouldn't influence device orientation",
-                orientation, mAmWmState.getWmState().getRotation());
-
-        getLaunchActivityBuilder().setMultipleTask(true).setTargetActivityName(activity).execute();
-        mAmWmState.computeState(mDevice, new String[] {activity});
-        mAmWmState.assertVisibility(activity, true /* visible */);
-        assertEquals("Split-screen apps shouldn't influence device orientation",
-                orientation, mAmWmState.getWmState().getRotation());
-    }
-
-    /**
-     * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
-     * Asserts that initial and final reported sizes in docked stack are the same.
-     */
-    private void moveActivitySplitFullSplit(String activityName) throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        // Launch to docked stack and record size.
-        String logSeparator = clearLogcat();
-        launchActivityInStack(activityName, DOCKED_STACK_ID);
-        final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
-        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
-        // will come up.
-        launchActivityInStack(activityName, DOCKED_STACK_ID);
-        mAmWmState.computeState(mDevice, new String[] { activityName },
-                false /* compareTaskAndStackBounds */);
-
-        // Move to fullscreen stack.
-        logSeparator = clearLogcat();
-        moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
-        assertSizesAreSane(fullscreenSizes, initialDockedSizes);
-
-        // Move activity back to docked stack.
-        logSeparator = clearLogcat();
-        moveActivityToStack(activityName, DOCKED_STACK_ID);
-        final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
-
-        // After activity configuration was changed twice it must report same size as original one.
-        assertSizesAreSame(initialDockedSizes, finalDockedSizes);
-    }
-
-    /**
-     * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
-     * have flipped.
-     */
-    private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes rotationB)
-            throws Exception {
-        assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
-        assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
-        assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
-        assertEquals(rotationB.displayHeight, rotationB.metricsHeight);
-
-        final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
-        final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
-        assertFalse(beforePortrait == afterPortrait);
-
-        final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
-        final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
-        assertEquals(beforePortrait, beforeConfigPortrait);
-        assertEquals(afterPortrait, afterConfigPortrait);
-
-        assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
-    }
-
-    /**
-     * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
-     * that are smaller than the dockedSizes.
-     */
-    private static void assertSizesAreSane(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
-            throws Exception {
-        final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
-        if (portrait) {
-            assertTrue(dockedSizes.displayHeight < fullscreenSizes.displayHeight);
-            assertTrue(dockedSizes.heightDp < fullscreenSizes.heightDp);
-            assertTrue(dockedSizes.metricsHeight < fullscreenSizes.metricsHeight);
-        } else {
-            assertTrue(dockedSizes.displayWidth < fullscreenSizes.displayWidth);
-            assertTrue(dockedSizes.widthDp < fullscreenSizes.widthDp);
-            assertTrue(dockedSizes.metricsWidth < fullscreenSizes.metricsWidth);
-        }
-    }
-
-    /**
-     * Throws an AssertionError if sizes are different.
-     */
-    private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
-            throws Exception {
-        assertEquals(firstSize.widthDp, secondSize.widthDp);
-        assertEquals(firstSize.heightDp, secondSize.heightDp);
-        assertEquals(firstSize.displayWidth, secondSize.displayWidth);
-        assertEquals(firstSize.displayHeight, secondSize.displayHeight);
-        assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
-        assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
-        assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
-    }
-
-    private ReportedSizes getActivityDisplaySize(String activityName, String logSeparator)
-            throws Exception {
-        mAmWmState.computeState(mDevice, new String[] { activityName },
-                false /* compareTaskAndStackBounds */);
-        final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
-        assertNotNull(details);
-        return details;
-    }
-
-    private Rectangle getDisplayRect(String activityName)
-            throws Exception {
-        final String windowName = getWindowName(activityName);
-
-        mAmWmState.computeState(mDevice, new String[] {activityName});
-        mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
-
-        final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
-        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
-
-        assertEquals("Should have exactly one window state for the activity.", 1,
-                tempWindowList.size());
-
-        WindowManagerState.WindowState windowState = tempWindowList.get(0);
-        assertNotNull("Should have a valid window", windowState);
-
-        WindowManagerState.Display display = mAmWmState.getWmState()
-                .getDisplay(windowState.getDisplayId());
-        assertNotNull("Should be on a display", display);
-
-        return display.getDisplayRect();
-    }
-
-    /**
-     * Test launching an activity which requests specific UI mode during creation.
-     */
-    public void testLaunchWithUiModeChange() throws Exception {
-        // Launch activity that changes UI mode and handles this configuration change.
-        launchActivity(NIGHT_MODE_ACTIVITY);
-        mAmWmState.waitForActivityState(mDevice, NIGHT_MODE_ACTIVITY, STATE_RESUMED);
-
-        // Check if activity is launched successfully.
-        mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Launched activity should be focused",
-                NIGHT_MODE_ACTIVITY);
-        mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java
deleted file mode 100644
index abc3082..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAssistantStackTests
- */
-public class ActivityManagerAssistantStackTests extends ActivityManagerTestBase {
-
-    private static final String VOICE_INTERACTION_SERVICE = "AssistantVoiceInteractionService";
-
-    private static final String TEST_ACTIVITY = "TestActivity";
-    private static final String ANIMATION_TEST_ACTIVITY = "AnimationTestActivity";
-    private static final String DOCKED_ACTIVITY = "DockedActivity";
-    private static final String ASSISTANT_ACTIVITY = "AssistantActivity";
-    private static final String TRANSLUCENT_ASSISTANT_ACTIVITY = "TranslucentAssistantActivity";
-    private static final String LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION =
-            "LaunchAssistantActivityFromSession";
-    private static final String LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK =
-            "LaunchAssistantActivityIntoAssistantStack";
-    private static final String PIP_ACTIVITY = "PipActivity";
-
-    private static final String EXTRA_ENTER_PIP = "enter_pip";
-    private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
-    private static final String EXTRA_FINISH_SELF = "finish_self";
-    public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
-
-    private static final String TEST_ACTIVITY_ACTION_FINISH_SELF =
-            "android.server.cts.TestActivity.finish_self";
-
-    public void testLaunchingAssistantActivityIntoAssistantStack() throws Exception {
-        // Enable the assistant and launch an assistant activity
-        enableAssistant();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
-        mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-
-        // Ensure that the activity launched in the fullscreen assistant stack
-        assertAssistantStackExists();
-        assertTrue("Expected assistant stack to be fullscreen",
-                mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).isFullscreen());
-
-        disableAssistant();
-    }
-
-    public void testAssistantStackZOrder() throws Exception {
-        if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
-        // Launch a pinned stack task
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-
-        // Dock a task
-        launchActivity(TEST_ACTIVITY);
-        launchActivityInDockStack(DOCKED_ACTIVITY);
-        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
-        // Enable the assistant and launch an assistant activity, ensure it is on top
-        enableAssistant();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
-        mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-        assertAssistantStackExists();
-
-        mAmWmState.assertFrontStack("Pinned stack should be on top.", PINNED_STACK_ID);
-        mAmWmState.assertFocusedStack("Assistant stack should be focused.", ASSISTANT_STACK_ID);
-
-        disableAssistant();
-    }
-
-    public void testAssistantStackLaunchNewTask() throws Exception {
-        enableAssistant();
-        assertAssistantStackCanLaunchAndReturnFromNewTask();
-        disableAssistant();
-    }
-
-    public void testAssistantStackLaunchNewTaskWithDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) return;
-        // Dock a task
-        launchActivity(TEST_ACTIVITY);
-        launchActivityInDockStack(DOCKED_ACTIVITY);
-        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
-        enableAssistant();
-        assertAssistantStackCanLaunchAndReturnFromNewTask();
-        disableAssistant();
-    }
-
-    private void assertAssistantStackCanLaunchAndReturnFromNewTask() throws Exception {
-        // Enable the assistant and launch an assistant activity which will launch a new task
-        enableAssistant();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_LAUNCH_NEW_TASK, TEST_ACTIVITY);
-        disableAssistant();
-
-        // Ensure that the fullscreen stack is on top and the test activity is now visible
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
-        mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-
-        // 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);
-        mAmWmState.waitForFocusedStack(mDevice, ASSISTANT_STACK_ID);
-        mAmWmState.assertFrontStack("Assistant stack should be on top.", ASSISTANT_STACK_ID);
-        mAmWmState.assertFocusedStack("Assistant stack should be focused.", ASSISTANT_STACK_ID);
-    }
-
-    public void testAssistantStackFinishToPreviousApp() throws Exception {
-        // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
-        // the fullscreen activity is still visible and on top after the assistant activity finishes
-        launchActivity(TEST_ACTIVITY);
-        enableAssistant();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_FINISH_SELF, "true");
-        disableAssistant();
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY, STATE_RESUMED);
-        mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
-        mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    public void testDisallowEnterPiPFromAssistantStack() throws Exception {
-        enableAssistant();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_ENTER_PIP, "true");
-        disableAssistant();
-        mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-        mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
-    }
-
-    public void testTranslucentAssistantActivityStackVisibility() throws Exception {
-        enableAssistant();
-        // Go home, launch the assistant and check to see that home is visible
-        removeStacks(FULLSCREEN_WORKSPACE_STACK_ID);
-        launchHomeActivity();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_IS_TRANSLUCENT, String.valueOf(true));
-        mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-        assertAssistantStackExists();
-        if (!noHomeScreen()) {
-            mAmWmState.assertHomeActivityVisible(true);
-        }
-
-        // Launch a fullscreen app and then launch the assistant and check to see that it is
-        // also visible
-        removeStacks(ASSISTANT_STACK_ID);
-        launchActivity(TEST_ACTIVITY);
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_IS_TRANSLUCENT, String.valueOf(true));
-        mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-        assertAssistantStackExists();
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Go home, launch assistant, launch app into fullscreen with activity present, and go back.
-        // Ensure home is visible.
-        removeStacks(ASSISTANT_STACK_ID);
-        launchHomeActivity();
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_IS_TRANSLUCENT, String.valueOf(true), EXTRA_LAUNCH_NEW_TASK,
-                TEST_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertHomeActivityVisible(false);
-        pressBackButton();
-        mAmWmState.waitForFocusedStack(mDevice, ASSISTANT_STACK_ID);
-        assertAssistantStackExists();
-        if (!noHomeScreen()) {
-            mAmWmState.waitForHomeActivityVisible(mDevice);
-            mAmWmState.assertHomeActivityVisible(true);
-        }
-
-        // Launch a fullscreen and docked app and then launch the assistant and check to see that it
-        // is also visible
-        if (supportsSplitScreenMultiWindow()) {
-            removeStacks(ASSISTANT_STACK_ID);
-            launchActivityInDockStack(DOCKED_ACTIVITY);
-            launchActivity(TEST_ACTIVITY);
-            mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_IS_TRANSLUCENT, String.valueOf(true));
-            mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-            assertAssistantStackExists();
-            mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
-            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-        }
-        disableAssistant();
-    }
-
-    public void testLaunchIntoSameTask() throws Exception {
-        enableAssistant();
-
-        // Launch the assistant
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
-        assertAssistantStackExists();
-        mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
-        mAmWmState.assertFocusedStack("Expected assistant stack focused", ASSISTANT_STACK_ID);
-        assertEquals(1, mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).getTasks().size());
-        final int taskId = mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY)
-                .mTaskId;
-
-        // Launch a new fullscreen activity
-        // Using Animation Test Activity because it is opaque on all devices.
-        launchActivity(ANIMATION_TEST_ACTIVITY);
-        mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, false);
-
-        // Launch the assistant again and ensure that it goes into the same task
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
-        assertAssistantStackExists();
-        mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
-        mAmWmState.assertFocusedStack("Expected assistant stack focused", ASSISTANT_STACK_ID);
-        assertEquals(1, mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).getTasks().size());
-        assertEquals(taskId,
-                mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY).mTaskId);
-
-        disableAssistant();
-    }
-
-    public void testPinnedStackWithAssistant() throws Exception {
-        if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
-
-        enableAssistant();
-
-        // Launch a fullscreen activity and a PIP activity, then launch the assistant, and ensure
-        // that the test activity is still visible
-        launchActivity(TEST_ACTIVITY);
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                EXTRA_IS_TRANSLUCENT, String.valueOf(true));
-        mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-        assertAssistantStackExists();
-        mAmWmState.assertVisibility(TRANSLUCENT_ASSISTANT_ACTIVITY, true);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        disableAssistant();
-    }
-
-    /**
-     * Asserts that the assistant stack exists.
-     */
-    private void assertAssistantStackExists() throws Exception {
-        mAmWmState.assertContainsStack("Must contain assistant stack.", ASSISTANT_STACK_ID);
-    }
-
-    /**
-     * Asserts that the assistant stack does not exist.
-     */
-    private void assertAssistantStackDoesNotExist() throws Exception {
-        mAmWmState.assertDoesNotContainStack("Must not contain assistant stack.",
-                ASSISTANT_STACK_ID);
-    }
-
-    /**
-     * Sets the system voice interaction service.
-     */
-    private void enableAssistant() throws Exception {
-        executeShellCommand("settings put secure voice_interaction_service " +
-                getActivityComponentName(VOICE_INTERACTION_SERVICE));
-    }
-
-    /**
-     * Resets the system voice interaction service.
-     */
-    private void disableAssistant() throws Exception {
-        executeShellCommand("settings delete secure voice_interaction_service " +
-                getActivityComponentName(VOICE_INTERACTION_SERVICE));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
deleted file mode 100644
index 7e2e53e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.StateLogger.logE;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
- */
-public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
-
-    private static final String FONT_SCALE_ACTIVITY_NAME = "FontScaleActivity";
-    private static final String FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME =
-            "FontScaleNoRelaunchActivity";
-
-    private static final float EXPECTED_FONT_SIZE_SP = 10.0f;
-
-    public void testRotation90Relaunch() throws Exception{
-        // Should relaunch on every rotation and receive no onConfigurationChanged()
-        testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
-    }
-
-    public void testRotation90NoRelaunch() throws Exception {
-        // Should receive onConfigurationChanged() on every rotation and no relaunch
-        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
-    }
-
-    public void testRotation180Relaunch() throws Exception {
-        // Should receive nothing
-        testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
-    }
-
-    public void testRotation180NoRelaunch() throws Exception {
-        // Should receive nothing
-        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
-    }
-
-    @Presubmit
-    public void testChangeFontScaleRelaunch() throws Exception {
-        // Should relaunch and receive no onConfigurationChanged()
-        testChangeFontScale(FONT_SCALE_ACTIVITY_NAME, true /* relaunch */);
-    }
-
-    @Presubmit
-    public void testChangeFontScaleNoRelaunch() throws Exception {
-        // Should receive onConfigurationChanged() and no relaunch
-        testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME, false /* relaunch */);
-    }
-
-    private void testRotation(
-            String activityName, int rotationStep, int numRelaunch, int numConfigChange)
-                    throws Exception {
-        launchActivity(activityName);
-
-        final String[] waitForActivitiesVisible = new String[] {activityName};
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
-        final int initialRotation = 4 - rotationStep;
-        setDeviceRotation(initialRotation);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
-                activityName).mStackId;
-        final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
-        final int newDeviceRotation = getDeviceRotation(displayId);
-        if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
-            CLog.logAndDisplay(LogLevel.WARN, "Got an invalid device rotation value. "
-                    + "Continuing the test despite of that, but it is likely to fail.");
-        } else if (newDeviceRotation != initialRotation) {
-            CLog.logAndDisplay(LogLevel.INFO, "This device doesn't support user rotation "
-                    + "mode. Not continuing the rotation checks.");
-            return;
-        }
-
-        for (int rotation = 0; rotation < 4; rotation += rotationStep) {
-            final String logSeparator = clearLogcat();
-            setDeviceRotation(rotation);
-            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-            assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange, logSeparator);
-        }
-    }
-
-    private void testChangeFontScale(
-            String activityName, boolean relaunch) throws Exception {
-        setFontScale(1.0f);
-        String logSeparator = clearLogcat();
-        launchActivity(activityName);
-        final String[] waitForActivitiesVisible = new String[] {activityName};
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
-        final int densityDpi = getActivityDensityDpi(activityName, logSeparator);
-
-        for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
-            logSeparator = clearLogcat();
-            setFontScale(fontScale);
-            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-            assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
-                    logSeparator);
-
-            // Verify that the display metrics are updated, and therefore the text size is also
-            // updated accordingly.
-            assertExpectedFontPixelSize(activityName,
-                    scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
-                    logSeparator);
-        }
-    }
-
-    /**
-     * Test updating application info when app is running. An activity with matching package name
-     * must be recreated and its asset sequence number must be incremented.
-     */
-    public void testUpdateApplicationInfo() throws Exception {
-        final String firstLogSeparator = clearLogcat();
-
-        // Launch an activity that prints applied config.
-        launchActivity(TEST_ACTIVITY_NAME);
-        final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, firstLogSeparator);
-
-        final String logSeparator = clearLogcat();
-        // Update package info.
-        executeShellCommand("am update-appinfo all " + componentName);
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            // Wait for activity to be resumed and asset seq number to be updated.
-            try {
-                return readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator) == assetSeq + 1
-                        && amState.hasActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
-            } catch (Exception e) {
-                logE("Error waiting for valid state: " + e.getMessage());
-                return false;
-            }
-        }, "Waiting asset sequence number to be updated and for activity to be resumed.");
-
-        // Check if activity is relaunched and asset seq is updated.
-        assertRelaunchOrConfigChanged(TEST_ACTIVITY_NAME, 1 /* numRelaunch */,
-                0 /* numConfigChange */, logSeparator);
-        final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator);
-        assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
-    }
-
-    private static final Pattern sConfigurationPattern = Pattern.compile(
-            "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
-
-    /** Read asset sequence number in last applied configuration from logs. */
-    private int readAssetSeqNumber(String activityName, String logSeparator) throws Exception {
-        final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sConfigurationPattern.matcher(line);
-            if (matcher.matches()) {
-                final String assetSeqNumber = matcher.group(3);
-                try {
-                    return Integer.valueOf(assetSeqNumber);
-                } catch (NumberFormatException e) {
-                    // Ignore, asset seq number is not printed when not set.
-                }
-            }
-        }
-        return 0;
-    }
-
-    // Calculate the scaled pixel size just like the device is supposed to.
-    private static int scaledPixelsToPixels(float sp, float fontScale, int densityDpi) {
-        final int DEFAULT_DENSITY = 160;
-        float f = densityDpi * (1.0f / DEFAULT_DENSITY) * fontScale * sp;
-        return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
-    }
-
-    private static Pattern sDeviceDensityPattern = Pattern.compile("^(.+): fontActivityDpi=(.+)$");
-
-    private int getActivityDensityDpi(String activityName, String logSeparator) throws Exception {
-        final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sDeviceDensityPattern.matcher(line);
-            if (matcher.matches()) {
-                return Integer.parseInt(matcher.group(2));
-            }
-        }
-        fail("No fontActivityDpi reported from activity " + activityName);
-        return -1;
-    }
-
-    private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
-
-    /** Read the font size in the last log line. */
-    private void assertExpectedFontPixelSize(String activityName, int fontPixelSize,
-            String logSeparator) throws Exception {
-        final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sFontSizePattern.matcher(line);
-            if (matcher.matches()) {
-                assertEquals("Expected font pixel size does not match", fontPixelSize,
-                        Integer.parseInt(matcher.group(2)));
-                return;
-            }
-        }
-        fail("No fontPixelSize reported from activity " + activityName);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java
deleted file mode 100644
index 1e8c5a1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-import static android.server.cts.StateLogger.logE;
-
-/**
- * Display tests that require a locked keyguard.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayLockedKeyguardTests
- */
-public class ActivityManagerDisplayLockedKeyguardTests extends ActivityManagerDisplayTestBase {
-
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        setLockCredential();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            tearDownLockCredentials();
-        } catch (DeviceNotAvailableException e) {
-            logE(e.getMessage());
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Test that virtual display content is hidden when device is locked.
-     */
-    public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
-        if (!supportsMultiDisplay() || !isHandheld()) { return; }
-
-        // Create new usual virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-
-        // Lock the device.
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_STOPPED);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false /* visible */);
-
-        // Unlock and check if visibility is back.
-        unlockDeviceWithCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java
deleted file mode 100644
index cc0a257..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Base class for ActivityManager display tests.
- *
- * @see ActivityManagerDisplayTests
- * @see ActivityManagerDisplayLockedKeyguardTests
- */
-public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
-
-    static final int CUSTOM_DENSITY_DPI = 222;
-
-    private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
-    private static final String DUMPSYS_DISPLAY = "dumpsys display";
-    private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
-    private static final int INVALID_DENSITY_DPI = -1;
-
-    private boolean mVirtualDisplayCreated;
-    private boolean mDisplaySimulated;
-
-    /** Temp storage used for parsing. */
-    final LinkedList<String> mDumpLines = new LinkedList<>();
-    final LinkedList<String> mAltDumpLines = new LinkedList<>();
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            destroyVirtualDisplays();
-            destroySimulatedDisplays();
-        } catch (DeviceNotAvailableException e) {
-            logE(e.getMessage());
-        }
-        super.tearDown();
-    }
-
-    /** Contains the configurations applied to attached displays. */
-    static final class DisplayState {
-        int mDisplayId;
-        String mOverrideConfig;
-        String mDisplayViewportId;
-
-        private DisplayState(int displayId, String overrideConfig, String uniqueId) {
-            mDisplayId = displayId;
-            mOverrideConfig = overrideConfig;
-            mDisplayViewportId = uniqueId;
-        }
-
-        private int getWidth() {
-            final String[] configParts = mOverrideConfig.split(" ");
-            for (String part : configParts) {
-                if (part.endsWith("dp") && part.startsWith("w")) {
-                    final String widthString = part.substring(1, part.length() - 3);
-                    return Integer.parseInt(widthString);
-                }
-            }
-
-            return -1;
-        }
-
-        private int getHeight() {
-            final String[] configParts = mOverrideConfig.split(" ");
-            for (String part : configParts) {
-                if (part.endsWith("dp") && part.startsWith("h")) {
-                    final String heightString = part.substring(1, part.length() - 3);
-                    return Integer.parseInt(heightString);
-                }
-            }
-
-            return -1;
-        }
-
-        int getDpi() {
-            final String[] configParts = mOverrideConfig.split(" ");
-            for (String part : configParts) {
-                if (part.endsWith("dpi")) {
-                    final String densityDpiString = part.substring(0, part.length() - 3);
-                    return Integer.parseInt(densityDpiString);
-                }
-            }
-
-            return -1;
-        }
-
-        String getDisplayViewPortId() {
-            return mDisplayViewportId;
-        }
-    }
-
-    /** Contains the configurations applied to attached displays. */
-    static final class ReportedDisplays {
-        private static final Pattern sGlobalConfigurationPattern =
-                Pattern.compile("mGlobalConfiguration: (\\{.*\\})");
-        private static final Pattern sDisplayOverrideConfigurationsPattern =
-                Pattern.compile("Display override configurations:");
-        private static final Pattern sDisplayConfigPattern =
-                Pattern.compile("(\\d+): (\\{.*\\})");
-        private static final Pattern sVirtualTouchViewportPattern =
-                Pattern.compile("mVirtualTouchViewports.*displayId=(\\d+), uniqueId='([^']+)', .*");
-
-        String mGlobalConfig;
-        private Map<Integer, DisplayState> mDisplayStates = new HashMap<>();
-
-        static ReportedDisplays create(LinkedList<String> activityProcessDump,
-                LinkedList<String> displayDump) {
-            final ReportedDisplays result = new ReportedDisplays();
-            HashMap<String, String> virtualUniqueIdMap = new HashMap<String, String>();
-
-            while (!displayDump.isEmpty()) {
-                final String line = displayDump.pop().trim();
-
-                Matcher matcher = sVirtualTouchViewportPattern.matcher(line);
-                if (matcher.matches()) {
-                    virtualUniqueIdMap.put(matcher.group(1), matcher.group(2));
-                }
-            }
-
-            while (!activityProcessDump.isEmpty()) {
-                final String line = activityProcessDump.pop().trim();
-
-
-                Matcher actMatcher = sDisplayOverrideConfigurationsPattern.matcher(line);
-                if (actMatcher.matches()) {
-                    log(line);
-                    while (ReportedDisplays.shouldContinueExtracting(activityProcessDump,
-                              sDisplayConfigPattern)) {
-                        final String displayOverrideConfigLine = activityProcessDump.pop().trim();
-                        log(displayOverrideConfigLine);
-                        actMatcher = sDisplayConfigPattern.matcher(displayOverrideConfigLine);
-                        actMatcher.matches();
-                        final String tempUniqueId = "local:" + actMatcher.group(1);
-                        final Integer displayId = Integer.valueOf(actMatcher.group(1));
-                        // Default unique ids for non virtual display.
-                        final String uniqueId =
-                                (virtualUniqueIdMap.containsKey(actMatcher.group(1)))
-                                ? virtualUniqueIdMap.get(actMatcher.group(1)) : tempUniqueId;
-                        result.mDisplayStates.put(displayId,
-                                new DisplayState(displayId, actMatcher.group(2), uniqueId));
-                    }
-                    continue;
-                }
-
-                actMatcher = sGlobalConfigurationPattern.matcher(line);
-                if (actMatcher.matches()) {
-                    log(line);
-                    result.mGlobalConfig = actMatcher.group(1);
-                }
-            }
-
-            return result;
-        }
-
-        /** Check if next line in dump matches the pattern and we should continue extracting. */
-        static boolean shouldContinueExtracting(LinkedList<String> dump, Pattern matchingPattern) {
-            if (dump.isEmpty()) {
-                return false;
-            }
-
-            final String line = dump.peek().trim();
-            return matchingPattern.matcher(line).matches();
-        }
-
-        DisplayState getDisplayState(int displayId) {
-            return mDisplayStates.get(displayId);
-        }
-
-        int getNumberOfDisplays() {
-            return mDisplayStates.size();
-        }
-
-        /** Return the display state with width, height, dpi */
-        DisplayState getDisplayState(int width, int height, int dpi) {
-            for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
-                final DisplayState ds = entry.getValue();
-                if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == dpi
-                        && ds.getWidth() == width && ds.getHeight() == height) {
-                    return ds;
-                }
-            }
-            return null;
-        }
-
-        /** Return the display state with the given unique id */
-        DisplayState getDisplayState(String viewportId) {
-            for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
-                final DisplayState ds = entry.getValue();
-                if (ds.mDisplayViewportId.equals(viewportId)) {
-                    return ds;
-                }
-            }
-            return null;
-        }
-
-        /** Check if reported state is valid. */
-        boolean isValidState(int expectedDisplayCount) {
-            if (mDisplayStates.size() != expectedDisplayCount) {
-                return false;
-            }
-
-            for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
-                final DisplayState ds = entry.getValue();
-                if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == -1) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    ReportedDisplays getDisplaysStates() throws DeviceNotAvailableException {
-        // Parse dumpsys activity processes.
-        final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        mDevice.executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES, outputReceiver);
-        String dump = outputReceiver.getOutput();
-        mDumpLines.clear();
-
-        Collections.addAll(mDumpLines, dump.split("\\n"));
-
-        // Parse dumpsys display
-        final CollectingOutputReceiver outputReceiverNew = new CollectingOutputReceiver();
-        mDevice.executeShellCommand(DUMPSYS_DISPLAY, outputReceiverNew);
-        String dumpNew = outputReceiverNew.getOutput();
-        mAltDumpLines.clear();
-
-        Collections.addAll(mAltDumpLines, dumpNew.split("\\n"));
-
-        return ReportedDisplays.create(mDumpLines, mAltDumpLines);
-    }
-
-    /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
-    protected List<ActivityManagerDisplayTests.DisplayState> findNewDisplayStates(
-            ReportedDisplays oldDisplays, ReportedDisplays newDisplays) {
-        final ArrayList<DisplayState> displays = new ArrayList();
-
-        for (Integer displayId : newDisplays.mDisplayStates.keySet()) {
-            if (!oldDisplays.mDisplayStates.containsKey(displayId)) {
-                displays.add(newDisplays.getDisplayState(displayId));
-            }
-        }
-
-        return displays;
-    }
-
-    /**
-     * Create new virtual display.
-     * @param densityDpi provide custom density for the display.
-     * @param launchInSplitScreen start {@link VirtualDisplayActivity} to side from
-     *                            {@link LaunchingActivity} on primary display.
-     * @param canShowWithInsecureKeyguard allow showing content when device is showing an insecure
-     *                                    keyguard.
-     * @param mustBeCreated should assert if the display was or wasn't created.
-     * @param publicDisplay make display public.
-     * @param resizeDisplay should resize display when surface size changes.
-     * @param launchActivity should launch test activity immediately after display creation.
-     * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display.
-     * @throws Exception
-     */
-    private List<ActivityManagerDisplayTests.DisplayState> createVirtualDisplays(int densityDpi,
-            boolean launchInSplitScreen, boolean canShowWithInsecureKeyguard, boolean mustBeCreated,
-            boolean publicDisplay, boolean resizeDisplay, String launchActivity, int displayCount)
-            throws Exception {
-        // Start an activity that is able to create virtual displays.
-        if (launchInSplitScreen) {
-            getLaunchActivityBuilder().setToSide(true)
-                    .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY).execute();
-        } else {
-            launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
-        }
-        mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY},
-                false /* compareTaskAndStackBounds */);
-        final ActivityManagerDisplayTests.ReportedDisplays originalDS = getDisplaysStates();
-
-        // Create virtual display with custom density dpi.
-        executeShellCommand(getCreateVirtualDisplayCommand(densityDpi, canShowWithInsecureKeyguard,
-                publicDisplay, resizeDisplay, launchActivity, displayCount));
-        mVirtualDisplayCreated = true;
-
-        return assertAndGetNewDisplays(mustBeCreated ? displayCount : -1, originalDS);
-    }
-
-    /**
-     * Simulate new display.
-     * @param densityDpi provide custom density for the display.
-     * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display.
-     */
-    private List<ActivityManagerDisplayTests.DisplayState> simulateDisplay(int densityDpi)
-            throws Exception {
-        final ActivityManagerDisplayTests.ReportedDisplays originalDs = getDisplaysStates();
-
-        // Create virtual display with custom density dpi.
-        executeShellCommand(getSimulateDisplayCommand(densityDpi));
-        mDisplaySimulated = true;
-
-        return assertAndGetNewDisplays(1, originalDs);
-    }
-
-    /**
-     * Wait for desired number of displays to be created and get their properties.
-     * @param newDisplayCount expected display count, -1 if display should not be created.
-     * @param originalDS display states before creation of new display(s).
-     */
-    private List<ActivityManagerDisplayTests.DisplayState> assertAndGetNewDisplays(
-            int newDisplayCount, ActivityManagerDisplayTests.ReportedDisplays originalDS)
-            throws Exception {
-        final int originalDisplayCount = originalDS.mDisplayStates.size();
-
-        // Wait for the display(s) to be created and get configurations.
-        final ActivityManagerDisplayTests.ReportedDisplays ds =
-                getDisplayStateAfterChange(originalDisplayCount + newDisplayCount);
-        if (newDisplayCount != -1) {
-            assertEquals("New virtual display(s) must be created",
-                    originalDisplayCount + newDisplayCount, ds.mDisplayStates.size());
-        } else {
-            assertEquals("New virtual display must not be created",
-                    originalDisplayCount, ds.mDisplayStates.size());
-            return null;
-        }
-
-        // Find the newly added display(s).
-        final List<ActivityManagerDisplayTests.DisplayState> newDisplays
-                = findNewDisplayStates(originalDS, ds);
-        assertTrue("New virtual display must be created", newDisplayCount == newDisplays.size());
-
-        return newDisplays;
-    }
-
-    /**
-     * Destroy existing virtual display.
-     */
-    void destroyVirtualDisplays() throws Exception {
-        if (mVirtualDisplayCreated) {
-            executeShellCommand(getDestroyVirtualDisplayCommand());
-            mVirtualDisplayCreated = false;
-        }
-    }
-
-    /**
-     * Destroy existing simulated display.
-     */
-    private void destroySimulatedDisplays() throws Exception {
-        if (mDisplaySimulated) {
-            executeShellCommand(getDestroySimulatedDisplayCommand());
-            mDisplaySimulated = false;
-        }
-    }
-
-    static class VirtualDisplayBuilder {
-        private final ActivityManagerDisplayTestBase mTests;
-
-        private int mDensityDpi = CUSTOM_DENSITY_DPI;
-        private boolean mLaunchInSplitScreen = false;
-        private boolean mCanShowWithInsecureKeyguard = false;
-        private boolean mPublicDisplay = false;
-        private boolean mResizeDisplay = true;
-        private String mLaunchActivity = null;
-        private boolean mSimulateDisplay = false;
-        private boolean mMustBeCreated = true;
-
-        public VirtualDisplayBuilder(ActivityManagerDisplayTestBase tests) {
-            mTests = tests;
-        }
-
-        public VirtualDisplayBuilder setDensityDpi(int densityDpi) {
-            mDensityDpi = densityDpi;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setLaunchInSplitScreen(boolean launchInSplitScreen) {
-            mLaunchInSplitScreen = launchInSplitScreen;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setCanShowWithInsecureKeyguard(
-                boolean canShowWithInsecureKeyguard) {
-            mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setPublicDisplay(boolean publicDisplay) {
-            mPublicDisplay = publicDisplay;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setResizeDisplay(boolean resizeDisplay) {
-            mResizeDisplay = resizeDisplay;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setLaunchActivity(String launchActivity) {
-            mLaunchActivity = launchActivity;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setSimulateDisplay(boolean simulateDisplay) {
-            mSimulateDisplay = simulateDisplay;
-            return this;
-        }
-
-        public VirtualDisplayBuilder setMustBeCreated(boolean mustBeCreated) {
-            mMustBeCreated = mustBeCreated;
-            return this;
-        }
-
-        public DisplayState build() throws Exception {
-            final List<DisplayState> displays = build(1);
-            return displays != null && !displays.isEmpty() ? displays.get(0) : null;
-        }
-
-        public List<DisplayState> build(int count) throws Exception {
-            if (mSimulateDisplay) {
-                return mTests.simulateDisplay(mDensityDpi);
-            }
-
-            return mTests.createVirtualDisplays(mDensityDpi, mLaunchInSplitScreen,
-                    mCanShowWithInsecureKeyguard, mMustBeCreated, mPublicDisplay, mResizeDisplay,
-                    mLaunchActivity, count);
-        }
-    }
-
-    private static String getCreateVirtualDisplayCommand(int densityDpi,
-            boolean canShowWithInsecureKeyguard, boolean publicDisplay, boolean resizeDisplay,
-            String launchActivity, int displayCount) {
-        final StringBuilder commandBuilder
-                = new StringBuilder(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY));
-        commandBuilder.append(" -f 0x20000000");
-        commandBuilder.append(" --es command create_display");
-        if (densityDpi != INVALID_DENSITY_DPI) {
-            commandBuilder.append(" --ei density_dpi ").append(densityDpi);
-        }
-        commandBuilder.append(" --ei count ").append(displayCount);
-        commandBuilder.append(" --ez can_show_with_insecure_keyguard ")
-                .append(canShowWithInsecureKeyguard);
-        commandBuilder.append(" --ez public_display ").append(publicDisplay);
-        commandBuilder.append(" --ez resize_display ").append(resizeDisplay);
-        if (launchActivity != null) {
-            commandBuilder.append(" --es launch_target_activity ").append(launchActivity);
-        }
-        return commandBuilder.toString();
-    }
-
-    private static String getDestroyVirtualDisplayCommand() {
-        return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
-                " --es command destroy_display";
-    }
-
-    private static String getSimulateDisplayCommand(int densityDpi) {
-        return "settings put global overlay_display_devices 1024x768/" + densityDpi;
-    }
-
-    private static String getDestroySimulatedDisplayCommand() {
-        return "settings delete global overlay_display_devices";
-    }
-
-    /** Wait for provided number of displays and report their configurations. */
-    ReportedDisplays getDisplayStateAfterChange(int expectedDisplayCount)
-            throws DeviceNotAvailableException {
-        ReportedDisplays ds = getDisplaysStates();
-
-        int retriesLeft = 5;
-        while (!ds.isValidState(expectedDisplayCount) && retriesLeft-- > 0) {
-            log("***Waiting for the correct number of displays...");
-            try {
-                Thread.sleep(1000);
-            } catch (InterruptedException e) {
-                log(e.toString());
-            }
-            ds = getDisplaysStates();
-        }
-
-        return ds;
-    }
-
-    /** Checks if the device supports multi-display. */
-    boolean supportsMultiDisplay() throws Exception {
-        return hasDeviceFeature("android.software.activities_on_secondary_displays");
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
deleted file mode 100644
index 72a477f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ /dev/null
@@ -1,2041 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-import android.server.displayservice.DisplayHelper;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.ActivityManagerState.STATE_PAUSED;
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayTests
- */
-public class ActivityManagerDisplayTests extends ActivityManagerDisplayTestBase {
-    private static final String WM_SIZE = "wm size";
-    private static final String WM_DENSITY = "wm density";
-
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
-    private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
-    private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
-    private static final String SECOND_ACTIVITY_NAME = "SecondActivity";
-    private static final String SECOND_ACTIVITY_NO_EMBEDDING_NAME = "SecondActivityNoEmbedding";
-    private static final String THIRD_ACTIVITY_NAME = "ThirdActivity";
-    private static final String VR_TEST_ACTIVITY_NAME = "VrTestActivity";
-    private static final String SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME = "ShowWhenLockedAttrActivity";
-    private static final String SECOND_PACKAGE_NAME = "android.server.cts.second";
-    private static final String THIRD_PACKAGE_NAME = "android.server.cts.third";
-    private static final String VR_UNIQUE_DISPLAY_ID =
-            "virtual:android:277f1a09-b88d-4d1e-8716-796f114d080b";
-    private static final String VR_STANDALONE_DEVICE_PROPERTY = "ro.boot.vr";
-
-    private DisplayHelper mExternalDisplayHelper;
-
-    /** Physical display metrics and overrides in the beginning of the test. */
-    private ReportedDisplayMetrics mInitialDisplayMetrics;
-
-    // Set on standalone VR devices to indicate that the VR virtual display is the display where 2d
-    // activities are launched.
-    private boolean mVrHeadset;
-    private int mVrVirtualDisplayId;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInitialDisplayMetrics = getDisplayMetrics();
-        mVrHeadset = isVrHeadset();
-        final DisplayState vrDisplay = getDisplaysStates().getDisplayState(VR_UNIQUE_DISPLAY_ID);
-        mVrVirtualDisplayId = mVrHeadset ? vrDisplay.mDisplayId : -1;
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            enablePersistentVrMode(false);
-            restoreDisplayMetricsOverrides();
-            if (mExternalDisplayHelper != null) {
-                mExternalDisplayHelper.releaseDisplay();
-                mExternalDisplayHelper = null;
-            }
-            setPrimaryDisplayState(true);
-        } catch (DeviceNotAvailableException e) {
-            logE(e.getMessage());
-        }
-        super.tearDown();
-    }
-
-    private void enablePersistentVrMode(boolean enabled) throws Exception {
-        if (enabled) {
-            executeShellCommand("setprop vr_virtualdisplay true");
-            executeShellCommand("vr set-persistent-vr-mode-enabled true");
-        } else {
-            executeShellCommand("vr set-persistent-vr-mode-enabled false");
-            executeShellCommand("setprop vr_virtualdisplay false");
-        }
-    }
-
-    private boolean isVrHeadset() {
-        try {
-            if (mDevice.getProperty(VR_STANDALONE_DEVICE_PROPERTY).equals("1")) {
-              return true;
-            }
-
-            return false;
-        } catch (DeviceNotAvailableException e) {
-            return false;
-        }
-    }
-
-    private void restoreDisplayMetricsOverrides() throws Exception {
-        if (mInitialDisplayMetrics.sizeOverrideSet) {
-            executeShellCommand(WM_SIZE + " " + mInitialDisplayMetrics.overrideWidth + "x"
-                    + mInitialDisplayMetrics.overrideHeight);
-        } else {
-            executeShellCommand("wm size reset");
-        }
-        if (mInitialDisplayMetrics.densityOverrideSet) {
-            executeShellCommand(WM_DENSITY + " " + mInitialDisplayMetrics.overrideDensity);
-        } else {
-            executeShellCommand("wm density reset");
-        }
-    }
-
-    /**
-     * Tests that the global configuration is equal to the default display's override configuration.
-     */
-    public void testDefaultDisplayOverrideConfiguration() throws Exception {
-        final ReportedDisplays reportedDisplays = getDisplaysStates();
-        assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
-        final DisplayState primaryDisplay = reportedDisplays.getDisplayState(DEFAULT_DISPLAY_ID);
-        assertEquals("Primary display's configuration should not be equal to global configuration.",
-                reportedDisplays.mGlobalConfig, primaryDisplay.mOverrideConfig);
-    }
-
-    /**
-     * Tests that secondary display has override configuration set.
-     */
-    public void testCreateVirtualDisplayWithCustomConfig() throws Exception {
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Find the density of created display.
-        final int newDensityDpi = newDisplay.getDpi();
-        assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
-    }
-
-    /**
-     * Tests that launch on secondary display is not permitted if device has the feature disabled.
-     * Activities requested to be launched on a secondary display in this case should land on the
-     * default display.
-     */
-    public void testMultiDisplayDisabled() throws Exception {
-        if (supportsMultiDisplay()) {
-            // Only check devices with the feature disabled.
-            return;
-        }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
-
-        // Check that activity is on the right display.
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed",
-                getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
-        assertEquals("Front stack must be on the default display", DEFAULT_DISPLAY_ID,
-                frontStack.mDisplayId);
-        mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
-    }
-
-    /**
-     * Tests that any new activity launch in Vr mode is in Vr display.
-     */
-    public void testVrActivityLaunch() throws Exception {
-        if (!supportsVrMode() || !supportsMultiDisplay()) {
-            // VR Mode is not supported on this device, bail from this test.
-            return;
-        }
-
-        // Put the device in persistent vr mode.
-        enablePersistentVrMode(true);
-
-        // Launch the VR activity.
-        launchActivity(VR_TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
-        mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
-        // Launch the non-VR 2D activity and check where it ends up.
-        launchActivity(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Ensure that the subsequent activity is visible
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
-        // Check that activity is launched in focused stack on primary display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
-        final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        final ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(focusedStackId);
-        assertEquals("Launched activity must be resumed in focused stack",
-            getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
-        // Check if the launch activity is in Vr virtual display id.
-        final ReportedDisplays reportedDisplays = getDisplaysStates();
-        assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
-        final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
-        assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
-        // Check if the focused activity is on this virtual stack.
-        assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
-            focusedStack.mDisplayId);
-
-        // Put the device out of persistent vr mode.
-        enablePersistentVrMode(false);
-    }
-
-    /**
-     * Tests that any activity already present is re-launched in Vr display in vr mode.
-     */
-    public void testVrActivityReLaunch() throws Exception {
-        if (!supportsVrMode() || !supportsMultiDisplay()) {
-            // VR Mode is not supported on this device, bail from this test.
-            return;
-        }
-
-        // Launch a 2D activity.
-        launchActivity(LAUNCHING_ACTIVITY);
-
-        // Put the device in persistent vr mode.
-        enablePersistentVrMode(true);
-
-        // Launch the VR activity.
-        launchActivity(VR_TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
-        mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
-        // Re-launch the non-VR 2D activity and check where it ends up.
-        launchActivity(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Ensure that the subsequent activity is visible
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
-        // Check that activity is launched in focused stack on primary display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
-        final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        final ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(focusedStackId);
-        assertEquals("Launched activity must be resumed in focused stack",
-            getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
-        // Check if the launch activity is in Vr virtual display id.
-        final ReportedDisplays reportedDisplays = getDisplaysStates();
-        assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
-        final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
-        assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
-        // Check if the focused activity is on this virtual stack.
-        assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
-            focusedStack.mDisplayId);
-
-        // Put the device out of persistent vr mode.
-        enablePersistentVrMode(false);
-    }
-
-    private int getCurrentDefaultDisplayId() {
-        return mVrHeadset ? mVrVirtualDisplayId : DEFAULT_DISPLAY_ID;
-    }
-
-    /**
-     * Tests that any new activity launch post Vr mode is in the main display.
-     */
-    public void testActivityLaunchPostVr() throws Exception {
-        if (!supportsVrMode() || !supportsMultiDisplay()) {
-            // VR Mode is not supported on this device, bail from this test.
-            return;
-        }
-
-        // Put the device in persistent vr mode.
-        enablePersistentVrMode(true);
-
-        // Launch the VR activity.
-        launchActivity(VR_TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
-        mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
-        // Launch the non-VR 2D activity and check where it ends up.
-        launchActivity(ALT_LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {ALT_LAUNCHING_ACTIVITY});
-
-        // Ensure that the subsequent activity is visible
-        mAmWmState.assertVisibility(ALT_LAUNCHING_ACTIVITY, true /* visible */);
-
-        // Check that activity is launched in focused stack on primary display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", ALT_LAUNCHING_ACTIVITY);
-        final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        final ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(focusedStackId);
-        assertEquals("Launched activity must be resumed in focused stack",
-            getActivityComponentName(ALT_LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
-        // Check if the launch activity is in Vr virtual display id.
-        final ReportedDisplays reportedDisplays = getDisplaysStates();
-        assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
-        final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
-        assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
-        // Check if the focused activity is on this virtual stack.
-        assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
-            focusedStack.mDisplayId);
-
-        // Put the device out of persistent vr mode.
-        enablePersistentVrMode(false);
-
-        // There isn't a direct launch of activity which can take an user out of persistent VR mode.
-        // This sleep is to account for that delay and let device settle once it comes out of VR
-        // mode.
-        try {
-            Thread.sleep(2000);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        // Launch the non-VR 2D activity and check where it ends up.
-        launchActivity(RESIZEABLE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {RESIZEABLE_ACTIVITY_NAME});
-
-        // Ensure that the subsequent activity is visible
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-
-        // Check that activity is launched in focused stack on the correct display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                RESIZEABLE_ACTIVITY_NAME);
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(RESIZEABLE_ACTIVITY_NAME), frontStack.mResumedActivity);
-        assertEquals("Front stack must be on the correct display",
-                displayId, frontStack.mDisplayId);
-    }
-
-    public void testCreateMultipleVirtualDisplays() throws Exception {
-        // Create new virtual display.
-        final List<DisplayState> newDisplays = new VirtualDisplayBuilder(this).build(3);
-        destroyVirtualDisplays();
-        getDisplayStateAfterChange(1);
-    }
-
-    /**
-     * Tests launching an activity on virtual display.
-     */
-    @Presubmit
-    public void testLaunchActivityOnSecondaryDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        final String logSeparator = clearLogcat();
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                TEST_ACTIVITY_NAME);
-
-        // Check that activity is on the right display.
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the secondary display and resumed",
-                getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        // Check that activity config corresponds to display config.
-        final ReportedSizes reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY_NAME,
-                logSeparator);
-        assertEquals("Activity launched on secondary display must have proper configuration",
-                CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
-    }
-
-    /**
-     * Tests launching a non-resizeable activity on virtual display. It should land on the
-     * default display.
-     */
-    public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                NON_RESIZEABLE_ACTIVITY_NAME);
-
-        // Check that activity is on the right display.
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-        final ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
-    }
-
-    /**
-     * 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.
-     */
-    public void testLaunchNonResizeableActivityWithSplitScreen() throws Exception {
-        if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
-        // Start launching activity.
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        // Create new virtual display.
-        final DisplayState newDisplay =
-                new VirtualDisplayBuilder(this).setLaunchInSplitScreen(true).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                NON_RESIZEABLE_ACTIVITY_NAME);
-
-        // Check that activity is on the right display.
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-        final ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
-        mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
-    }
-
-    /**
-     * Tests moving a non-resizeable activity to a virtual display. It should land on the default
-     * display.
-     */
-    public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        // Launch a non-resizeable activity on a primary display.
-        launchActivityInNewTask(NON_RESIZEABLE_ACTIVITY_NAME);
-        // Launch a resizeable activity on new secondary display to create a new stack there.
-        launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-
-        // Try to move the non-resizeable activity to new secondary display.
-        moveActivityToStack(NON_RESIZEABLE_ACTIVITY_NAME, frontStackId);
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                NON_RESIZEABLE_ACTIVITY_NAME);
-
-        // Check that activity is on the right display.
-        frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-        final ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
-    }
-
-    /**
-     * Tests launching a non-resizeable activity on virtual display from activity there. It should
-     * land on the secondary display based on the resizeability of the root activity of the task.
-     */
-    public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
-        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.mDisplayId);
-        ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the secondary display and resumed",
-                getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
-
-        // Launch non-resizeable activity from secondary display.
-        executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
-                + "--ez new_task true --es target_activity " + NON_RESIZEABLE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
-        // Check that non-resizeable activity is on the secondary display, because of the resizeable
-        // root of the task.
-        frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
-    }
-
-    /**
-     * 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).
-     */
-    public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                LAUNCHING_ACTIVITY);
-
-        // Check that launching activity is on the secondary display.
-        int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the secondary display and resumed",
-                getActivityComponentName(LAUNCHING_ACTIVITY),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
-
-        // Launch non-resizeable activity from secondary display.
-        getLaunchActivityBuilder().setTargetActivityName(NON_RESIZEABLE_ACTIVITY_NAME)
-                .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.mDisplayId == frontStack.mDisplayId);
-        assertEquals("Launched activity must be resumed",
-                getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on a just launched activity", frontStackId);
-    }
-
-    /**
-     * Tests launching an activity on a virtual display without special permission must not be
-     * allowed.
-     */
-    public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        final String logSeparator = clearLogcat();
-
-        // Try to launch an activity and check it security exception was triggered.
-        final String broadcastTarget = "-a " + SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION"
-                + " -p " + SECOND_PACKAGE_NAME;
-        final String includeStoppedPackagesFlag = " -f 0x00000020";
-        executeShellCommand("am broadcast " + broadcastTarget
-                + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
-                + " --es package_name " + componentName
-                + " --ei display_id " + newDisplay.mDisplayId
-                + includeStoppedPackagesFlag);
-
-        assertSecurityException("LaunchBroadcastReceiver", logSeparator);
-
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        assertFalse("Restricted activity must not be launched",
-                mAmWmState.getAmState().containsActivity(TEST_ACTIVITY_NAME));
-    }
-
-    /**
-     * Tests launching an activity on a virtual display without special permission must be allowed
-     * for activities with same UID.
-     */
-    public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Try to launch an activity and check it security exception was triggered.
-        final String broadcastTarget = "-a " + componentName + ".LAUNCH_BROADCAST_ACTION"
-                + " -p " + componentName;
-        executeShellCommand("am broadcast " + broadcastTarget
-                + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
-                + " --es package_name " + componentName
-                + " --ei display_id " + newDisplay.mDisplayId);
-
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME);
-
-        final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        final ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState()
-                .getStackById(externalFocusedStackId);
-        assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
-                focusedStack.mDisplayId);
-
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app", TEST_ACTIVITY_NAME);
-        assertEquals("Activity launched by owner must be on external display",
-                externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
-    }
-
-    /**
-     * Tests launching an activity on virtual display and then launching another activity via shell
-     * command and without specifying the display id - the second activity must appear on the
-     * primary display.
-     */
-    @Presubmit
-    public void testConsequentLaunchActivity() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                TEST_ACTIVITY_NAME);
-
-        // Launch second activity without specifying display.
-        launchActivity(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Check that activity is launched in focused stack on the correct display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
-
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
-        assertEquals("Front stack must be on the correct display",
-                displayId, frontStack.mDisplayId);
-    }
-
-    /**
-     * Tests launching an activity on simulated display and then launching another activity from the
-     * first one - it must appear on the secondary display, because it was launched from there.
-     */
-    @Presubmit
-    public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
-                LAUNCHING_ACTIVITY);
-
-        // Launch second activity from app on secondary display without specifying display id.
-        getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        // Check that activity is launched in focused stack on external display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
-    }
-
-    /**
-     * Tests launching an activity on virtual display and then launching another activity from the
-     * first one - it must appear on the secondary display, because it was launched from there.
-     */
-    public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
-                LAUNCHING_ACTIVITY);
-
-        // Launch second activity from app on secondary display without specifying display id.
-        getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        // Check that activity is launched in focused stack on external display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
-    }
-
-    /**
-     * Tests launching an activity on virtual display and then launching another activity from the
-     * first one with specifying the target display - it must appear on the secondary display.
-     */
-    public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
-                LAUNCHING_ACTIVITY);
-
-        // Launch second activity from app on secondary display specifying same display id.
-        getLaunchActivityBuilder().setTargetActivityName(SECOND_ACTIVITY_NAME)
-                .setTargetPackage(SECOND_PACKAGE_NAME)
-                .setDisplayId(newDisplay.mDisplayId).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        // Check that activity is launched in focused stack on external display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", SECOND_PACKAGE_NAME,
-                SECOND_ACTIVITY_NAME);
-        int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-
-        // Launch other activity with different uid and check if it has launched successfully.
-        final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
-        executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
-                + " --ei display_id " + newDisplay.mDisplayId
-                + " --es target_activity " + THIRD_ACTIVITY_NAME
-                + " --es package_name " + THIRD_PACKAGE_NAME);
-        mAmWmState.waitForValidState(mDevice, new String[] {THIRD_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME);
-
-        // Check that activity is launched in focused stack on external display.
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", THIRD_PACKAGE_NAME,
-                THIRD_ACTIVITY_NAME);
-        frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(THIRD_PACKAGE_NAME, THIRD_ACTIVITY_NAME),
-                frontStack.mResumedActivity);
-    }
-
-    /**
-     * Tests launching an activity on virtual display and then launching another activity that
-     * doesn't allow embedding - it should fail with security exception.
-     */
-    public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
-                LAUNCHING_ACTIVITY);
-
-        final String logSeparator = clearLogcat();
-
-        // Launch second activity from app on secondary display specifying same display id.
-        getLaunchActivityBuilder().setTargetActivityName(SECOND_ACTIVITY_NO_EMBEDDING_NAME)
-                .setTargetPackage(SECOND_PACKAGE_NAME)
-                .setDisplayId(newDisplay.mDisplayId).execute();
-
-        assertSecurityException("ActivityLauncher", logSeparator);
-    }
-
-    /**
-     * Tests launching an activity to secondary display from activity on primary display.
-     */
-    public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Start launching activity.
-        launchActivity(LAUNCHING_ACTIVITY);
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        // Launch activity on secondary display from the app on primary display.
-        getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME)
-                .setDisplayId(newDisplay.mDisplayId).execute();
-
-        // Check that activity is launched on external display.
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                TEST_ACTIVITY_NAME);
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be resumed in front stack",
-                getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
-    }
-
-    /**
-     * Tests launching activities on secondary and then on primary display to see if the stack
-     * visibility is not affected.
-     */
-    @Presubmit
-    public void testLaunchActivitiesAffectsVisibility() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Start launching activity.
-        launchActivity(LAUNCHING_ACTIVITY);
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch activity on primary display and check if it doesn't affect activity on secondary
-        // display.
-        getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
-        mAmWmState.waitForValidState(mDevice, RESIZEABLE_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-    }
-
-    /**
-     * Test that move-task works when moving between displays.
-     */
-    @Presubmit
-    public void testMoveTaskBetweenDisplays() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
-                VIRTUAL_DISPLAY_ACTIVITY);
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
-        ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(defaultDisplayStackId);
-        assertEquals("Focus must remain on the correct display", displayId,
-                focusedStack.mDisplayId);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display", TEST_ACTIVITY_NAME);
-        int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
-        assertEquals("Focused stack must be on secondary display",
-                newDisplay.mDisplayId, focusedStack.mDisplayId);
-
-        // Move activity from secondary display to primary.
-        moveActivityToStack(TEST_ACTIVITY_NAME, defaultDisplayStackId);
-        mAmWmState.waitForFocusedStack(mDevice, defaultDisplayStackId);
-        mAmWmState.assertFocusedActivity("Focus must be on moved activity", TEST_ACTIVITY_NAME);
-        focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
-        assertEquals("Focus must return to primary display", displayId,
-                focusedStack.mDisplayId);
-    }
-
-    /**
-     * Tests launching activities on secondary display and then removing it to see if stack focus
-     * is moved correctly.
-     * This version launches virtual display creator to fullscreen stack in split-screen.
-     */
-    @Presubmit
-    public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
-        if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
-        // Start launching activity into docked stack.
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
-        tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    /**
-     * Tests launching activities on secondary display and then removing it to see if stack focus
-     * is moved correctly.
-     * This version launches virtual display creator to docked stack in split-screen.
-     */
-    public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
-        if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
-        // Setup split-screen.
-        launchActivityInDockStack(RESIZEABLE_ACTIVITY_NAME);
-
-        // Start launching activity into fullscreen stack.
-        launchActivityInStack(LAUNCHING_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
-        tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    /**
-     * Tests launching activities on secondary display and then removing it to see if stack focus
-     * is moved correctly.
-     * This version works without split-screen.
-     */
-    public void testStackFocusSwitchOnDisplayRemoved3() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Start an activity on default display to determine default stack.
-        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
-        final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-        // Finish probing activity.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
-        tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */, focusedStackId);
-    }
-
-    /**
-     * Create a virtual display, launch a test activity there, destroy the display and check if test
-     * activity is moved to a stack on the default display.
-     */
-    private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int defaultStackId)
-            throws Exception {
-        // Create new virtual display.
-        final VirtualDisplayBuilder builder = new VirtualDisplayBuilder(this)
-                .setPublicDisplay(true);
-        if (splitScreen) {
-            builder.setLaunchInSplitScreen(true);
-        }
-        final DisplayState newDisplay = builder.build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        if (splitScreen) {
-            mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-        }
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                TEST_ACTIVITY_NAME);
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        // Destroy virtual display.
-        destroyVirtualDisplays();
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME, defaultStackId);
-        mAmWmState.assertSanity();
-        mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */);
-
-        // Check if the focus is switched back to primary display.
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertFocusedStack(
-                "Default stack on primary display must be focused after display removed",
-                defaultStackId);
-        mAmWmState.assertFocusedActivity(
-                "Focus must be switched back to activity on primary display",
-                TEST_ACTIVITY_NAME);
-    }
-
-    /**
-     * Tests launching activities on secondary display and then removing it to see if stack focus
-     * is moved correctly.
-     */
-    public void testStackFocusSwitchOnStackEmptied() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                BROADCAST_RECEIVER_ACTIVITY);
-
-        // Lock the device, so that activity containers will be detached.
-        sleepDevice();
-
-        // Finish activity on secondary display.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
-        // Unlock and check if the focus is switched back to primary display.
-        wakeUpAndUnlockDevice();
-        mAmWmState.waitForFocusedStack(mDevice, focusedStackId);
-        mAmWmState.waitForValidState(mDevice, VIRTUAL_DISPLAY_ACTIVITY);
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
-                VIRTUAL_DISPLAY_ACTIVITY);
-    }
-
-    /**
-     * Tests that input events on the primary display take focus from the virtual display.
-     */
-    public void testStackFocusSwitchOnTouchEvent() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY});
-        mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
-                VIRTUAL_DISPLAY_ACTIVITY);
-
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
-                TEST_ACTIVITY_NAME);
-
-        final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
-        final int width = displayMetrics.getWidth();
-        final int height = displayMetrics.getHeight();
-        executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
-
-        mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY});
-        mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
-                VIRTUAL_DISPLAY_ACTIVITY);
-    }
-
-    /** Test that shell is allowed to launch on secondary displays. */
-    public void testPermissionLaunchFromShell() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
-                VIRTUAL_DISPLAY_ACTIVITY);
-        final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
-        assertEquals("Focus must remain on the correct display", displayId,
-                focusedStack.mDisplayId);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                TEST_ACTIVITY_NAME);
-        final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-        assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
-                focusedStack.mDisplayId);
-
-        // Launch other activity with different uid and check it is launched on dynamic stack on
-        // secondary display.
-        final String startCmd =  "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME
-                + " --display " + newDisplay.mDisplayId;
-        executeShellCommand(startCmd);
-
-        mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app", SECOND_PACKAGE_NAME,
-                SECOND_ACTIVITY_NAME);
-        assertEquals("Activity launched by system must be on external display",
-                externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
-    }
-
-    /** Test that launching from app that is on external display is allowed. */
-    public void testPermissionLaunchFromAppOnSecondary() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        // Launch activity with different uid on secondary display.
-        final String startCmd =  "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME;
-        final String displayTarget = " --display " + newDisplay.mDisplayId;
-        executeShellCommand(startCmd + displayTarget);
-
-        mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
-                SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
-        final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-        assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
-                focusedStack.mDisplayId);
-
-        // Launch another activity with third different uid from app on secondary display and check
-        // it is launched on secondary display.
-        final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
-        final String targetActivity = " --es target_activity " + THIRD_ACTIVITY_NAME
-                + " --es package_name " + THIRD_PACKAGE_NAME
-                + " --ei display_id " + newDisplay.mDisplayId;
-        final String includeStoppedPackagesFlag = " -f 0x00000020";
-        executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
-                + targetActivity + includeStoppedPackagesFlag);
-
-        mAmWmState.waitForValidState(mDevice, new String[] {THIRD_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
-                THIRD_PACKAGE_NAME, THIRD_ACTIVITY_NAME);
-        assertEquals("Activity launched by app on secondary display must be on that display",
-                externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
-    }
-
-    /** Tests that an activity can launch an activity from a different UID into its own task. */
-    public void testPermissionLaunchMultiUidTask() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Check that the first activity is launched onto the secondary display
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        ActivityManagerState.ActivityStack frontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Activity launched on secondary display must be resumed",
-                getActivityComponentName(LAUNCHING_ACTIVITY),
-                frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        // Launch an activity from a different UID into the first activity's task
-        getLaunchActivityBuilder()
-                .setTargetPackage(SECOND_PACKAGE_NAME)
-                .setTargetActivityName(SECOND_ACTIVITY_NAME).execute();
-
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-        frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
-                SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
-        assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
-    }
-
-    /**
-     * Test that launching from display owner is allowed even when the the display owner
-     * doesn't have anything on the display.
-     */
-    public void testPermissionLaunchFromOwner() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
-                VIRTUAL_DISPLAY_ACTIVITY);
-        final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
-        assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
-                focusedStack.mDisplayId);
-
-        // Launch other activity with different uid on secondary display.
-        final String startCmd =  "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME;
-        final String displayTarget = " --display " + newDisplay.mDisplayId;
-        executeShellCommand(startCmd + displayTarget);
-
-        mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
-                SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
-        final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-        assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
-                focusedStack.mDisplayId);
-
-        // Check that owner uid can launch its own activity on secondary display.
-        final String broadcastAction = componentName + ".LAUNCH_BROADCAST_ACTION";
-        executeShellCommand("am broadcast -a " + broadcastAction + " -p " + componentName
-                + " --ez launch_activity true --ez new_task true --ez multiple_task true"
-                + " --ei display_id " + newDisplay.mDisplayId);
-
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME);
-        mAmWmState.assertFocusedActivity("Focus must be on newly launched app", TEST_ACTIVITY_NAME);
-        assertEquals("Activity launched by owner must be on external display",
-                externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
-    }
-
-    /**
-     * Test that launching from app that is not present on external display and doesn't own it to
-     * that external display is not allowed.
-     */
-    public void testPermissionLaunchFromDifferentApp() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
-                VIRTUAL_DISPLAY_ACTIVITY);
-        final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        ActivityManagerState.ActivityStack focusedStack
-                = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
-        assertEquals("Focus must remain on the correct display", displayId,
-                focusedStack.mDisplayId);
-
-        // Launch activity on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                TEST_ACTIVITY_NAME);
-        final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-        focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-        assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
-                focusedStack.mDisplayId);
-
-        final String logSeparator = clearLogcat();
-
-        // Launch other activity with different uid and check security exception is triggered.
-        final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
-        final String includeStoppedPackagesFlag = " -f 0x00000020";
-        executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
-                + " --ei display_id " + newDisplay.mDisplayId + includeStoppedPackagesFlag);
-
-        assertSecurityException("LaunchBroadcastReceiver", logSeparator);
-
-        mAmWmState.waitForValidState(mDevice, new String[] {TEST_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-        mAmWmState.assertFocusedActivity(
-                "Focus must be on first activity", componentName, TEST_ACTIVITY_NAME);
-        assertEquals("Focused stack must be on secondary display's stack",
-                externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
-    }
-
-    private void assertSecurityException(String component, String logSeparator) throws Exception {
-        int tries = 0;
-        boolean match = false;
-        final Pattern pattern = Pattern.compile(".*SecurityException launching activity.*");
-        while (tries < 5 && !match) {
-            String[] logs = getDeviceLogsForComponent(component, logSeparator);
-            for (String line : logs) {
-                Matcher m = pattern.matcher(line);
-                if (m.matches()) {
-                    match = true;
-                    break;
-                }
-            }
-            tries++;
-            try {
-                Thread.sleep(500);
-            } catch (InterruptedException e) {
-            }
-        }
-
-        assertTrue("Expected exception not found", match);
-    }
-
-    /**
-     * Test that only private virtual display can show content with insecure keyguard.
-     */
-    public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
-        if (!supportsMultiDisplay()) {
-            return;
-        }
-
-        // Try to create new show-with-insecure-keyguard public virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this)
-                .setPublicDisplay(true)
-                .setCanShowWithInsecureKeyguard(true)
-                .setMustBeCreated(false)
-                .build();
-
-        // Check that the display is not created.
-        assertNull(newDisplay);
-    }
-
-    /**
-     * Test that all activities that were on the private display are destroyed on display removal.
-     */
-    // TODO: Flaky, add to presubmit when b/63404575 is fixed.
-    public void testContentDestroyOnDisplayRemoved() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new private virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch activities on new secondary display.
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
-        launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                RESIZEABLE_ACTIVITY_NAME);
-
-        // Destroy the display and check if activities are removed from system.
-        final String logSeparator = clearLogcat();
-        destroyVirtualDisplays();
-        final String activityName1
-                = ActivityManagerTestBase.getActivityComponentName(TEST_ACTIVITY_NAME);
-        final String activityName2
-                = ActivityManagerTestBase.getActivityComponentName(RESIZEABLE_ACTIVITY_NAME);
-        final String windowName1
-                = ActivityManagerTestBase.getWindowName(TEST_ACTIVITY_NAME);
-        final String windowName2
-                = ActivityManagerTestBase.getWindowName(RESIZEABLE_ACTIVITY_NAME);
-        mAmWmState.waitForWithAmState(mDevice,
-                (state) -> !state.containsActivity(activityName1)
-                        && !state.containsActivity(activityName2),
-                "Waiting for activity to be removed");
-        mAmWmState.waitForWithWmState(mDevice,
-                (state) -> !state.containsWindow(windowName1)
-                        && !state.containsWindow(windowName2),
-                "Waiting for activity window to be gone");
-
-        // Check AM state.
-        assertFalse("Activity from removed display must be destroyed",
-                mAmWmState.getAmState().containsActivity(activityName1));
-        assertFalse("Activity from removed display must be destroyed",
-                mAmWmState.getAmState().containsActivity(activityName2));
-        // Check WM state.
-        assertFalse("Activity windows from removed display must be destroyed",
-                mAmWmState.getWmState().containsWindow(windowName1));
-        assertFalse("Activity windows from removed display must be destroyed",
-                mAmWmState.getWmState().containsWindow(windowName2));
-        // Check activity logs.
-        assertActivityDestroyed(TEST_ACTIVITY_NAME, logSeparator);
-        assertActivityDestroyed(RESIZEABLE_ACTIVITY_NAME, logSeparator);
-    }
-
-    /**
-     * Test that the update of display metrics updates all its content.
-     */
-    @Presubmit
-    public void testDisplayResize() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch a resizeable activity on new secondary display.
-        final String initialLogSeparator = clearLogcat();
-        launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-        mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                RESIZEABLE_ACTIVITY_NAME);
-
-        // Grab reported sizes and compute new with slight size change.
-        final ReportedSizes initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY_NAME,
-                initialLogSeparator);
-
-        // Resize the docked stack, so that activity with virtual display will also be resized.
-        final String logSeparator = clearLogcat();
-        executeShellCommand(getResizeVirtualDisplayCommand());
-
-        mAmWmState.waitForWithAmState(mDevice, amState -> {
-            try {
-                return readConfigChangeNumber(RESIZEABLE_ACTIVITY_NAME, logSeparator) == 1
-                        && amState.hasActivityState(RESIZEABLE_ACTIVITY_NAME, STATE_RESUMED);
-            } catch (Exception e) {
-                logE("Error waiting for valid state: " + e.getMessage());
-                return false;
-            }
-        }, "Wait for the configuration change to happen and for activity to be resumed.");
-
-        mAmWmState.computeState(mDevice, new String[] {RESIZEABLE_ACTIVITY_NAME,
-                VIRTUAL_DISPLAY_ACTIVITY}, false /* compareTaskAndStackBounds */);
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true);
-
-        // Check if activity in virtual display was resized properly.
-        assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY_NAME, 0 /* numRelaunch */,
-                1 /* numConfigChange */, logSeparator);
-
-        final ReportedSizes updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY_NAME,
-                logSeparator);
-        assertTrue(updatedSize.widthDp <= initialSize.widthDp);
-        assertTrue(updatedSize.heightDp <= initialSize.heightDp);
-        assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
-        assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
-    }
-
-    /** Read the number of configuration changes sent to activity from logs. */
-    private int readConfigChangeNumber(String activityName, String logSeparator) throws Exception {
-        return (new ActivityLifecycleCounts(activityName, logSeparator)).mConfigurationChangedCount;
-    }
-
-    /**
-     * Tests that when an activity is launched with displayId specified and there is an existing
-     * matching task on some other display - that task will moved to the target display.
-     */
-    public void testMoveToDisplayOnLaunch() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Launch activity with unique affinity, so it will the only one in its task.
-        launchActivity(LAUNCHING_ACTIVITY);
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-        final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
-        // Launch something to that display so that a new stack is created. We need this to be able
-        // to compare task numbers in stacks later.
-        launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-
-        final int taskNum = mAmWmState.getAmState().getStackById(defaultDisplayStackId)
-                .getTasks().size();
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final int taskNumOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
-                .getTasks().size();
-
-        // Launch activity on new secondary display.
-        // Using custom command here, because normally we add flags Intent#FLAG_ACTIVITY_NEW_TASK
-        // and 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 " + getActivityComponentName(LAUNCHING_ACTIVITY)
-                + " --display " + newDisplay.mDisplayId;
-        executeShellCommand(launchCommand);
-        mAmWmState.waitForActivityState(mDevice, 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",
-                getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        // Check that task has moved from primary display to secondary.
-        final int taskNumFinal = mAmWmState.getAmState().getStackById(defaultDisplayStackId)
-                .getTasks().size();
-        mAmWmState.assertEquals("Task number in default stack must be decremented.", taskNum - 1,
-                taskNumFinal);
-        final int taskNumFinalOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
-                .getTasks().size();
-        mAmWmState.assertEquals("Task number in stack on external display must be incremented.",
-                taskNumOnSecondary + 1, taskNumFinalOnSecondary);
-    }
-
-    /**
-     * Tests that when primary display is rotated secondary displays are not affected.
-     */
-    public void testRotationNotAffectingSecondaryScreen() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this)
-                .setResizeDisplay(false)
-                .build();
-        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
-        // Launch activity on new secondary display.
-        String logSeparator = clearLogcat();
-        launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                RESIZEABLE_ACTIVITY_NAME);
-        final ReportedSizes initialSizes = getLastReportedSizesForActivity(
-                RESIZEABLE_ACTIVITY_NAME, logSeparator);
-        assertNotNull("Test activity must have reported initial sizes on launch", initialSizes);
-
-        // Rotate primary display and check that activity on secondary display is not affected.
-        rotateAndCheckSameSizes(RESIZEABLE_ACTIVITY_NAME);
-
-        // Launch activity to secondary display when primary one is rotated.
-        final int initialRotation = mAmWmState.getWmState().getRotation();
-        setDeviceRotation((initialRotation + 1) % 4);
-
-        logSeparator = clearLogcat();
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-        mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
-        mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                TEST_ACTIVITY_NAME);
-        final ReportedSizes testActivitySizes = getLastReportedSizesForActivity(
-                TEST_ACTIVITY_NAME, logSeparator);
-        assertEquals("Sizes of secondary display must not change after rotation of primary display",
-                initialSizes, testActivitySizes);
-    }
-
-    private void rotateAndCheckSameSizes(String activityName) throws Exception {
-        for (int rotation = 3; rotation >= 0; --rotation) {
-            final String logSeparator = clearLogcat();
-            setDeviceRotation(rotation);
-            final ReportedSizes rotatedSizes = getLastReportedSizesForActivity(activityName,
-                    logSeparator);
-            assertNull("Sizes must not change after rotation", rotatedSizes);
-        }
-    }
-
-    /**
-     * Tests that task affinity does affect what display an activity is launched on but that
-     * matching the task component root does.
-     */
-    public void testTaskMatchAcrossDisplays() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Check that activity is on the right display.
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack firstFrontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Activity launched on secondary display must be resumed",
-                getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        executeShellCommand("am start -n " + getActivityComponentName(ALT_LAUNCHING_ACTIVITY));
-        mAmWmState.waitForValidState(mDevice, new String[] {ALT_LAUNCHING_ACTIVITY},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        final int displayId = getCurrentDefaultDisplayId();
-        // Check that second activity gets launched on the correct display
-        final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
-                displayId);
-        final ActivityManagerState.ActivityStack defaultDisplayFrontStack =
-                mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
-        assertEquals("Activity launched on default display must be resumed",
-                getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
-                defaultDisplayFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on primary display",
-                defaultDisplayFrontStackId);
-
-        executeShellCommand("am start -n " + getActivityComponentName(LAUNCHING_ACTIVITY));
-        final int stackId = mVrHeadset ? defaultDisplayFrontStackId : frontStackId;
-        final int focusedStackTaskCount = mVrHeadset ? 3 : 1;
-        mAmWmState.waitForFocusedStack(mDevice, stackId);
-
-        // Check that the third intent is redirected to the first task
-        final ActivityManagerState.ActivityStack secondFrontStack
-                = mAmWmState.getAmState().getStackById(stackId);
-        assertEquals("Activity launched on default display must be resumed",
-                getActivityComponentName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on primary display", stackId);
-        assertEquals("Focused stack must contain correct tasks",
-                focusedStackTaskCount, secondFrontStack.getTasks().size());
-        assertEquals("Focused task must only contain 1 activity",
-                1, secondFrontStack.getTasks().get(0).mActivities.size());
-    }
-
-    /**
-     * Tests than 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.
-     */
-    public void testNewTaskSameDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
-                .build();
-
-        launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
-        mAmWmState.computeState(mDevice, new String[] {BROADCAST_RECEIVER_ACTIVITY});
-
-        // Check that the first activity is launched onto the secondary display
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack firstFrontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Activity launched on secondary display must be resumed",
-                getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
-                firstFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
-        executeShellCommand("am start -n " + getActivityComponentName(TEST_ACTIVITY_NAME));
-        mAmWmState.waitForValidState(mDevice, new String[] {TEST_ACTIVITY_NAME},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
-        // 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",
-                getActivityComponentName(TEST_ACTIVITY_NAME), focusedStack.mResumedActivity);
-        // The default display id for a 2d activity launch is vr virtual display for a vr headset.
-        int displayId = getCurrentDefaultDisplayId();
-        assertEquals("Focus must be on the correct display", displayId,
-                focusedStack.mDisplayId);
-
-        executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
-                + "--ez new_task true --es target_activity " + LAUNCHING_ACTIVITY);
-
-        // Check that the third activity ends up in a new task in the same stack as the
-        // first activity
-        mAmWmState.waitForValidState(mDevice, new String[] {LAUNCHING_ACTIVITY},
-                null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
-        int stackId = mAmWmState.getAmState().getFrontStackId(displayId);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", stackId);
-        final ActivityManagerState.ActivityStack secondFrontStack =
-                mAmWmState.getAmState().getStackById(stackId);
-        assertEquals("Activity must be launched on secondary display",
-                getActivityComponentName(LAUNCHING_ACTIVITY),
-                secondFrontStack.mResumedActivity);
-        final int taskCount = mVrHeadset ? 3 : 2;
-        assertEquals("Secondary display must contain correct tasks",
-                taskCount, secondFrontStack.getTasks().size());
-    }
-
-    /**
-     * Test that display overrides apply correctly and won't be affected by display changes.
-     * This sets overrides to display size and density, initiates a display changed event by locking
-     * and unlocking the phone and verifies that overrides are kept.
-     */
-    @Presubmit
-    public void testForceDisplayMetrics() throws Exception {
-        launchHomeActivity();
-
-        // Read initial sizes.
-        final ReportedDisplayMetrics originalDisplayMetrics = getDisplayMetrics();
-
-        // Apply new override values that don't match the physical metrics.
-        final int overrideWidth = (int) (originalDisplayMetrics.physicalWidth * 1.5);
-        final int overrideHeight = (int) (originalDisplayMetrics.physicalHeight * 1.5);
-        executeShellCommand(WM_SIZE + " " + overrideWidth + "x" + overrideHeight);
-        final int overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
-        executeShellCommand(WM_DENSITY + " " + overrideDensity);
-
-        // Check if overrides applied correctly.
-        ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
-        assertEquals(overrideWidth, displayMetrics.overrideWidth);
-        assertEquals(overrideHeight, displayMetrics.overrideHeight);
-        assertEquals(overrideDensity, displayMetrics.overrideDensity);
-
-        // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
-        // might update the metrics.
-        sleepDevice();
-        wakeUpAndUnlockDevice();
-        mAmWmState.waitForHomeActivityVisible(mDevice);
-
-        // Check if overrides are still applied.
-        displayMetrics = getDisplayMetrics();
-        assertEquals(overrideWidth, displayMetrics.overrideWidth);
-        assertEquals(overrideHeight, displayMetrics.overrideHeight);
-        assertEquals(overrideDensity, displayMetrics.overrideDensity);
-
-        // All overrides will be cleared in tearDown.
-    }
-
-    /**
-     * Tests than an immediate launch after new display creation is handled correctly.
-     */
-    public void testImmediateLaunchOnNewDisplay() throws Exception {
-        if (!supportsMultiDisplay()) { return; }
-
-        // Create new virtual display and immediately launch an activity on it.
-        final DisplayState newDisplay = new VirtualDisplayBuilder(this)
-                .setLaunchActivity(TEST_ACTIVITY_NAME).build();
-
-        // Check that activity is launched and placed correctly.
-        mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
-        mAmWmState.assertResumedActivity("Test activity must be launched on a new display",
-                TEST_ACTIVITY_NAME);
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-        final ActivityManagerState.ActivityStack firstFrontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Activity launched on secondary display must be resumed",
-                getActivityComponentName(TEST_ACTIVITY_NAME), firstFrontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-    }
-
-    /**
-     * Tests that turning the primary display off does not affect the activity running
-     * on an external secondary display.
-     */
-    public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
-        // Launch something on the primary display so we know there is a resumed activity there
-        launchActivity(RESIZEABLE_ACTIVITY_NAME);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
-                "Activity launched on primary display must be resumed");
-
-        final DisplayState newDisplay = createExternalVirtualDisplay(
-                true /* showContentWhenLocked */);
-
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-        // Check that the activity is launched onto the external display
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-
-        setPrimaryDisplayState(false);
-
-        // Wait for the fullscreen stack to start sleeping, and then make sure the
-        // test activity is still resumed. Note that on some devices, the top activity may go to
-        // the stopped state by itself on sleep, causing the server side to believe it is still
-        // paused.
-        waitAndAssertActivityPausedOrStopped(RESIZEABLE_ACTIVITY_NAME,
-                "Activity launched on primary display must be stopped or paused after turning off");
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-    }
-
-    /**
-     * Tests that an activity can be launched on a secondary display while the primary
-     * display is off.
-     */
-    public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
-        // Launch something on the primary display so we know there is a resumed activity there
-        launchActivity(RESIZEABLE_ACTIVITY_NAME);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
-                "Activity launched on primary display must be resumed");
-
-        setPrimaryDisplayState(false);
-
-        // Make sure there is no resumed activity when the primary display is off
-        waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
-                "Activity launched on primary display must be stopped after turning off");
-        assertEquals("Unexpected resumed activity",
-                0, mAmWmState.getAmState().getResumedActivitiesCount());
-
-        final DisplayState newDisplay = createExternalVirtualDisplay(
-                true /* showContentWhenLocked */);
-
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-        // Check that the test activity is resumed on the external display
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-    }
-
-    /**
-     * Tests that turning the secondary display off stops activities running on that display.
-     */
-    public void testExternalDisplayToggleState() throws Exception {
-        final DisplayState newDisplay = createExternalVirtualDisplay(
-                false /* showContentWhenLocked */);
-
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-        // Check that the test activity is resumed on the external display
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-
-        mExternalDisplayHelper.turnDisplayOff();
-
-        // Check that turning off the external display stops the activity
-        waitAndAssertActivityStopped(TEST_ACTIVITY_NAME,
-                "Activity launched on external display must be stopped after turning off");
-
-        mExternalDisplayHelper.turnDisplayOn();
-
-        // Check that turning on the external display resumes the activity
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-    }
-
-    /**
-     * Tests that tapping on the primary display after showing the keyguard resumes the
-     * activity on the primary display.
-     */
-    public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
-        // Launch something on the primary display so we know there is a resumed activity there
-        launchActivity(RESIZEABLE_ACTIVITY_NAME);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
-                "Activity launched on primary display must be resumed");
-
-        sleepDevice();
-
-        // Make sure there is no resumed activity when the primary display is off
-        waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
-                "Activity launched on primary display must be stopped after turning off");
-        assertEquals("Unexpected resumed activity",
-                0, mAmWmState.getAmState().getResumedActivitiesCount());
-
-        final DisplayState newDisplay = createExternalVirtualDisplay(
-                true /* showContentWhenLocked */);
-
-        launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-        // Check that the test activity is resumed on the external display
-        waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
-                "Activity launched on external display must be resumed");
-
-        // Unlock the device and tap on the middle of the primary display
-        wakeUpDevice();
-        executeShellCommand("wm dismiss-keyguard");
-        final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
-        final int width = displayMetrics.getWidth();
-        final int height = displayMetrics.getHeight();
-        executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
-
-        // Check that the activity on the primary display is resumed
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
-                "Activity launched on primary display must be resumed");
-        assertEquals("Unexpected resumed activity",
-                1, mAmWmState.getAmState().getResumedActivitiesCount());
-    }
-
-    private void waitAndAssertActivityResumed(String activityName, int displayId, String message)
-            throws Exception {
-        mAmWmState.waitForActivityState(mDevice, activityName, STATE_RESUMED);
-
-        final String fullActivityName = getActivityComponentName(activityName);
-        assertEquals(message, fullActivityName, mAmWmState.getAmState().getResumedActivity());
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
-        ActivityManagerState.ActivityStack firstFrontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals(message, fullActivityName, 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(String activityName, String message)
-            throws Exception {
-        waitAndAssertActivityState(activityName, message, STATE_STOPPED);
-    }
-
-    private void waitAndAssertActivityPausedOrStopped(String activityName, String message)
-            throws Exception {
-        waitAndAssertActivityState(activityName, message, STATE_PAUSED, STATE_STOPPED);
-    }
-
-    private void waitAndAssertActivityState(String activityName, String message, String... states)
-            throws Exception {
-        mAmWmState.waitForActivityState(mDevice, activityName, states);
-
-        boolean stateFound = false;
-
-        for (String state : states) {
-            if (mAmWmState.getAmState().hasActivityState(activityName, state)) {
-                stateFound = true;
-                break;
-            }
-        }
-
-        assertTrue(message, stateFound);
-    }
-
-    /**
-     * Tests that showWhenLocked works on a secondary display.
-     */
-    public void testSecondaryDisplayShowWhenLocked() throws Exception {
-        try {
-            setLockCredential();
-
-            launchActivity(TEST_ACTIVITY_NAME);
-
-            final DisplayState newDisplay = createExternalVirtualDisplay(
-                    false /* showContentWhenLocked */);
-            launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, newDisplay.mDisplayId);
-
-            gotoKeyguard();
-            mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-
-            mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_STOPPED);
-            mAmWmState.waitForActivityState(
-                    mDevice, SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED);
-
-            mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME });
-            assertTrue("Expected resumed activity on secondary display", mAmWmState.getAmState()
-                    .hasActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED));
-        } finally {
-            tearDownLockCredentials();
-        }
-    }
-
-    /** Get physical and override display metrics from WM. */
-    private ReportedDisplayMetrics getDisplayMetrics() throws Exception {
-        mDumpLines.clear();
-        final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        mDevice.executeShellCommand(WM_SIZE, outputReceiver);
-        mDevice.executeShellCommand(WM_DENSITY, outputReceiver);
-        final String dump = outputReceiver.getOutput();
-        mDumpLines.clear();
-        Collections.addAll(mDumpLines, dump.split("\\n"));
-        return ReportedDisplayMetrics.create(mDumpLines);
-    }
-
-    private static class ReportedDisplayMetrics {
-        private static final Pattern sPhysicalSizePattern =
-                Pattern.compile("Physical size: (\\d+)x(\\d+)");
-        private static final Pattern sOverrideSizePattern =
-                Pattern.compile("Override size: (\\d+)x(\\d+)");
-        private static final Pattern sPhysicalDensityPattern =
-                Pattern.compile("Physical density: (\\d+)");
-        private static final Pattern sOverrideDensityPattern =
-                Pattern.compile("Override density: (\\d+)");
-
-        int physicalWidth;
-        int physicalHeight;
-        int physicalDensity;
-
-        boolean sizeOverrideSet;
-        int overrideWidth;
-        int overrideHeight;
-        boolean densityOverrideSet;
-        int overrideDensity;
-
-        /** Get width that WM operates with. */
-        int getWidth() {
-            return sizeOverrideSet ? overrideWidth : physicalWidth;
-        }
-
-        /** Get height that WM operates with. */
-        int getHeight() {
-            return sizeOverrideSet ? overrideHeight : physicalHeight;
-        }
-
-        /** Get density that WM operates with. */
-        int getDensity() {
-            return densityOverrideSet ? overrideDensity : physicalDensity;
-        }
-
-        static ReportedDisplayMetrics create(LinkedList<String> dump) {
-            final ReportedDisplayMetrics result = new ReportedDisplayMetrics();
-
-            boolean physicalSizeFound = false;
-            boolean physicalDensityFound = false;
-
-            while (!dump.isEmpty()) {
-                final String line = dump.pop().trim();
-
-                Matcher matcher = sPhysicalSizePattern.matcher(line);
-                if (matcher.matches()) {
-                    physicalSizeFound = true;
-                    log(line);
-                    result.physicalWidth = Integer.parseInt(matcher.group(1));
-                    result.physicalHeight = Integer.parseInt(matcher.group(2));
-                    continue;
-                }
-
-                matcher = sOverrideSizePattern.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    result.overrideWidth = Integer.parseInt(matcher.group(1));
-                    result.overrideHeight = Integer.parseInt(matcher.group(2));
-                    result.sizeOverrideSet = true;
-                    continue;
-                }
-
-                matcher = sPhysicalDensityPattern.matcher(line);
-                if (matcher.matches()) {
-                    physicalDensityFound = true;
-                    log(line);
-                    result.physicalDensity = Integer.parseInt(matcher.group(1));
-                    continue;
-                }
-
-                matcher = sOverrideDensityPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    result.overrideDensity = Integer.parseInt(matcher.group(1));
-                    result.densityOverrideSet = true;
-                    continue;
-                }
-            }
-
-            assertTrue("Physical display size must be reported", physicalSizeFound);
-            assertTrue("Physical display density must be reported", physicalDensityFound);
-
-            return result;
-        }
-    }
-
-    /** Assert that component received onMovedToDisplay and onConfigurationChanged callbacks. */
-    private void assertMovedToDisplay(String componentName, String 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);
-        }
-    }
-
-    private static String getResizeVirtualDisplayCommand() {
-        return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
-                " --es command resize_display";
-    }
-
-    /**
-     * Creates a private virtual display with the external and show with insecure
-     * keyguard flags set.
-     */
-    private DisplayState createExternalVirtualDisplay(boolean showContentWhenLocked)
-            throws Exception {
-        final ReportedDisplays originalDS = getDisplaysStates();
-        final int originalDisplayCount = originalDS.getNumberOfDisplays();
-
-        mExternalDisplayHelper = new DisplayHelper(getDevice());
-        mExternalDisplayHelper.createAndWaitForDisplay(true /* external */, showContentWhenLocked);
-
-        // Wait for the virtual display to be created and get configurations.
-        final ReportedDisplays ds =
-                getDisplayStateAfterChange(originalDisplayCount + 1);
-        assertEquals("New virtual display must be created",
-                originalDisplayCount + 1, ds.getNumberOfDisplays());
-
-        // Find the newly added display.
-        final List<DisplayState> newDisplays = findNewDisplayStates(originalDS, ds);
-        return newDisplays.get(0);
-    }
-
-    /** Turns the primary display on/off by pressing the power key */
-    private void setPrimaryDisplayState(boolean wantOn) throws DeviceNotAvailableException {
-        // Either KeyEvent.KEYCODE_WAKEUP or KeyEvent.KEYCODE_SLEEP
-        int keycode = wantOn ? 224 : 223;
-        getDevice().executeShellCommand("input keyevent " + keycode);
-        DisplayHelper.waitForDefaultDisplayState(getDevice(), wantOn);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
deleted file mode 100644
index cf3540c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.awt.Rectangle;
-
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDockedStackTests
- */
-public class ActivityManagerDockedStackTests extends ActivityManagerTestBase {
-
-    private static final String TEST_ACTIVITY_NAME = "TestActivity";
-    private static final String FINISHABLE_ACTIVITY_NAME = "FinishableActivity";
-    private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
-    private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
-    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
-    private static final String SINGLE_INSTANCE_ACTIVITY_NAME = "SingleInstanceActivity";
-    private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
-
-    private static final int TASK_SIZE = 600;
-    private static final int STACK_SIZE = 300;
-
-    public void testMinimumDeviceSize() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        mAmWmState.assertDeviceDefaultDisplaySize(mDevice,
-                "Devices supporting multi-window must be larger than the default minimum"
-                        + " task size");
-    }
-
-    public void testStackList() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivity(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
-    }
-
-    public void testDockActivity() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-    }
-
-    public void testNonResizeableNotDocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(NON_RESIZEABLE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
-        mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
-        mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
-        mAmWmState.assertFrontStack(
-                "Fullscreen stack must be front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    public void testLaunchToSide() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-    }
-
-    public void testLaunchToSideMultiWindowCallbacks() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        // Launch two activities, one docked, one adjacent
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
-        // Remove the docked stack, and ensure that
-        final String logSeparator = clearLogcat();
-        removeStacks(DOCKED_STACK_ID);
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                TEST_ACTIVITY_NAME, logSeparator);
-        if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
-            fail(TEST_ACTIVITY_NAME + " has received "
-                    + lifecycleCounts.mMultiWindowModeChangedCount
-                    + " onMultiWindowModeChanged() calls, expecting 1");
-        }
-    }
-
-    public void testLaunchToSideAndBringToFront() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        final String[] waitForFirstVisible = new String[] {TEST_ACTIVITY_NAME};
-        final String[] waitForSecondVisible = new String[] {NO_RELAUNCH_ACTIVITY_NAME};
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        // Launch activity to side.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, waitForFirstVisible);
-        int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY_NAME);
-
-        // Launch another activity to side to cover first one.
-        launchActivityInStack(NO_RELAUNCH_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.computeState(mDevice, waitForSecondVisible);
-        int taskNumberCovered = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertEquals("Fullscreen stack must have one task added.",
-                taskNumberInitial + 1, taskNumberCovered);
-        mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                NO_RELAUNCH_ACTIVITY_NAME);
-
-        // Launch activity that was first launched to side. It should be brought to front.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, waitForFirstVisible);
-        int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertEquals("Task number in fullscreen stack must remain the same.",
-                taskNumberCovered, taskNumberFinal);
-        mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                TEST_ACTIVITY_NAME);
-    }
-
-    public void testLaunchToSideMultiple() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        final String[] waitForActivitiesVisible =
-            new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
-
-        // Launch activity to side.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-
-        // Try to launch to side same activity again.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
-        mAmWmState.assertFocusedActivity("Launched to side activity must remain in front.",
-                TEST_ACTIVITY_NAME);
-        mAmWmState.assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-    }
-
-    public void testLaunchToSideSingleInstance() throws Exception {
-        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY_NAME, false);
-    }
-
-    public void testLaunchToSideSingleTask() throws Exception {
-        launchTargetToSide(SINGLE_TASK_ACTIVITY_NAME, false);
-    }
-
-    public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
-        launchTargetToSide(TEST_ACTIVITY_NAME, true);
-    }
-
-    private void launchTargetToSide(String targetActivityName,
-                                    boolean taskCountMustIncrement) throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
-        final String[] waitForActivitiesVisible =
-            new String[] {targetActivityName, LAUNCHING_ACTIVITY};
-
-        // Launch activity to side with data.
-        launchActivityToSide(true, false, targetActivityName);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
-
-        // Try to launch to side same activity again with different data.
-        launchActivityToSide(true, false, targetActivityName);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberSecondLaunch = mAmWmState.getAmState()
-                .getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTasks().size();
-        if (taskCountMustIncrement) {
-            mAmWmState.assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                    taskNumberSecondLaunch);
-        } else {
-            mAmWmState.assertEquals("Task number must not change.", taskNumberInitial,
-                    taskNumberSecondLaunch);
-        }
-        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        mAmWmState.assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
-
-        // Try to launch to side same activity again with no data.
-        launchActivityToSide(false, false, targetActivityName);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        if (taskCountMustIncrement) {
-            mAmWmState.assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
-                    taskNumberFinal);
-        } else {
-            mAmWmState.assertEquals("Task number must not change.", taskNumberSecondLaunch,
-                    taskNumberFinal);
-        }
-        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        mAmWmState.assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
-    }
-
-    public void testLaunchToSideMultipleWithFlag() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        final String[] waitForActivitiesVisible =
-            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
-
-        // Launch activity to side.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-
-        // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
-        getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
-                .getTasks().size();
-        mAmWmState.assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                taskNumberFinal);
-        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY_NAME);
-        mAmWmState.assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mAmWmState.getAmState()
-                        .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-    }
-
-    public void testRotationWhenDocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
-        // Rotate device single steps (90°) 0-1-2-3.
-        // Each time we compute the state we implicitly assert valid bounds.
-        String[] waitForActivitiesVisible =
-            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
-        for (int i = 0; i < 4; i++) {
-            setDeviceRotation(i);
-            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        }
-        // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
-        // step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
-        setDeviceRotation(1);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(3);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(0);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(2);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(0);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-    }
-
-    public void testRotationWhenDockedWhileLocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertSanity();
-        mAmWmState.assertContainsStack(
-                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
-        String[] waitForActivitiesVisible =
-            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
-        for (int i = 0; i < 4; i++) {
-            sleepDevice();
-            setDeviceRotation(i);
-            wakeUpAndUnlockDevice();
-            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        }
-    }
-
-    public void testRotationWhileDockMinimized() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
-        assertDockMinimized();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-        mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
-                HOME_STACK_ID);
-
-        // Rotate device single steps (90°) 0-1-2-3.
-        // Each time we compute the state we implicitly assert valid bounds in minimized mode.
-        String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
-        for (int i = 0; i < 4; i++) {
-            setDeviceRotation(i);
-            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        }
-
-        // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
-        // step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in minimized
-        // mode.
-        setDeviceRotation(1);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(3);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(0);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(2);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        setDeviceRotation(0);
-        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-    }
-
-    public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        // Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
-        // going home has the correct app transition
-        for (int i = 0; i < 4; i++) {
-            setDeviceRotation(i);
-            launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY_NAME);
-            assertDockMinimized();
-
-            // Unminimize the docked stack
-            pressAppSwitchButton();
-            waitForDockNotMinimized();
-            assertDockNotMinimized();
-
-            // Dismiss the dock stack
-            launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-            moveActivityToStack(DOCKED_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-            mAmWmState.computeState(mDevice, new String[]{DOCKED_ACTIVITY_NAME});
-
-            // Go home and check the app transition
-            assertNotSame(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
-            pressHomeButton();
-            mAmWmState.computeState(mDevice, null);
-            assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
-        }
-    }
-
-    public void testFinishDockActivityWhileMinimized() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStackAndMinimize(FINISHABLE_ACTIVITY_NAME);
-        assertDockMinimized();
-
-        runCommandAndPrintOutput("am broadcast -a 'android.server.cts.FinishableActivity.finish'");
-        waitForDockNotMinimized();
-        mAmWmState.assertVisibility(FINISHABLE_ACTIVITY_NAME, false);
-        assertDockNotMinimized();
-    }
-
-    public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        sleepDevice();
-        wakeUpAndUnlockDevice();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        assertDockMinimized();
-    }
-
-    public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStackAndMinimize(FINISHABLE_ACTIVITY_NAME);
-        assertDockMinimized();
-
-        sleepDevice();
-        wakeUpAndUnlockDevice();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
-        // Unminimized back to splitscreen
-        pressAppSwitchButton();
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-    }
-
-    public void testResizeDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
-        launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-        resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
-        mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME},
-                false /* compareTaskAndStackBounds */);
-        mAmWmState.assertContainsStack("Must contain docked stack", DOCKED_STACK_ID);
-        mAmWmState.assertContainsStack("Must contain fullscreen stack",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        assertEquals(new Rectangle(0, 0, STACK_SIZE, STACK_SIZE),
-                mAmWmState.getAmState().getStackById(DOCKED_STACK_ID).getBounds());
-        mAmWmState.assertDockedTaskBounds(TASK_SIZE, TASK_SIZE, DOCKED_ACTIVITY_NAME);
-        mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
-    }
-
-    public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        final String[] waitTestActivityName = new String[] {TEST_ACTIVITY_NAME};
-        launchActivity(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, waitTestActivityName);
-        final Rectangle fullScreenBounds =
-                mAmWmState.getWmState().getStack(FULLSCREEN_WORKSPACE_STACK_ID).getBounds();
-
-        moveActivityToDockStack(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, waitTestActivityName);
-        launchActivityInStack(NO_RELAUNCH_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-
-        mAmWmState.computeState(mDevice,
-                new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
-        final Rectangle initialDockBounds =
-                mAmWmState.getWmState().getStack(DOCKED_STACK_ID).getBounds();
-
-        final String logSeparator = clearLogcat();
-
-        Rectangle newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
-        resizeDockedStack(newBounds.width, newBounds.height, newBounds.width, newBounds.height);
-        mAmWmState.computeState(mDevice,
-                new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
-
-        // We resize twice to make sure we cross an orientation change threshold for both
-        // activities.
-        newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, false);
-        resizeDockedStack(newBounds.width, newBounds.height, newBounds.width, newBounds.height);
-        mAmWmState.computeState(mDevice,
-                new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
-        assertActivityLifecycle(TEST_ACTIVITY_NAME, true /* relaunched */, logSeparator);
-        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY_NAME, false /* relaunched */, logSeparator);
-    }
-
-    private Rectangle computeNewDockBounds(
-            Rectangle fullscreenBounds, Rectangle dockBounds, boolean reduceSize) {
-        final boolean inLandscape = fullscreenBounds.width > dockBounds.width;
-        // We are either increasing size or reducing it.
-        final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
-        final Rectangle newBounds = new Rectangle(dockBounds);
-        if (inLandscape) {
-            // In landscape we change the width.
-            newBounds.width *= sizeChangeFactor;
-        } else {
-            // In portrait we change the height
-            newBounds.height *= sizeChangeFactor;
-        }
-
-        return newBounds;
-    }
-
-    public void testStackListOrderLaunchDockedActivity() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-
-        launchActivityInDockStack(TEST_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY_NAME});
-
-        final int homeStackIndex = mAmWmState.getStackPosition(HOME_STACK_ID);
-        final int recentsStackIndex = mAmWmState.getStackPosition(RECENTS_STACK_ID);
-        assertTrue("Recents stack should be on top of home stack",
-                recentsStackIndex < homeStackIndex);
-    }
-
-    private void launchActivityInDockStackAndMinimize(String activityName) throws Exception {
-        launchActivityInDockStack(activityName);
-        pressHomeButton();
-        waitForDockMinimized();
-    }
-
-    private void assertDockMinimized() {
-        assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
-    }
-
-    private void assertDockNotMinimized() {
-        assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
-    }
-
-    private void waitForDockMinimized() throws Exception {
-        mAmWmState.waitForWithWmState(mDevice, state -> state.isDockedStackMinimized(),
-                "***Waiting for Dock stack to be minimized");
-    }
-
-    private void waitForDockNotMinimized() throws Exception {
-        mAmWmState.waitForWithWmState(mDevice, state -> !state.isDockedStackMinimized(),
-                "***Waiting for Dock stack to not be minimized");
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
deleted file mode 100644
index a1d76c8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerFreeformStackTests
- */
-public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
-
-    private static final String TEST_ACTIVITY = "TestActivity";
-    private static final int TEST_TASK_OFFSET = 20;
-    private static final int TEST_TASK_OFFSET_2 = 100;
-    private static final int TEST_TASK_SIZE_1 = 900;
-    private static final int TEST_TASK_SIZE_2 = TEST_TASK_SIZE_1 * 2;
-    private static final int TEST_TASK_SIZE_DP_1 = 220;
-    private static final int TEST_TASK_SIZE_DP_2 = TEST_TASK_SIZE_DP_1 * 2;
-
-    // NOTE: Launching the FreeformActivity will automatically launch the TestActivity
-    // with bounds (0, 0, 900, 900)
-    private static final String FREEFORM_ACTIVITY = "FreeformActivity";
-    private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
-    private static final String NO_RELAUNCH_ACTIVITY = "NoRelaunchActivity";
-
-    public void testFreeformWindowManagementSupport() throws Exception {
-
-        launchActivityInStack(FREEFORM_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
-        mAmWmState.computeState(mDevice, new String[] {FREEFORM_ACTIVITY, TEST_ACTIVITY});
-
-        if (!supportsFreeform()) {
-            mAmWmState.assertDoesNotContainStack(
-                    "Must not contain freeform stack.", FREEFORM_WORKSPACE_STACK_ID);
-            return;
-        }
-
-        mAmWmState.assertFrontStack(
-                "Freeform stack must be the front stack.", FREEFORM_WORKSPACE_STACK_ID);
-        mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-        mAmWmState.assertFocusedActivity(
-                TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
-        assertEquals(new Rectangle(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
-                mAmWmState.getAmState().getTaskByActivityName(TEST_ACTIVITY).getBounds());
-    }
-
-    public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
-        launchActivityInStack(NON_RESIZEABLE_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY});
-
-        final ActivityTask task =
-                mAmWmState.getAmState().getTaskByActivityName(NON_RESIZEABLE_ACTIVITY);
-        final ActivityStack stack = mAmWmState.getAmState().getStackById(task.mStackId);
-
-        if (task.isFullscreen()) {
-            // If the task is on the fullscreen stack, then we know that it will have bounds that
-            // fill the entire display.
-            return;
-        }
-
-        // If the task is not on the fullscreen stack, then compare the task bounds to the display
-        // bounds.
-        assertEquals(mAmWmState.getWmState().getDisplay(stack.mDisplayId).getDisplayRect(),
-                task.getBounds());
-    }
-
-    public void testActivityLifeCycleOnResizeFreeformTask() throws Exception {
-        launchActivityInStack(TEST_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-        launchActivityInStack(NO_RELAUNCH_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
-        mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
-        if (!supportsFreeform()) {
-            mAmWmState.assertDoesNotContainStack(
-                    "Must not contain freeform stack.", FREEFORM_WORKSPACE_STACK_ID);
-            return;
-        }
-
-        final int displayId = mAmWmState.getAmState().getStackById(
-                ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID).mDisplayId;
-        final int densityDpi =
-                mAmWmState.getWmState().getDisplay(displayId).getDpi();
-        final int testTaskSize1 =
-                ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_1, densityDpi);
-        final int testTaskSize2 =
-                ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_2, densityDpi);
-
-        resizeActivityTask(TEST_ACTIVITY,
-                TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize1, testTaskSize2);
-        resizeActivityTask(NO_RELAUNCH_ACTIVITY,
-                TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize1, testTaskSize2);
-
-        mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
-        final String logSeparator = clearLogcat();
-        resizeActivityTask(TEST_ACTIVITY,
-                TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize2, testTaskSize1);
-        resizeActivityTask(NO_RELAUNCH_ACTIVITY,
-                TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize2, testTaskSize1);
-        mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
-        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
-        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
deleted file mode 100644
index e6df4b2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.Assert;
-
-import java.awt.Rectangle;
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.WindowManagerState.Display;
-
-import static android.server.cts.ActivityAndWindowManagersState.dpToPx;
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.log.LogUtil.CLog;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerManifestLayoutTests
- */
-public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
-
-    // Test parameters
-    private static final int DEFAULT_WIDTH_DP = 240;
-    private static final int DEFAULT_HEIGHT_DP = 160;
-    private static final float DEFAULT_WIDTH_FRACTION = 0.25f;
-    private static final float DEFAULT_HEIGHT_FRACTION = 0.35f;
-    private static final int MIN_WIDTH_DP = 100;
-    private static final int MIN_HEIGHT_DP = 80;
-
-    private static final int GRAVITY_VER_CENTER = 0x01;
-    private static final int GRAVITY_VER_TOP    = 0x02;
-    private static final int GRAVITY_VER_BOTTOM = 0x04;
-    private static final int GRAVITY_HOR_CENTER = 0x10;
-    private static final int GRAVITY_HOR_LEFT   = 0x20;
-    private static final int GRAVITY_HOR_RIGHT  = 0x40;
-
-    private List<WindowState> mTempWindowList = new ArrayList();
-    private Display mDisplay;
-    private WindowState mWindowState;
-
-    public void testGravityAndDefaultSizeTopLeft() throws Exception {
-        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/);
-    }
-
-    public void testGravityAndDefaultSizeTopRight() throws Exception {
-        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/);
-    }
-
-    public void testGravityAndDefaultSizeBottomLeft() throws Exception {
-        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/);
-    }
-
-    public void testGravityAndDefaultSizeBottomRight() throws Exception {
-        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/);
-    }
-
-    public void testMinimalSizeFreeform() throws Exception {
-        if (!supportsFreeform()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no freeform support");
-            return;
-        }
-        testMinimalSize(FREEFORM_WORKSPACE_STACK_ID);
-    }
-
-    public void testMinimalSizeDocked() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
-            return;
-        }
-        testMinimalSize(DOCKED_STACK_ID);
-    }
-
-    private void testMinimalSize(int stackId) throws Exception {
-        final String activityName = "BottomRightLayoutActivity";
-
-        // Issue command to resize to <0,0,1,1>. We expect the size to be floored at
-        // MIN_WIDTH_DPxMIN_HEIGHT_DP.
-        if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
-            launchActivityInStack(activityName, stackId);
-            resizeActivityTask(activityName, 0, 0, 1, 1);
-        } else { // stackId == DOCKED_STACK_ID
-            launchActivityInDockStack(activityName);
-            resizeDockedStack(1, 1, 1, 1);
-        }
-        getDisplayAndWindowState(activityName, false);
-
-        final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
-        final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
-        final Rectangle containingRect = mWindowState.getContainingFrame();
-
-        Assert.assertEquals("Min width is incorrect", minWidth, containingRect.width);
-        Assert.assertEquals("Min height is incorrect", minHeight, containingRect.height);
-    }
-
-    private void testLayout(
-            int vGravity, int hGravity, boolean fraction) throws Exception {
-        if (!supportsFreeform()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no freeform support");
-            return;
-        }
-
-        final String activityName = (vGravity == GRAVITY_VER_TOP ? "Top" : "Bottom")
-                + (hGravity == GRAVITY_HOR_LEFT ? "Left" : "Right") + "LayoutActivity";
-
-        // Launch in freeform stack
-        launchActivityInStack(activityName, FREEFORM_WORKSPACE_STACK_ID);
-
-        getDisplayAndWindowState(activityName, true);
-
-        final Rectangle containingRect = mWindowState.getContainingFrame();
-        final Rectangle appRect = mDisplay.getAppRect();
-        final int expectedWidthPx, expectedHeightPx;
-        // Evaluate the expected window size in px. If we're using fraction dimensions,
-        // calculate the size based on the app rect size. Otherwise, convert the expected
-        // size in dp to px.
-        if (fraction) {
-            expectedWidthPx = (int) (appRect.width * DEFAULT_WIDTH_FRACTION);
-            expectedHeightPx = (int) (appRect.height * DEFAULT_HEIGHT_FRACTION);
-        } else {
-            final int densityDpi = mDisplay.getDpi();
-            expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
-            expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
-        }
-
-        verifyFrameSizeAndPosition(
-                vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
-    }
-
-    private void getDisplayAndWindowState(String activityName, boolean checkFocus)
-            throws Exception {
-        final String windowName = getWindowName(activityName);
-
-        mAmWmState.computeState(mDevice, new String[] {activityName});
-
-        if (checkFocus) {
-            mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
-        } else {
-            mAmWmState.assertVisibility(activityName, true);
-        }
-
-        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mTempWindowList);
-
-        Assert.assertEquals("Should have exactly one window state for the activity.",
-                1, mTempWindowList.size());
-
-        mWindowState = mTempWindowList.get(0);
-        Assert.assertNotNull("Should have a valid window", mWindowState);
-
-        mDisplay = mAmWmState.getWmState().getDisplay(mWindowState.getDisplayId());
-        Assert.assertNotNull("Should be on a display", mDisplay);
-    }
-
-    private void verifyFrameSizeAndPosition(
-            int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
-            Rectangle containingFrame, Rectangle parentFrame) {
-        Assert.assertEquals("Width is incorrect", expectedWidthPx, containingFrame.width);
-        Assert.assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height);
-
-        if (vGravity == GRAVITY_VER_TOP) {
-            Assert.assertEquals("Should be on the top", parentFrame.y, containingFrame.y);
-        } else if (vGravity == GRAVITY_VER_BOTTOM) {
-            Assert.assertEquals("Should be on the bottom",
-                    parentFrame.y + parentFrame.height, containingFrame.y + containingFrame.height);
-        }
-
-        if (hGravity == GRAVITY_HOR_LEFT) {
-            Assert.assertEquals("Should be on the left", parentFrame.x, containingFrame.x);
-        } else if (hGravity == GRAVITY_HOR_RIGHT){
-            Assert.assertEquals("Should be on the right",
-                    parentFrame.x + parentFrame.width, containingFrame.x + containingFrame.width);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
deleted file mode 100644
index 5f63580..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ /dev/null
@@ -1,1338 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-
-import java.awt.Rectangle;
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests
- */
-public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
-    private static final String TEST_ACTIVITY = "TestActivity";
-    private static final String TEST_ACTIVITY_WITH_SAME_AFFINITY = "TestActivityWithSameAffinity";
-    private static final String TRANSLUCENT_TEST_ACTIVITY = "TranslucentTestActivity";
-    private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
-    private static final String RESUME_WHILE_PAUSING_ACTIVITY = "ResumeWhilePausingActivity";
-    private static final String PIP_ACTIVITY = "PipActivity";
-    private static final String PIP_ACTIVITY2 = "PipActivity2";
-    private static final String PIP_ACTIVITY_WITH_SAME_AFFINITY = "PipActivityWithSameAffinity";
-    private static final String ALWAYS_FOCUSABLE_PIP_ACTIVITY = "AlwaysFocusablePipActivity";
-    private static final String LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
-            "LaunchIntoPinnedStackPipActivity";
-    private static final String LAUNCH_ENTER_PIP_ACTIVITY = "LaunchEnterPipActivity";
-    private static final String PIP_ON_STOP_ACTIVITY = "PipOnStopActivity";
-
-    private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-    private static final String EXTRA_ENTER_PIP = "enter_pip";
-    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
-            "enter_pip_aspect_ratio_numerator";
-    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
-            "enter_pip_aspect_ratio_denominator";
-    private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
-    private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
-    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
-            "set_aspect_ratio_with_delay_numerator";
-    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
-            "set_aspect_ratio_with_delay_denominator";
-    private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
-    private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-    private static final String EXTRA_START_ACTIVITY = "start_activity";
-    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
-    private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
-    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
-    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
-
-    private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
-            "android.server.cts.PipActivity.enter_pip";
-    private static final String PIP_ACTIVITY_ACTION_MOVE_TO_BACK =
-            "android.server.cts.PipActivity.move_to_back";
-    private static final String PIP_ACTIVITY_ACTION_EXPAND_PIP =
-            "android.server.cts.PipActivity.expand_pip";
-    private static final String PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION =
-            "android.server.cts.PipActivity.set_requested_orientation";
-    private static final String PIP_ACTIVITY_ACTION_FINISH =
-            "android.server.cts.PipActivity.finish";
-    private static final String TEST_ACTIVITY_ACTION_FINISH =
-            "android.server.cts.TestActivity.finish_self";
-
-    private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
-    private static final int APP_OPS_MODE_ALLOWED = 0;
-    private static final int APP_OPS_MODE_IGNORED = 1;
-    private static final int APP_OPS_MODE_ERRORED = 2;
-
-    private static final int ROTATION_0 = 0;
-    private static final int ROTATION_90 = 1;
-    private static final int ROTATION_180 = 2;
-    private static final int ROTATION_270 = 3;
-
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-    private static final int ORIENTATION_LANDSCAPE = 0;
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-    private static final int ORIENTATION_PORTRAIT = 1;
-
-    private static final float FLOAT_COMPARE_EPSILON = 0.005f;
-
-    // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
-    private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
-    private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
-    private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
-    // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
-    private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
-    private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
-    private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
-
-    public void testMinimumDeviceSize() throws Exception {
-        if (!supportsPip()) return;
-
-        mAmWmState.assertDeviceDefaultDisplaySize(mDevice,
-                "Devices supporting picture-in-picture must be larger than the default minimum"
-                        + " task size");
-    }
-
-    public void testEnterPictureInPictureMode() throws Exception {
-        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"), PIP_ACTIVITY,
-                false /* moveTopToPinnedStack */, false /* isFocusable */);
-    }
-
-    public void testMoveTopActivityToPinnedStack() throws Exception {
-        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY,
-                true /* moveTopToPinnedStack */, false /* isFocusable */);
-    }
-
-    public void testAlwaysFocusablePipActivity() throws Exception {
-        pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
-                ALWAYS_FOCUSABLE_PIP_ACTIVITY, false /* moveTopToPinnedStack */,
-                true /* isFocusable */);
-    }
-
-    public void testLaunchIntoPinnedStack() throws Exception {
-        pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
-                ALWAYS_FOCUSABLE_PIP_ACTIVITY, false /* moveTopToPinnedStack */,
-                true /* isFocusable */);
-    }
-
-    public void testNonTappablePipActivity() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the tap-to-finish activity at a specific place
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_TAP_TO_FINISH, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-
-        // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
-        // not passed down to the top task
-        tapToFinishPip();
-        mAmWmState.computeState(mDevice, new String[] {PIP_ACTIVITY},
-                false /* compareTaskAndStackBounds */);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-    }
-
-    public void testPinnedStackDefaultBounds() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a PIP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
-        setDeviceRotation(ROTATION_0);
-        WindowManagerState wmState = mAmWmState.getWmState();
-        wmState.computeState(mDevice);
-        Rectangle defaultPipBounds = wmState.getDefaultPinnedStackBounds();
-        Rectangle stableBounds = wmState.getStableBounds();
-        assertTrue(defaultPipBounds.width > 0 && defaultPipBounds.height > 0);
-        assertTrue(stableBounds.contains(defaultPipBounds));
-
-        setDeviceRotation(ROTATION_90);
-        wmState = mAmWmState.getWmState();
-        wmState.computeState(mDevice);
-        defaultPipBounds = wmState.getDefaultPinnedStackBounds();
-        stableBounds = wmState.getStableBounds();
-        assertTrue(defaultPipBounds.width > 0 && defaultPipBounds.height > 0);
-        assertTrue(stableBounds.contains(defaultPipBounds));
-        setDeviceRotation(ROTATION_0);
-    }
-
-    public void testPinnedStackMovementBounds() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a PIP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
-        setDeviceRotation(ROTATION_0);
-        WindowManagerState wmState = mAmWmState.getWmState();
-        wmState.computeState(mDevice);
-        Rectangle pipMovementBounds = wmState.getPinnedStackMomentBounds();
-        Rectangle stableBounds = wmState.getStableBounds();
-        assertTrue(pipMovementBounds.width > 0 && pipMovementBounds.height > 0);
-        assertTrue(stableBounds.contains(pipMovementBounds));
-
-        setDeviceRotation(ROTATION_90);
-        wmState = mAmWmState.getWmState();
-        wmState.computeState(mDevice);
-        pipMovementBounds = wmState.getPinnedStackMomentBounds();
-        stableBounds = wmState.getStableBounds();
-        assertTrue(pipMovementBounds.width > 0 && pipMovementBounds.height > 0);
-        assertTrue(stableBounds.contains(pipMovementBounds));
-        setDeviceRotation(ROTATION_0);
-    }
-
-    public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
-        if (!supportsPip()) return;
-
-        final WindowManagerState wmState = mAmWmState.getWmState();
-
-        // Launch an activity into the pinned stack
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_TAP_TO_FINISH, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
-        // Get the display dimensions
-        WindowManagerState.WindowState windowState = getWindowState(PIP_ACTIVITY);
-        WindowManagerState.Display display = wmState.getDisplay(windowState.getDisplayId());
-        Rectangle displayRect = display.getDisplayRect();
-
-        // Move the pinned stack offscreen
-        String moveStackOffscreenCommand = String.format("am stack resize 4 %d %d %d %d",
-                displayRect.width - 200, 0, displayRect.width + 200, 500);
-        executeShellCommand(moveStackOffscreenCommand);
-
-        // Ensure that the surface insets are not negative
-        windowState = getWindowState(PIP_ACTIVITY);
-        Rectangle contentInsets = windowState.getContentInsets();
-        assertTrue(contentInsets.x >= 0 && contentInsets.y >= 0 && contentInsets.width >= 0 &&
-                contentInsets.height >= 0);
-    }
-
-    public void testPinnedStackInBoundsAfterRotation() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch an activity into the pinned stack
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_TAP_TO_FINISH, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
-        // Ensure that the PIP stack is fully visible in each orientation
-        setDeviceRotation(ROTATION_0);
-        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
-        setDeviceRotation(ROTATION_90);
-        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
-        setDeviceRotation(ROTATION_180);
-        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
-        setDeviceRotation(ROTATION_270);
-        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
-        setDeviceRotation(ROTATION_0);
-    }
-
-    public void testEnterPipToOtherOrientation() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a portrait only app on the fullscreen stack
-        launchActivity(TEST_ACTIVITY,
-                EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
-        // Launch the PiP activity fixed as landscape
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_FIXED_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 " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
-    }
-
-    public void testEnterPipAspectRatioMin() throws Exception {
-        testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    public void testEnterPipAspectRatioMax() throws Exception {
-        testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    private void testEnterPipAspectRatio(int num, int denom) throws Exception {
-        if (!supportsPip()) return;
-
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
-        assertPinnedStackExists();
-
-        // Assert that we have entered PIP and that the aspect ratio is correct
-        Rectangle pinnedStackBounds =
-                mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
-        assertTrue(floatEquals((float) pinnedStackBounds.width / pinnedStackBounds.height,
-                (float) num / denom));
-    }
-
-    public void testResizePipAspectRatioMin() throws Exception {
-        testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    public void testResizePipAspectRatioMax() throws Exception {
-        testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    private void testResizePipAspectRatio(int num, int denom) throws Exception {
-        if (!supportsPip()) return;
-
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
-        assertPinnedStackExists();
-
-        // Hacky, but we need to wait for the enterPictureInPicture animation to complete and
-        // the resize to be called before we can check the pinned stack bounds
-        final boolean[] result = new boolean[1];
-        mAmWmState.waitForWithAmState(mDevice, (state) -> {
-            Rectangle pinnedStackBounds = state.getStackById(PINNED_STACK_ID).getBounds();
-            boolean isValidAspectRatio = floatEquals(
-                    (float) pinnedStackBounds.width / pinnedStackBounds.height,
-                    (float) num / denom);
-            result[0] = isValidAspectRatio;
-            return isValidAspectRatio;
-        }, "Waiting for pinned stack to be resized");
-        assertTrue(result[0]);
-    }
-
-    public void testEnterPipExtremeAspectRatioMin() throws Exception {
-        testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
-                BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    public void testEnterPipExtremeAspectRatioMax() throws Exception {
-        testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
-                MAX_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
-        if (!supportsPip()) return;
-
-        // Assert that we could not create a pinned stack with an extreme aspect ratio
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testSetPipExtremeAspectRatioMin() throws Exception {
-        testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
-                BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    public void testSetPipExtremeAspectRatioMax() throws Exception {
-        testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
-                MAX_ASPECT_RATIO_DENOMINATOR);
-    }
-
-    private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
-        if (!supportsPip()) return;
-
-        // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
-        // fails (the aspect ratio remains the same)
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
-                        Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
-                        Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
-        assertPinnedStackExists();
-        Rectangle pinnedStackBounds =
-                mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
-        assertTrue(floatEquals((float) pinnedStackBounds.width / pinnedStackBounds.height,
-                (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR));
-    }
-
-    public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the bottom pip activity
-        launchActivity(PIP_ON_STOP_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
-        // Wait for the bottom pip activity to be stopped
-        mAmWmState.waitForActivityState(mDevice, PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
-
-        // Assert that there is no pinned stack (that enterPictureInPicture() failed)
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testAutoEnterPictureInPicture() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a test activity so that we're not over home
-        launchActivity(TEST_ACTIVITY);
-
-        // Launch the PIP activity on pause
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
-        assertPinnedStackDoesNotExist();
-
-        // Go home and ensure that there is a pinned stack
-        launchHomeActivity();
-        assertPinnedStackExists();
-    }
-
-    public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a test activity so that we're not over home
-        launchActivity(TEST_ACTIVITY);
-
-        // Launch the PIP activity on pause, and have it start another activity on
-        // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
-        // was not created in the process
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_START_ACTIVITY, getActivityComponentName(NON_RESIZEABLE_ACTIVITY));
-        mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY},
-                false /* compareTaskAndStackBounds */);
-        assertPinnedStackDoesNotExist();
-
-        // Go home while the pip activity is open and ensure the previous activity is not PIPed
-        launchHomeActivity();
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testAutoEnterPictureInPictureFinish() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a test activity so that we're not over home
-        launchActivity(TEST_ACTIVITY);
-
-        // Launch the PIP activity on pause, and set it to finish itself after
-        // some period.  Wait for the previous activity to be visible, and ensure that the pinned
-        // stack was not created in the process
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the PIP activity on pause, and set the aspect ratio
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
-
-        // Go home while the pip activity is open to trigger auto-PIP
-        launchHomeActivity();
-        assertPinnedStackExists();
-
-        // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
-        // and before we can check the pinned stack bounds
-        final boolean[] result = new boolean[1];
-        mAmWmState.waitForWithAmState(mDevice, (state) -> {
-            Rectangle pinnedStackBounds = state.getStackById(PINNED_STACK_ID).getBounds();
-            boolean isValidAspectRatio = floatEquals(
-                    (float) pinnedStackBounds.width / pinnedStackBounds.height,
-                    (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
-            result[0] = isValidAspectRatio;
-            return isValidAspectRatio;
-        }, "Waiting for pinned stack to be resized");
-        assertTrue(result[0]);
-    }
-
-    public void testAutoEnterPictureInPictureOverPip() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch another PIP activity
-        launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-
-        // Launch the PIP activity on pause
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
-
-        // Go home while the PIP activity is open to trigger auto-enter PIP
-        launchHomeActivity();
-        assertPinnedStackExists();
-
-        // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
-        // still the first activity
-        final ActivityStack pinnedStack = mAmWmState.getAmState().getStackById(PINNED_STACK_ID);
-        assertTrue(pinnedStack.getTasks().size() == 1);
-        assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
-                ALWAYS_FOCUSABLE_PIP_ACTIVITY)));
-    }
-
-    public void testDisallowMultipleTasksInPinnedStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a test activity so that we have multiple fullscreen tasks
-        launchActivity(TEST_ACTIVITY);
-
-        // Launch first PIP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
-        // Launch second PIP activity
-        launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
-
-        final ActivityStack pinnedStack = mAmWmState.getAmState().getStackById(PINNED_STACK_ID);
-        assertEquals(1, pinnedStack.getTasks().size());
-
-        assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
-                PIP_ACTIVITY2)));
-
-        final ActivityStack fullScreenStack = mAmWmState.getAmState().getStackById(
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        assertTrue(fullScreenStack.getBottomTask().mRealActivity.equals(getActivityComponentName(
-                PIP_ACTIVITY)));
-    }
-
-    public void testPipUnPipOverHome() throws Exception {
-        if (!supportsPip()) return;
-
-        // Go home
-        launchHomeActivity();
-        // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_REENTER_PIP_ON_EXIT, "true");
-        assertPinnedStackExists();
-
-        // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
-        launchActivity(PIP_ACTIVITY);
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
-        }, "Waiting for PIP to exit to fullscreen");
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == PINNED_STACK_ID;
-        }, "Waiting to re-enter PIP");
-        mAmWmState.assertFocusedStack("Expected home stack focused", HOME_STACK_ID);
-    }
-
-    public void testPipUnPipOverApp() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a test activity so that we're not over home
-        launchActivity(TEST_ACTIVITY);
-
-        // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_REENTER_PIP_ON_EXIT, "true");
-        assertPinnedStackExists();
-
-        // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
-        launchActivity(PIP_ACTIVITY);
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
-        }, "Waiting for PIP to exit to fullscreen");
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == PINNED_STACK_ID;
-        }, "Waiting to re-enter PIP");
-        mAmWmState.assertFocusedStack("Expected fullscreen stack focused",
-                FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    public void testRemovePipWithNoFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Start with a clean slate, remove all the stacks but home
-        removeStacks(ALL_STACK_IDS_BUT_HOME);
-
-        // Launch a pip activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Remove the stack and ensure that the task is now in the fullscreen stack (when no
-        // fullscreen stack existed before)
-        removeStacks(PINNED_STACK_ID);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
-                true /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testRemovePipWithVisibleFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity, and a pip activity over that
-        launchActivity(TEST_ACTIVITY);
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
-        // top fullscreen activity
-        removeStacks(PINNED_STACK_ID);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
-                false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testRemovePipWithHiddenFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
-        // launch a pip activity over home
-        launchActivity(TEST_ACTIVITY);
-        launchHomeActivity();
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // 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
-        removeStacks(PINNED_STACK_ID);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
-                false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testMovePipToBackWithNoFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Start with a clean slate, remove all the stacks but home
-        removeStacks(ALL_STACK_IDS_BUT_HOME);
-
-        // Launch a pip activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Remove the stack and ensure that the task is now in the fullscreen stack (when no
-        // fullscreen stack existed before)
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
-                false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity, and a pip activity over that
-        launchActivity(TEST_ACTIVITY);
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
-        // top fullscreen activity
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
-                false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
-        // launch a pip activity over home
-        launchActivity(TEST_ACTIVITY);
-        launchHomeActivity();
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // 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 " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
-        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
-                false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
-    }
-
-    public void testPinnedStackAlwaysOnTop() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch activity into pinned stack and assert it's on top.
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-        assertPinnedStackIsOnTop();
-
-        // Launch another activity in fullscreen stack and check that pinned stack is still on top.
-        launchActivity(TEST_ACTIVITY);
-        assertPinnedStackExists();
-        assertPinnedStackIsOnTop();
-
-        // Launch home and check that pinned stack is still on top.
-        launchHomeActivity();
-        assertPinnedStackExists();
-        assertPinnedStackIsOnTop();
-    }
-
-    public void testAppOpsDenyPipOnPause() throws Exception {
-        if (!supportsPip()) return;
-
-        // Disable enter-pip and try to enter pip
-        setAppOpsOpToMode(ActivityManagerTestBase.componentName,
-                APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
-
-        // Launch the PIP activity on pause
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackDoesNotExist();
-
-        // Go home and ensure that there is no pinned stack
-        launchHomeActivity();
-        assertPinnedStackDoesNotExist();
-
-        // Re-enable enter-pip-on-hide
-        setAppOpsOpToMode(ActivityManagerTestBase.componentName,
-                APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_ALLOWED);
-    }
-
-    public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
-        if (!supportsPip()) return;
-
-        // Try to enter picture-in-picture from an activity that has more than one activity in the
-        // task and ensure that it works
-        launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-    }
-
-    public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
-        if (!supportsPip()) return;
-
-        /*
-         * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
-         * stopped and actually went into the pinned stack.
-         *
-         * Note that this is a workaround because to trigger the path that we want to happen in
-         * activity manager, we need to add the leaving activity to the stopping state, which only
-         * happens when a hidden stack is brought forward. Normally, this happens when you go home,
-         * 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
-         *    fullscreen stack
-         * 4) Bring the activity in the dynamic stack forward to trigger PiP
-         */
-        int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
-        resizeStack(stackId, 0, 0, 500, 500);
-        // 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)
-        launchActivityInStack(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_ON_PAUSE_DELAY, "350",
-                EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
-        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
-        assertPinnedStackExists();
-    }
-
-    public void testDisallowEnterPipActivityLocked() throws Exception {
-        if (!supportsPip()) return;
-
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
-        ActivityTask task =
-                mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
-
-        // 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 " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackDoesNotExist();
-        launchHomeActivity();
-        assertPinnedStackDoesNotExist();
-        executeShellCommand("am task lock stop");
-    }
-
-    public void testConfigurationChangeOrderDuringTransition() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a PiP activity and ensure configuration change only happened once, and that the
-        // configuration change happened after the picture-in-picture and multi-window callbacks
-        launchActivity(PIP_ACTIVITY);
-        String logSeparator = clearLogcat();
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
-        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
-
-        // Trigger it to go back to fullscreen and ensure that only triggered one configuration
-        // change as well
-        logSeparator = clearLogcat();
-        launchActivity(PIP_ACTIVITY);
-        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
-        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
-    }
-
-    public void testEnterPipInterruptedCallbacks() throws Exception {
-        if (!supportsPip()) return;
-
-        // Slow down the transition animations for this test
-        setWindowTransitionAnimationDurationScale(20);
-
-        // Launch a PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        // Wait until the PiP activity has moved into the pinned stack (happens before the
-        // transition has started)
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-
-        // Relaunch the PiP activity back into fullscreen
-        String logSeparator = clearLogcat();
-        launchActivity(PIP_ACTIVITY);
-        // Wait until the PiP activity is reparented into the fullscreen stack (happens after the
-        // transition has finished)
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-
-        // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
-        // configuration change (since none was sent)
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                PIP_ACTIVITY, logSeparator);
-        assertTrue(lifecycleCounts.mConfigurationChangedCount == 0);
-        assertTrue(lifecycleCounts.mPictureInPictureModeChangedCount == 1);
-        assertTrue(lifecycleCounts.mMultiWindowModeChangedCount == 1);
-
-        // Reset the animation scale
-        setWindowTransitionAnimationDurationScale(1);
-    }
-
-    public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Dismiss it
-        String logSeparator = clearLogcat();
-        removeStacks(PINNED_STACK_ID);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-
-        // Confirm that we get stop before the multi-window and picture-in-picture mode change
-        // callbacks
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
-                logSeparator);
-        if (lifecycleCounts.mStopCount != 1) {
-            fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mStopCount
-                    + " onStop() calls, expecting 1");
-        } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
-            fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
-                    + " onPictureInPictureModeChanged() calls, expecting 1");
-        } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
-            fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
-                    + " onMultiWindowModeChanged() calls, expecting 1");
-        } else {
-            int lastStopLine = lifecycleCounts.mLastStopLineIndex;
-            int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
-            int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
-            if (!(lastStopLine < lastPipLine && lastPipLine < lastMwLine)) {
-                fail(PIP_ACTIVITY + " has received callbacks in unexpected order.  Expected:"
-                        + " stop < pip < mw, but got line indices: " + lastStopLine + ", "
-                        + lastPipLine + ", " + lastMwLine + " respectively");
-            }
-        }
-    }
-
-    public void testPreventSetAspectRatioWhileExpanding() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
-        // 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 " + PIP_ACTIVITY_ACTION_EXPAND_PIP
-                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR + " 123456789"
-                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR + " 100000000");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testSetRequestedOrientationWhilePinned() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the PiP activity fixed as portrait, and enter picture-in-picture
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
-                EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-
-        // Request that the orientation is set to landscape
-        executeShellCommand("am broadcast -a "
-                + PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION + " -e "
-                + EXTRA_FIXED_ORIENTATION + " " + String.valueOf(ORIENTATION_LANDSCAPE));
-
-        // Launch the activity back into fullscreen and ensure that it is now in landscape
-        launchActivity(PIP_ACTIVITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        assertPinnedStackDoesNotExist();
-        assertTrue(mAmWmState.getWmState().getLastOrientation() == ORIENTATION_LANDSCAPE);
-    }
-
-    public void testWindowButtonEntersPip() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch the PiP activity trigger the window button, ensure that we have entered PiP
-        launchActivity(PIP_ACTIVITY);
-        pressWindowButton();
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-    }
-
-    public void testFinishPipActivityWithTaskOverlay() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-        int taskId = mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getTopTask().mTaskId;
-
-        // Ensure that we don't any any other overlays as a result of launching into PIP
-        launchHomeActivity();
-
-        // Launch task overlay activity into PiP activity task
-        launchActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId, PINNED_STACK_ID);
-
-        // Finish the PiP activity and ensure that there is no pinned stack
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
-        mAmWmState.waitForWithAmState(mDevice, (amState) -> {
-            ActivityStack stack = amState.getStackById(PINNED_STACK_ID);
-            return stack == null;
-        }, "Waiting for pinned stack to be removed...");
-        assertPinnedStackDoesNotExist();
-    }
-
-    public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        assertPinnedStackExists();
-        int taskId = mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getTopTask().mTaskId;
-
-        // Launch task overlay activity into PiP activity task
-        launchActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId, PINNED_STACK_ID);
-
-        // Finish the task overlay activity while animating and ensure that the PiP activity never
-        // got resumed
-        String logSeparator = clearLogcat();
-        executeShellCommand("am stack resize-animated 4 20 20 500 500");
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
-        mAmWmState.waitFor(mDevice, (amState, wmState) -> !amState.containsActivity(
-                TRANSLUCENT_TEST_ACTIVITY), "Waiting for test activity to finish...");
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
-                logSeparator);
-        assertTrue(lifecycleCounts.mResumeCount == 0);
-        assertTrue(lifecycleCounts.mPauseCount == 0);
-    }
-
-    public void testPinnedStackWithDockedStack() throws Exception {
-        if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
-
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        launchActivityToSide(true, false, TEST_ACTIVITY);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Launch the activities again to take focus and make sure nothing is hidden
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        launchActivityToSide(true, false, TEST_ACTIVITY);
-        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Go to recents to make sure that fullscreen stack is invisible
-        // Some devices do not support recents or implement it differently (instead of using a
-        // separate stack id or as an activity), for those cases the visibility asserts will be
-        // ignored
-        pressAppSwitchButton();
-        if (mAmWmState.waitForRecentsActivityVisible(mDevice)) {
-            mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-            mAmWmState.assertVisibility(TEST_ACTIVITY, false);
-        }
-    }
-
-    public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
-        // affinity
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-        launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
-        assertPinnedStackExists();
-
-        // Launch the root activity again...
-        int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
-                TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-
-        // ...and ensure that the root activity task is found and reused, and that the pinned stack
-        // is unaffected
-        assertPinnedStackExists();
-        mAmWmState.assertFocusedActivity("Expected root activity focused",
-                TEST_ACTIVITY_WITH_SAME_AFFINITY);
-        assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
-                TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
-    }
-
-    public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
-        // affinity, and also launch another activity in the same task, while finishing itself. As
-        // a result, the task will not have a component matching the same activity as what it was
-        // started with
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
-                EXTRA_START_ACTIVITY, getActivityComponentName(TEST_ACTIVITY),
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
-        mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY_WITH_SAME_AFFINITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-
-        // Launch the root activity again...
-        int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
-                TEST_ACTIVITY).mTaskId;
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-
-        // ...and ensure that even while matching purely by task affinity, the root activity task is
-        // found and reused, and that the pinned stack is unaffected
-        assertPinnedStackExists();
-        mAmWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
-        assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
-                TEST_ACTIVITY).mTaskId);
-    }
-
-    public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
-        if (!supportsPip()) return;
-
-        // Launch an activity into the pinned stack with a fixed affinity
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_START_ACTIVITY, getActivityComponentName(PIP_ACTIVITY),
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        assertPinnedStackExists();
-
-        // Launch the root activity again, of the matching task and ensure that we expand to
-        // fullscreen
-        int activityTaskId = mAmWmState.getAmState().getTaskByActivityName(
-                PIP_ACTIVITY).mTaskId;
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-        assertPinnedStackDoesNotExist();
-        assertTrue(activityTaskId == mAmWmState.getAmState().getTaskByActivityName(
-                PIP_ACTIVITY).mTaskId);
-    }
-
-    /** Test that reported display size corresponds to fullscreen after exiting PiP. */
-    public void testDisplayMetricsPinUnpin() throws Exception {
-        if (!supportsPip()) return;
-
-        String logSeparator = clearLogcat();
-        launchActivity(TEST_ACTIVITY);
-        final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
-        final ReportedSizes initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
-                logSeparator);
-        final Rectangle initialAppBounds = readAppBounds(TEST_ACTIVITY, logSeparator);
-        assertNotNull("Must report display dimensions", initialSizes);
-        assertNotNull("Must report app bounds", initialAppBounds);
-
-        logSeparator = clearLogcat();
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-        final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
-                logSeparator);
-        final Rectangle pinnedAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
-        assertFalse("Reported display size when pinned must be different from default",
-                initialSizes.equals(pinnedSizes));
-        assertFalse("Reported app bounds when pinned must be different from default",
-                initialAppBounds.width == pinnedAppBounds.width
-                        && initialAppBounds.height == pinnedAppBounds.height);
-
-        logSeparator = clearLogcat();
-        launchActivityInStack(PIP_ACTIVITY, defaultDisplayStackId);
-        final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
-                logSeparator);
-        final Rectangle finalAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
-        assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
-        assertEquals("Must report default app width after exiting PiP", initialAppBounds.width,
-                finalAppBounds.width);
-        assertEquals("Must report default app height after exiting PiP", initialAppBounds.height,
-                finalAppBounds.height);
-    }
-
-    private static final Pattern sAppBoundsPattern = Pattern.compile(
-            "(.+)appBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)(.*)");
-
-    /** Read app bounds in last applied configuration from logs. */
-    private Rectangle readAppBounds(String activityName, String logSeparator) throws Exception {
-        final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sAppBoundsPattern.matcher(line);
-            if (matcher.matches()) {
-                final int left = Integer.parseInt(matcher.group(2));
-                final int top = Integer.parseInt(matcher.group(3));
-                final int right = Integer.parseInt(matcher.group(4));
-                final int bottom = Integer.parseInt(matcher.group(5));
-                return new Rectangle(left, top, right - left, bottom - top);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
-     * that the {@param focusedStackId} is focused, and checks the top and/or bottom tasks in the
-     * fullscreen stack if {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity}
-     * are set respectively.
-     */
-    private void assertPinnedStackStateOnMoveToFullscreen(String activityName, int focusedStackId,
-            boolean expectTopTaskHasActivity, boolean expectBottomTaskHasActivity)
-                    throws Exception {
-        mAmWmState.waitForFocusedStack(mDevice, focusedStackId);
-        mAmWmState.assertFocusedStack("Wrong focused stack", focusedStackId);
-        mAmWmState.waitForActivityState(mDevice, activityName, STATE_STOPPED);
-        assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
-        assertPinnedStackDoesNotExist();
-
-        if (expectTopTaskHasActivity) {
-            ActivityTask topTask = mAmWmState.getAmState().getStackById(
-                    FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
-            assertTrue(topTask.containsActivity(ActivityManagerTestBase.getActivityComponentName(
-                    activityName)));
-        }
-        if (expectBottomTaskHasActivity) {
-            ActivityTask bottomTask = mAmWmState.getAmState().getStackById(
-                    FULLSCREEN_WORKSPACE_STACK_ID).getBottomTask();
-            assertTrue(bottomTask.containsActivity(ActivityManagerTestBase.getActivityComponentName(
-                    activityName)));
-        }
-    }
-
-    /**
-     * Asserts that the pinned stack bounds does not intersect with the IME bounds.
-     */
-    private void assertPinnedStackDoesNotIntersectIME() throws Exception {
-        // Ensure that the IME is visible
-        WindowManagerState wmState = mAmWmState.getWmState();
-        wmState.computeState(mDevice);
-        WindowManagerState.WindowState imeWinState = wmState.getInputMethodWindowState();
-        assertTrue(imeWinState != null);
-
-        // Ensure that the PIP movement is constrained by the display bounds intersecting the
-        // non-IME bounds
-        Rectangle imeContentFrame = imeWinState.getContentFrame();
-        Rectangle imeContentInsets = imeWinState.getGivenContentInsets();
-        Rectangle imeBounds = new Rectangle(imeContentFrame.x + imeContentInsets.x,
-                imeContentFrame.y + imeContentInsets.y,
-                imeContentFrame.width - imeContentInsets.width,
-                imeContentFrame.height - imeContentInsets.height);
-        wmState.computeState(mDevice);
-        Rectangle pipMovementBounds = wmState.getPinnedStackMomentBounds();
-        assertTrue(!pipMovementBounds.intersects(imeBounds));
-    }
-
-    /**
-     * Asserts that the pinned stack bounds is contained in the display bounds.
-     */
-    private void assertPinnedStackActivityIsInDisplayBounds(String activity) throws Exception {
-        final WindowManagerState.WindowState windowState = getWindowState(activity);
-        final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(
-                windowState.getDisplayId());
-        final Rectangle displayRect = display.getDisplayRect();
-        final Rectangle pinnedStackBounds =
-                mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
-        assertTrue(displayRect.contains(pinnedStackBounds));
-    }
-
-    /**
-     * Asserts that the pinned stack exists.
-     */
-    private void assertPinnedStackExists() throws Exception {
-        mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-    }
-
-    /**
-     * Asserts that the pinned stack does not exist.
-     */
-    private void assertPinnedStackDoesNotExist() throws Exception {
-        mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
-    }
-
-    /**
-     * Asserts that the pinned stack is the front stack.
-     */
-    private void assertPinnedStackIsOnTop() throws Exception {
-        mAmWmState.assertFrontStack("Pinned stack must always be on top.", PINNED_STACK_ID);
-    }
-
-    /**
-     * Asserts that the activity received exactly one of each of the callbacks when entering and
-     * exiting picture-in-picture.
-     */
-    private void assertValidPictureInPictureCallbackOrder(String activityName, String logSeparator)
-            throws Exception {
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
-
-        if (lifecycleCounts.mConfigurationChangedCount != 1) {
-            fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
-                    + " onConfigurationChanged() calls, expecting 1");
-        } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
-            fail(activityName + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
-                    + " onPictureInPictureModeChanged() calls, expecting 1");
-        } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
-            fail(activityName + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
-                    + " onMultiWindowModeChanged() calls, expecting 1");
-        } else {
-            int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
-            int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
-            int lastConfigLine = lifecycleCounts.mLastConfigurationChangedLineIndex;
-            if (!(lastPipLine < lastMwLine && lastMwLine < lastConfigLine)) {
-                fail(activityName + " has received callbacks in unexpected order.  Expected:"
-                        + " pip < mw < config change, but got line indices: " + lastPipLine + ", "
-                        + lastMwLine + ", " + lastConfigLine + " respectively");
-            }
-        }
-    }
-
-    /**
-     * Waits until the expected picture-in-picture callbacks have been made.
-     */
-    private void waitForValidPictureInPictureCallbacks(String activityName, String logSeparator)
-            throws Exception {
-        mAmWmState.waitFor(mDevice, (amState, wmState) -> {
-            try {
-                final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                        activityName, logSeparator);
-                return lifecycleCounts.mConfigurationChangedCount == 1 &&
-                        lifecycleCounts.mPictureInPictureModeChangedCount == 1 &&
-                        lifecycleCounts.mMultiWindowModeChangedCount == 1;
-            } catch (Exception e) {
-                return false;
-            }
-        }, "Waiting for picture-in-picture activity callbacks...");
-    }
-
-    /**
-     * @return the window state for the given {@param activity}'s window.
-     */
-    private WindowManagerState.WindowState getWindowState(String activity) throws Exception {
-        String windowName = getWindowName(activity);
-        mAmWmState.computeState(mDevice, new String[] {activity});
-        final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
-        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
-        return tempWindowList.get(0);
-    }
-
-    /**
-     * Compares two floats with a common epsilon.
-     */
-    private boolean floatEquals(float f1, float f2) {
-        return Math.abs(f1 - f2) < FLOAT_COMPARE_EPSILON;
-    }
-
-    /**
-     * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
-     */
-    private void tapToFinishPip() throws Exception {
-        Rectangle pinnedStackBounds =
-                mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
-        int tapX = pinnedStackBounds.x + pinnedStackBounds.width - 100;
-        int tapY = pinnedStackBounds.y + pinnedStackBounds.height - 100;
-        executeShellCommand(String.format("input tap %d %d", tapX, tapY));
-    }
-
-    /**
-     * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
-     */
-    private void launchActivityAsTaskOverlay(String activityName, int taskId, int stackId)
-            throws Exception {
-        executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
-
-        mAmWmState.waitForValidState(mDevice, activityName, stackId);
-    }
-
-    /**
-     * Sets an app-ops op for a given package to a given mode.
-     */
-    private void setAppOpsOpToMode(String packageName, String op, int mode) throws Exception {
-        executeShellCommand(String.format("appops set %s %s %d", packageName, op, mode));
-    }
-
-    /**
-     * Triggers the window keycode.
-     */
-    private void pressWindowButton() throws Exception {
-        executeShellCommand(INPUT_KEYEVENT_WINDOW);
-    }
-
-    /**
-     * TODO: Improve tests check to actually check that apps are not interactive instead of checking
-     *       if the stack is focused.
-     */
-    private void pinnedStackTester(String startActivityCmd, String topActivityName,
-            boolean moveTopToPinnedStack, boolean isFocusable) throws Exception {
-
-        executeShellCommand(startActivityCmd);
-        if (moveTopToPinnedStack) {
-            executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
-        }
-
-        mAmWmState.waitForValidState(mDevice, topActivityName, PINNED_STACK_ID);
-        mAmWmState.computeState(mDevice, null);
-
-        if (supportsPip()) {
-            final String windowName = getWindowName(topActivityName);
-            assertPinnedStackExists();
-            mAmWmState.assertFrontStack("Pinned stack must be the front stack.", PINNED_STACK_ID);
-            mAmWmState.assertVisibility(topActivityName, true);
-
-            if (isFocusable) {
-                mAmWmState.assertFocusedStack(
-                        "Pinned stack must be the focused stack.", PINNED_STACK_ID);
-                mAmWmState.assertFocusedActivity(
-                        "Pinned activity must be focused activity.", topActivityName);
-                mAmWmState.assertFocusedWindow(
-                        "Pinned window must be focused window.", windowName);
-                // Not checking for resumed state here because PiP overlay can be launched on top
-                // in different task by SystemUI.
-            } else {
-                // Don't assert that the stack is not focused as a focusable PiP overlay can be
-                // launched on top as a task overlay by SystemUI.
-                mAmWmState.assertNotFocusedActivity(
-                        "Pinned activity can't be the focused activity.", topActivityName);
-                mAmWmState.assertNotResumedActivity(
-                        "Pinned activity can't be the resumed activity.", topActivityName);
-                mAmWmState.assertNotFocusedWindow(
-                        "Pinned window can't be focused window.", windowName);
-            }
-        } else {
-            mAmWmState.assertDoesNotContainStack(
-                    "Must not contain pinned stack.", PINNED_STACK_ID);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
deleted file mode 100644
index 3aa3ce0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import junit.framework.Assert;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerReplaceWindowTests
- */
-public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
-
-    private static final String SLOW_CREATE_ACTIVITY_NAME = "SlowCreateActivity";
-    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
-
-    private List<String> mTempWindowTokens = new ArrayList();
-
-    public void testReplaceWindow_Dock_Relaunch() throws Exception {
-        testReplaceWindow_Dock(true);
-    }
-
-    public void testReplaceWindow_Dock_NoRelaunch() throws Exception {
-        testReplaceWindow_Dock(false);
-    }
-
-    private void testReplaceWindow_Dock(boolean relaunch) throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
-            return;
-        }
-
-        final String activityName =
-                relaunch ? SLOW_CREATE_ACTIVITY_NAME : NO_RELAUNCH_ACTIVITY_NAME;
-        final String windowName = getWindowName(activityName);
-        final String amStartCmd = getAmStartCmd(activityName);
-
-        executeShellCommand(amStartCmd);
-
-        // Sleep 2 seconds, then check if the window is started properly.
-        // SlowCreateActivity will do a sleep inside its onCreate() to simulate a
-        // slow-starting app. So instead of relying on WindowManagerState's
-        // retrying mechanism, we do an explicit sleep to avoid excess spews
-        // from WindowManagerState.
-        if (SLOW_CREATE_ACTIVITY_NAME.equals(activityName)) {
-            Thread.sleep(2000);
-        }
-
-        CLog.logAndDisplay(INFO, "==========Before Docking========");
-        final String oldToken = getWindowToken(windowName, activityName);
-
-        // Move to docked stack
-        final int taskId = getActivityTaskId(activityName);
-        final String cmd = AM_MOVE_TASK + taskId + " " + DOCKED_STACK_ID + " true";
-        executeShellCommand(cmd);
-
-        // Sleep 5 seconds, then check if the window is replaced properly.
-        Thread.sleep(5000);
-
-        CLog.logAndDisplay(INFO, "==========After Docking========");
-        final String newToken = getWindowToken(windowName, activityName);
-
-        // For both relaunch and not relaunch case, we'd like the window to be kept.
-        Assert.assertEquals("Window replaced while docking.", oldToken, newToken);
-    }
-
-    private String getWindowToken(String windowName, String activityName)
-            throws Exception {
-        mAmWmState.computeState(mDevice, new String[] {activityName});
-
-        mAmWmState.assertVisibility(activityName, true);
-
-        mAmWmState.getWmState().getMatchingWindowTokens(windowName, mTempWindowTokens);
-
-        Assert.assertEquals("Should have exactly one window for the activity.",
-                1, mTempWindowTokens.size());
-
-        return mTempWindowTokens.get(0);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
deleted file mode 100644
index 63aa1ab..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_TASK_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_TASK_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_INTRA_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_INTRA_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
-
-/**
- * This test tests the transition type selection logic in ActivityManager/
- * WindowManager. BottomActivity is started first, then TopActivity, and we
- * check the transition type that the system selects when TopActivity enters
- * or exits under various setups.
- *
- * Note that we only require the correct transition type to be reported (eg.
- * TRANSIT_ACTIVITY_OPEN, TRANSIT_TASK_CLOSE, TRANSIT_WALLPAPER_OPEN, etc.).
- * The exact animation is unspecified and can be overridden.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerTransitionSelectionTests
- */
-@Presubmit
-public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
-
-    private static final String BOTTOM_ACTIVITY_NAME = "BottomActivity";
-    private static final String TOP_ACTIVITY_NAME = "TopActivity";
-    private static final String TRANSLUCENT_TOP_ACTIVITY_NAME = "TranslucentTopActivity";
-
-    //------------------------------------------------------------------------//
-
-    // Test activity open/close under normal timing
-    public void testOpenActivity_NeitherWallpaper() throws Exception {
-        testOpenActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_ACTIVITY_OPEN);
-    }
-
-    public void testCloseActivity_NeitherWallpaper() throws Exception {
-        testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
-    }
-
-    public void testOpenActivity_BottomWallpaper() throws Exception {
-        testOpenActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
-    }
-
-    public void testCloseActivity_BottomWallpaper() throws Exception {
-        testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testOpenActivity_BothWallpaper() throws Exception {
-        testOpenActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
-    }
-
-    public void testCloseActivity_BothWallpaper() throws Exception {
-        testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    //------------------------------------------------------------------------//
-
-    // Test task open/close under normal timing
-    public void testOpenTask_NeitherWallpaper() throws Exception {
-        testOpenTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_TASK_OPEN);
-    }
-
-    public void testCloseTask_NeitherWallpaper() throws Exception {
-        testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_TASK_CLOSE);
-    }
-
-    public void testOpenTask_BottomWallpaper() throws Exception {
-        testOpenTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
-    }
-
-    public void testCloseTask_BottomWallpaper() throws Exception {
-        testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testOpenTask_BothWallpaper() throws Exception {
-        testOpenTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
-    }
-
-    public void testCloseTask_BothWallpaper() throws Exception {
-        testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    //------------------------------------------------------------------------//
-
-    // Test activity close -- bottom activity slow in stopping
-    // These simulate the case where the bottom activity is resumed
-    // before AM receives its activitiyStopped
-    public void testCloseActivity_NeitherWallpaper_SlowStop() throws Exception {
-        testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
-    }
-
-    public void testCloseActivity_BottomWallpaper_SlowStop() throws Exception {
-        testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testCloseActivity_BothWallpaper_SlowStop() throws Exception {
-        testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    //------------------------------------------------------------------------//
-
-    // Test task close -- bottom task top activity slow in stopping
-    // These simulate the case where the bottom activity is resumed
-    // before AM receives its activitiyStopped
-    public void testCloseTask_NeitherWallpaper_SlowStop() throws Exception {
-        testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_TASK_CLOSE);
-    }
-
-    public void testCloseTask_BottomWallpaper_SlowStop() throws Exception {
-        testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testCloseTask_BothWallpaper_SlowStop() throws Exception {
-        testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    //------------------------------------------------------------------------//
-
-    /// Test closing of translucent activity/task
-    public void testCloseActivity_NeitherWallpaper_Translucent() throws Exception {
-        testCloseActivityTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                TRANSIT_ACTIVITY_CLOSE);
-    }
-
-    public void testCloseActivity_BottomWallpaper_Translucent() throws Exception {
-        testCloseActivityTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testCloseActivity_BothWallpaper_Translucent() throws Exception {
-        testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    public void testCloseTask_NeitherWallpaper_Translucent() throws Exception {
-        testCloseTaskTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
-                TRANSIT_TASK_CLOSE);
-    }
-
-    public void testCloseTask_BottomWallpaper_Translucent() throws Exception {
-        testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
-                TRANSIT_WALLPAPER_OPEN);
-    }
-
-    public void testCloseTask_BothWallpaper_Translucent() throws Exception {
-        testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                TRANSIT_WALLPAPER_INTRA_CLOSE);
-    }
-
-    //------------------------------------------------------------------------//
-
-    private void testOpenActivity(boolean bottomWallpaper,
-            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
-        testTransitionSelection(true /*testOpen*/, false /*testNewTask*/,
-                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
-    }
-    private void testCloseActivity(boolean bottomWallpaper,
-            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
-        testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
-                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
-    }
-    private void testOpenTask(boolean bottomWallpaper,
-            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
-        testTransitionSelection(true /*testOpen*/, true /*testNewTask*/,
-                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
-    }
-    private void testCloseTask(boolean bottomWallpaper,
-            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
-        testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
-                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
-    }
-    private void testCloseActivityTranslucent(boolean bottomWallpaper,
-            boolean topWallpaper, String expectedTransit) throws Exception {
-        testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
-                bottomWallpaper, topWallpaper, true /*topTranslucent*/,
-                false /*slowStop*/, expectedTransit);
-    }
-    private void testCloseTaskTranslucent(boolean bottomWallpaper,
-            boolean topWallpaper, String expectedTransit) throws Exception {
-        testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
-                bottomWallpaper, topWallpaper, true /*topTranslucent*/,
-                false /*slowStop*/, expectedTransit);
-    }
-    //------------------------------------------------------------------------//
-
-    private void testTransitionSelection(
-            boolean testOpen, boolean testNewTask,
-            boolean bottomWallpaper, boolean topWallpaper, boolean topTranslucent,
-            boolean testSlowStop, String expectedTransit) throws Exception {
-        String bottomStartCmd = getAmStartCmd(BOTTOM_ACTIVITY_NAME);
-        if (bottomWallpaper) {
-            bottomStartCmd += " --ez USE_WALLPAPER true";
-        }
-        if (testSlowStop) {
-            bottomStartCmd += " --ei STOP_DELAY 3000";
-        }
-        executeShellCommand(bottomStartCmd);
-
-        final String topActivityName = topTranslucent ?
-                TRANSLUCENT_TOP_ACTIVITY_NAME : TOP_ACTIVITY_NAME;
-        final String[] bottomActivityArray = new String[] {BOTTOM_ACTIVITY_NAME};
-        final String[] topActivityArray = new String[] {topActivityName};
-
-        mAmWmState.computeState(mDevice, bottomActivityArray);
-
-        String topStartCmd = getAmStartCmd(topActivityName);
-        if (testNewTask) {
-            topStartCmd += " -f 0x18000000";
-        }
-        if (topWallpaper) {
-            topStartCmd += " --ez USE_WALLPAPER true";
-        }
-        if (!testOpen) {
-            topStartCmd += " --ei FINISH_DELAY 1000";
-        }
-        executeShellCommand(topStartCmd);
-        Thread.sleep(5000);
-        if (testOpen) {
-            mAmWmState.computeState(mDevice, topActivityArray);
-        } else {
-            mAmWmState.computeState(mDevice, bottomActivityArray);
-        }
-
-        assertEquals("Picked wrong transition", expectedTransit,
-                mAmWmState.getWmState().getLastTransition());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
deleted file mode 100644
index 89f4b5d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.AnimationBackgroundTests
- */
-public class AnimationBackgroundTests extends ActivityManagerTestBase {
-
-    public void testAnimationBackground_duringAnimation() throws Exception {
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY_ID);
-        getLaunchActivityBuilder()
-                .setTargetActivityName("AnimationTestActivity")
-                .setWaitForLaunched(false)
-                .execute();
-
-        // Make sure we are in the middle of the animation.
-        mAmWmState.waitForWithWmState(mDevice, state -> state
-                .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
-                        .isWindowAnimationBackgroundSurfaceShowing(),
-                "***Waiting for animation background showing");
-        assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
-                .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
-                .isWindowAnimationBackgroundSurfaceShowing());
-    }
-
-    public void testAnimationBackground_gone() throws Exception {
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY_ID);
-        getLaunchActivityBuilder().setTargetActivityName("AnimationTestActivity").execute();
-        mAmWmState.computeState(mDevice, new String[] { "AnimationTestActivity "});
-        assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
-                .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
-                .isWindowAnimationBackgroundSurfaceShowing());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
deleted file mode 100644
index 0ebbfe2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-/**
- * Ensure that compatibility dialog is shown when launching an application with
- * an unsupported smallest width.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.DisplaySizeTest
- */
-public class DisplaySizeTest extends DeviceTestCase {
-    private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
-    private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
-
-    private static final String AM_START_COMMAND = "am start -n %s/%s.%s";
-    private static final String AM_FORCE_STOP = "am force-stop %s";
-
-    private static final int ACTIVITY_TIMEOUT_MILLIS = 1000;
-    private static final int WINDOW_TIMEOUT_MILLIS = 1000;
-
-    private ITestDevice mDevice;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mDevice = getDevice();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
-        try {
-            resetDensity();
-
-            // Ensure app process is stopped.
-            forceStopPackage("android.displaysize.app");
-            forceStopPackage("android.server.cts");
-        } catch (DeviceNotAvailableException e) {
-            // Do nothing.
-        }
-    }
-
-    public void testCompatibilityDialog() throws Exception {
-        // Launch some other app (not to perform density change on launcher).
-        startActivity("android.server.cts", "TestActivity");
-        verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
-
-        setUnsupportedDensity();
-
-        // Launch target app.
-        startActivity("android.displaysize.app", "SmallestWidthActivity");
-        verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
-        verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
-    }
-
-    public void testCompatibilityDialogWhenFocused() throws Exception {
-        startActivity("android.displaysize.app", "SmallestWidthActivity");
-        verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
-
-        setUnsupportedDensity();
-
-        verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
-    }
-
-    public void testCompatibilityDialogAfterReturn() throws Exception {
-        // Launch target app.
-        startActivity("android.displaysize.app", "SmallestWidthActivity");
-        verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
-        // Launch another activity.
-        startOtherActivityOnTop("android.displaysize.app", "SmallestWidthActivity");
-        verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
-
-        setUnsupportedDensity();
-
-        // Go back.
-        mDevice.executeShellCommand("input keyevent 4");
-
-        verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
-        verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
-    }
-
-    private void setUnsupportedDensity() throws DeviceNotAvailableException {
-        // Set device to 0.85 zoom. It doesn't matter that we're zooming out
-        // since the feature verifies that we're in a non-default density.
-        final int stableDensity = getStableDensity();
-        final int targetDensity = (int) (stableDensity * 0.85);
-        setDensity(targetDensity);
-    }
-
-    private int getStableDensity() {
-        try {
-            final String densityProp;
-            if (mDevice.getSerialNumber().startsWith("emulator-")) {
-                densityProp = DENSITY_PROP_EMULATOR;
-            } else {
-                densityProp = DENSITY_PROP_DEVICE;
-            }
-
-            return Integer.parseInt(mDevice.getProperty(densityProp));
-        } catch (DeviceNotAvailableException e) {
-            return 0;
-        }
-    }
-
-    private void setDensity(int targetDensity) throws DeviceNotAvailableException {
-        mDevice.executeShellCommand("wm density " + targetDensity);
-
-        // Verify that the density is changed.
-        final String output = mDevice.executeShellCommand("wm density");
-        final boolean success = output.contains("Override density: " + targetDensity);
-
-        assertTrue("Failed to set density to " + targetDensity, success);
-    }
-
-    private void resetDensity() throws DeviceNotAvailableException {
-        mDevice.executeShellCommand("wm density reset");
-    }
-
-    private void forceStopPackage(String packageName) throws DeviceNotAvailableException {
-        final String forceStopCmd = String.format(AM_FORCE_STOP, packageName);
-        mDevice.executeShellCommand(forceStopCmd);
-    }
-
-    private void startActivity(String packageName, String activityName)
-            throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(getStartCommand(packageName, activityName));
-    }
-
-    private void startOtherActivityOnTop(String packageName, String activityName)
-            throws DeviceNotAvailableException {
-        final String startCmd = getStartCommand(packageName, activityName)
-                + " -f 0x20000000 --ez launch_another_activity true";
-        mDevice.executeShellCommand(startCmd);
-    }
-
-    private String getStartCommand(String packageName, String activityName) {
-        return String.format(AM_START_COMMAND, packageName, packageName, activityName);
-    }
-
-    private void verifyWindowDisplayed(String windowName, long timeoutMillis)
-            throws DeviceNotAvailableException {
-        boolean success = false;
-
-        // Verify that compatibility dialog is shown within 1000ms.
-        final long timeoutTimeMillis = System.currentTimeMillis() + timeoutMillis;
-        while (!success && System.currentTimeMillis() < timeoutTimeMillis) {
-            final String output = mDevice.executeShellCommand("dumpsys window");
-            success = output.contains(windowName);
-        }
-
-        assertTrue(windowName + " was not displayed", success);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
deleted file mode 100644
index b3d7add..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardLockedTests
- */
-public class KeyguardLockedTests extends KeyguardTestBase {
-
-    private static final String SHOW_WHEN_LOCKED_ACTIVITY = "ShowWhenLockedActivity";
-    private static final String PIP_ACTIVITY = "PipActivity";
-    private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
-            "android.server.cts.PipActivity.enter_pip";
-    private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        setLockCredential();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        tearDownLockCredentials();
-    }
-
-    public void testLockAndUnlock() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        unlockDeviceWithCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        assertKeyguardGone();
-    }
-
-    public void testDismissKeyguard() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity("DismissKeyguardActivity");
-        enterAndConfirmLockCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        assertKeyguardGone();
-        mAmWmState.assertVisibility("DismissKeyguardActivity", true);
-    }
-
-    public void testDismissKeyguard_whileOccluded() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-        launchActivity("DismissKeyguardActivity");
-        enterAndConfirmLockCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        assertKeyguardGone();
-        mAmWmState.assertVisibility("DismissKeyguardActivity", true);
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
-    }
-
-    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-        executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
-        enterAndConfirmLockCredential();
-
-        // Make sure we stay on Keyguard.
-        assertShowingAndOccluded();
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-    }
-
-    public void testDismissKeyguardActivity_method() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        final String logSeparator = clearLogcat();
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("DismissKeyguardMethodActivity");
-        enterAndConfirmLockCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardMethodActivity"});
-        mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertOnDismissSucceededInLogcat(logSeparator);
-    }
-
-    public void testDismissKeyguardActivity_method_cancelled() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-        final String logSeparator = clearLogcat();
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("DismissKeyguardMethodActivity");
-        pressBackButton();
-        assertOnDismissCancelledInLogcat(logSeparator);
-        mAmWmState.computeState(mDevice, new String[] {});
-        mAmWmState.assertVisibility("DismissKeyguardMethodActivity", false);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        unlockDeviceWithCredential();
-    }
-
-    public void testEnterPipOverKeyguard() throws Exception {
-        if (!isHandheld() || !supportsPip()) {
-            return;
-        }
-
-        // Go to the keyguard
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-
-        // Enter PiP on an activity on top of the keyguard, and ensure that it prompts the user for
-        // their credentials and does not enter picture-in-picture yet
-        launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
-        assertShowingAndOccluded();
-        mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
-
-        // Enter the credentials and ensure that the activity actually entered picture-in-picture
-        enterAndConfirmLockCredential();
-        mAmWmState.waitForKeyguardGone(mDevice);
-        assertKeyguardGone();
-        mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-    }
-
-    public void testShowWhenLockedActivityAndPipActivity() throws Exception {
-        if (!isHandheld() || !supportsPip()) {
-            return;
-        }
-
-        launchActivity(PIP_ACTIVITY);
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.computeState(mDevice, new String[] { PIP_ACTIVITY });
-        mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
-        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
-        mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
-        assertShowingAndOccluded();
-        mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, false);
-    }
-
-    public void testShowWhenLockedPipActivity() throws Exception {
-        if (!isHandheld() || !supportsPip()) {
-            return;
-        }
-
-        launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
-        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
-        mAmWmState.computeState(mDevice, new String[] { PIP_ACTIVITY });
-        mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        mAmWmState.assertVisibility(PIP_ACTIVITY, false);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java
deleted file mode 100644
index d578644..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.StateLogger.log;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class KeyguardTestBase extends ActivityManagerTestBase {
-
-    protected void assertShowingAndOccluded() {
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardOccluded);
-    }
-
-    protected void assertShowingAndNotOccluded() {
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardOccluded);
-    }
-
-    protected void assertKeyguardGone() {
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-    }
-
-    protected void assertOnDismissSucceededInLogcat(String logSeparator) throws Exception {
-        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissSucceeded", logSeparator);
-    }
-
-    protected void assertOnDismissCancelledInLogcat(String logSeparator) throws Exception {
-        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissCancelled", logSeparator);
-    }
-
-    protected void assertOnDismissErrorInLogcat(String logSeparator) throws Exception {
-        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissError", logSeparator);
-    }
-
-    private void assertInLogcat(String activityName, String entry, String logSeparator)
-            throws Exception {
-        final Pattern pattern = Pattern.compile("(.+)" + entry);
-        int tries = 0;
-        while (tries < 5) {
-            final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-            log("Looking at logcat");
-            for (int i = lines.length - 1; i >= 0; i--) {
-                final String line = lines[i].trim();
-                log(line);
-                Matcher matcher = pattern.matcher(line);
-                if (matcher.matches()) {
-                    return;
-                }
-            }
-            tries++;
-            Thread.sleep(500);
-        }
-        fail("Not in logcat: " + entry);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
deleted file mode 100644
index 8c6f037..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.server.cts.WindowManagerState.WindowState;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTests
- */
-public class KeyguardTests extends KeyguardTestBase {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // Set screen lock (swipe)
-        setLockDisabled(false);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-
-        tearDownLockCredentials();
-    }
-
-    public void testKeyguardHidesActivity() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("TestActivity");
-        mAmWmState.computeState(mDevice, new String[] { "TestActivity"});
-        mAmWmState.assertVisibility("TestActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertShowingAndNotOccluded();
-        mAmWmState.assertVisibility("TestActivity", false);
-        unlockDevice();
-    }
-
-    public void testShowWhenLockedActivity() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity"});
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * Tests whether dialogs from SHOW_WHEN_LOCKED activities are also visible if Keyguard is
-     * showing.
-     */
-    public void testShowWhenLockedActivity_withDialog() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedWithDialogActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedWithDialogActivity"});
-        mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
-        assertTrue(mAmWmState.getWmState().allWindowsVisible(
-                getWindowName("ShowWhenLockedWithDialogActivity")));
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
-     */
-    public void testMultipleShowWhenLockedActivities() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedActivity");
-        launchActivity("ShowWhenLockedTranslucentActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity",
-                "ShowWhenLockedTranslucentActivity"});
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
-     */
-    public void testTranslucentShowWhenLockedActivity() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedTranslucentActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedTranslucentActivity"});
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        assertWallpaperShowing();
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * If we have a translucent SHOW_WHEN_LOCKED activity, the activity behind should not be shown.
-     */
-    public void testTranslucentDoesntRevealBehind() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("TestActivity");
-        launchActivity("ShowWhenLockedTranslucentActivity");
-        mAmWmState.computeState(mDevice, new String[] { "TestActivity",
-                "ShowWhenLockedTranslucentActivity"});
-        mAmWmState.assertVisibility("TestActivity", true);
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
-        mAmWmState.assertVisibility("TestActivity", false);
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    public void testDialogShowWhenLockedActivity() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedDialogActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedDialogActivity"});
-        mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
-        assertWallpaperShowing();
-        assertShowingAndOccluded();
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * Test that showWhenLocked activity is fullscreen when shown over keyguard
-     */
-    public void testShowWhenLockedActivityWhileSplit() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset() || !supportsSplitScreenMultiWindow()) {
-            return;
-        }
-        launchActivityInDockStack(LAUNCHING_ACTIVITY);
-        launchActivityToSide(true, false, "ShowWhenLockedActivity");
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        assertShowingAndOccluded();
-        mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
-                ActivityManagerTestBase.DOCKED_STACK_ID);
-        pressHomeButton();
-        unlockDevice();
-    }
-
-    /**
-     * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
-     */
-    public void testDismissKeyguardActivity() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("DismissKeyguardActivity");
-        mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
-        mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardActivity"});
-        mAmWmState.assertVisibility("DismissKeyguardActivity", true);
-        assertShowingAndOccluded();
-    }
-
-    public void testDismissKeyguardActivity_method() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        final String logSeparator = clearLogcat();
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("DismissKeyguardMethodActivity");
-        mAmWmState.waitForKeyguardGone(mDevice);
-        mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardMethodActivity"});
-        mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertOnDismissSucceededInLogcat(logSeparator);
-    }
-
-    public void testDismissKeyguardActivity_method_notTop() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        final String logSeparator = clearLogcat();
-        gotoKeyguard();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("BroadcastReceiverActivity");
-        launchActivity("TestActivity");
-        executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguardMethod true");
-        assertOnDismissErrorInLogcat(logSeparator);
-    }
-
-    public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        final String logSeparator = clearLogcat();
-        sleepDevice();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity("TurnScreenOnDismissKeyguardActivity");
-        mAmWmState.waitForKeyguardGone(mDevice);
-        mAmWmState.computeState(mDevice, new String[] { "TurnScreenOnDismissKeyguardActivity"});
-        mAmWmState.assertVisibility("TurnScreenOnDismissKeyguardActivity", true);
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertOnDismissSucceededInLogcat(logSeparator);
-    }
-
-    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity("ShowWhenLockedActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        assertShowingAndOccluded();
-        executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
-        assertShowingAndOccluded();
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-    }
-
-    public void testKeyguardLock() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity("KeyguardLockActivity");
-        mAmWmState.computeState(mDevice, new String[] { "KeyguardLockActivity" });
-        mAmWmState.assertVisibility("KeyguardLockActivity", true);
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-    }
-
-    public void testUnoccludeRotationChange() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        executeShellCommand(getAmStartCmd("ShowWhenLockedActivity"));
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
-        setDeviceRotation(1);
-        pressHomeButton();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        mAmWmState.waitForDisplayUnfrozen(mDevice);
-        mAmWmState.assertSanity();
-        mAmWmState.assertHomeActivityVisible(false);
-        assertShowingAndNotOccluded();
-        mAmWmState.assertVisibility("ShowWhenLockedActivity", false);
-    }
-
-    private void assertWallpaperShowing() {
-        WindowState wallpaper =
-                mAmWmState.getWmState().findFirstWindowWithType(WindowState.TYPE_WALLPAPER);
-        assertNotNull(wallpaper);
-        assertTrue(wallpaper.isShown());
-    }
-
-    public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
-        sleepDevice();
-
-        final String logSeparator = clearLogcat();
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity(activityName);
-        mAmWmState.waitForKeyguardGone(mDevice);
-        mAmWmState.assertVisibility(activityName, true);
-        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertOnDismissSucceededInLogcat(logSeparator);
-        assertTrue(isDisplayOn());
-    }
-
-    public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
-
-        setLockCredential();
-        sleepDevice();
-
-        mAmWmState.computeState(mDevice, null);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        launchActivity(activityName);
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        mAmWmState.assertVisibility(activityName, false);
-        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-        assertTrue(isDisplayOn());
-    }
-
-    public void testScreenOffWhileOccludedStopsActivity() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        final String logSeparator = clearLogcat();
-        gotoKeyguard();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        assertShowingAndNotOccluded();
-        launchActivity("ShowWhenLockedAttrActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedAttrActivity" });
-        mAmWmState.assertVisibility("ShowWhenLockedAttrActivity", true);
-        assertShowingAndOccluded();
-        sleepDevice();
-        assertSingleLaunchAndStop("ShowWhenLockedAttrActivity", logSeparator);
-    }
-
-    public void testScreenOffCausesSingleStop() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        final String logSeparator = clearLogcat();
-        launchActivity("TestActivity");
-        mAmWmState.assertVisibility("TestActivity", true);
-        sleepDevice();
-        assertSingleLaunchAndStop("TestActivity", logSeparator);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
deleted file mode 100644
index 5971ccb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_UNOCCLUDE;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTransitionTests
- */
-public class KeyguardTransitionTests extends ActivityManagerTestBase {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // Set screen lock (swipe)
-        setLockDisabled(false);
-    }
-
-    public void testUnlock() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("TestActivity");
-        gotoKeyguard();
-        unlockDevice();
-        mAmWmState.computeState(mDevice, new String[] { "TestActivity"} );
-        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-    public void testUnlockWallpaper() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("WallpaperActivity");
-        gotoKeyguard();
-        unlockDevice();
-        mAmWmState.computeState(mDevice, new String[] { "WallpaperActivity"} );
-        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-    public void testOcclude() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        launchActivity("ShowWhenLockedActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity"} );
-        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-    public void testUnocclude() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        gotoKeyguard();
-        launchActivity("ShowWhenLockedActivity");
-        launchActivity("TestActivity");
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-        mAmWmState.computeState(mDevice, null);
-        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-    public void testNewActivityDuringOccluded() throws Exception {
-        if (!isHandheld() || isUiModeLockedToVrHeadset()) {
-            return;
-        }
-        launchActivity("ShowWhenLockedActivity");
-        gotoKeyguard();
-        launchActivity("ShowWhenLockedWithDialogActivity");
-        mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedWithDialogActivity" });
-        assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-    public void testOccludeManifestAttr() throws Exception {
-         if (!isHandheld()) {
-             return;
-         }
-
-         String activityName = "ShowWhenLockedAttrActivity";
-
-         gotoKeyguard();
-         final String logSeparator = clearLogcat();
-         launchActivity(activityName);
-         mAmWmState.computeState(mDevice, new String[] {activityName});
-         assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                 mAmWmState.getWmState().getLastTransition());
-         assertSingleLaunch(activityName, logSeparator);
-    }
-
-    public void testOccludeAttrRemove() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        String activityName = "ShowWhenLockedAttrRemoveAttrActivity";
-
-        gotoKeyguard();
-        String logSeparator = clearLogcat();
-        launchActivity(activityName);
-        mAmWmState.computeState(mDevice, new String[] {activityName});
-        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                mAmWmState.getWmState().getLastTransition());
-        assertSingleLaunch(activityName, logSeparator);
-
-        gotoKeyguard();
-        logSeparator = clearLogcat();
-        launchActivity(activityName);
-        mAmWmState.computeState(mDevice, new String[] {activityName});
-        assertSingleStartAndStop(activityName, logSeparator);
-    }
-
-    public void testNewActivityDuringOccludedWithAttr() throws Exception {
-        if (!isHandheld()) {
-            return;
-        }
-
-        String activityName1 = "ShowWhenLockedAttrActivity";
-        String activityName2 = "ShowWhenLockedAttrWithDialogActivity";
-
-        launchActivity(activityName1);
-        gotoKeyguard();
-        launchActivity(activityName2);
-        mAmWmState.computeState(mDevice, new String[] { activityName2 });
-        assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
-                mAmWmState.getWmState().getLastTransition());
-    }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java
deleted file mode 100644
index ed7f383..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import java.awt.Color;
-import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.SplashscreenTests
- */
-public class SplashscreenTests extends ActivityManagerTestBase {
-
-    public void testSplashscreenContent() throws Exception {
-        launchActivityNoWait("SplashscreenActivity");
-        mAmWmState.waitForAppTransitionIdle(mDevice);
-        mAmWmState.getWmState().getStableBounds();
-        final BufferedImage image = takeScreenshot();
-
-        // Use ratios to flexibly accomodate circular or not quite rectangular displays
-        // Note: Color.BLACK is the pixel color outside of the display region
-        assertColors(image, mAmWmState.getWmState().getStableBounds(),
-            Color.RED.getRGB(), 0.50f, Color.BLACK.getRGB(), 0.01f);
-    }
-
-    private void assertColors(BufferedImage img, Rectangle bounds, int primaryColor,
-        float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio) {
-
-        int primaryPixels = 0;
-        int secondaryPixels = 0;
-        int wrongPixels = 0;
-        for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
-            for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
-                assertTrue(x < img.getWidth());
-                assertTrue(y < img.getHeight());
-                final int color = img.getRGB(x, y);
-                if (primaryColor == color) {
-                    primaryPixels++;
-                } else if (secondaryColor == color) {
-                    secondaryPixels++;
-                } else {
-                    wrongPixels++;
-                }
-            }
-        }
-
-        final int totalPixels = bounds.width * bounds.height;
-        final float primaryRatio = (float) primaryPixels / totalPixels;
-        if (primaryRatio < expectedPrimaryRatio) {
-            fail("Less than " + (expectedPrimaryRatio * 100.0f)
-                    + "% of pixels have non-primary color primaryPixels=" + primaryPixels
-                    + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
-        }
-
-        // Some pixels might be covered by screen shape decorations, like rounded corners.
-        // On circular displays, there is an antialiased edge.
-        final float wrongRatio = (float) wrongPixels / totalPixels;
-        if (wrongRatio > acceptableWrongRatio) {
-            fail("More than " + (acceptableWrongRatio * 100.0f)
-                    + "% of pixels have wrong color primaryPixels=" + primaryPixels
-                    + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk
deleted file mode 100644
index 3b075e1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk
+++ /dev/null
@@ -1,32 +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)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceTranslucentTestApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml
deleted file mode 100755
index ee3bff8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +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"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.translucentapp">
-    <application android:label="CtsTranslucentApp">
-        <activity android:name=".TranslucentLandscapeActivity"
-                android:theme="@android:style/Theme.Translucent.NoTitleBar"
-                android:exported="true"
-                android:screenOrientation="landscape">
-            <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/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java
deleted file mode 100644
index 471e3b6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.translucentapp;
-
-import android.app.Activity;
-
-public class TranslucentLandscapeActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk
deleted file mode 100644
index dbb0b2b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk
+++ /dev/null
@@ -1,32 +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)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, ../translucentapp/src) \
-
-LOCAL_SDK_VERSION := 26
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceTranslucentTestApp26
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml
deleted file mode 100755
index 43c85f5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +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"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.translucentapp26">
-    <application android:label="CtsTranslucentApp26">
-        <activity android:name="android.server.translucentapp.TranslucentLandscapeActivity"
-                  android:theme="@android:style/Theme.Translucent.NoTitleBar"
-                android:exported="true"
-                android:screenOrientation="landscape">
-            <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/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml
deleted file mode 100644
index d3ae55e..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +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"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.displayservice">
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <application android:label="CtsDisplayService">
-        <service android:name=".VirtualDisplayService"
-                 android:exported="true" />
-    </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java b/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java
deleted file mode 100644
index eb58963..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.displayservice;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.Surface;
-
-public class VirtualDisplayService extends Service {
-    private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
-    private static final String TAG = "VirtualDisplayService";
-
-    private static final int FOREGROUND_ID = 1;
-
-    private static final int DENSITY = 160;
-    private static final int HEIGHT = 480;
-    private static final int WIDTH = 800;
-
-    private ImageReader mReader;
-    private VirtualDisplay mVirtualDisplay;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        notificationManager.createNotificationChannel(new NotificationChannel(
-            NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
-            NotificationManager.IMPORTANCE_DEFAULT));
-        Notification notif = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
-                .setSmallIcon(android.R.drawable.ic_dialog_alert)
-                .build();
-        startForeground(FOREGROUND_ID, notif);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        String command = intent.getStringExtra("command");
-        Log.d(TAG, "Got command: " + command);
-
-        if ("create".equals(command)) {
-            createVirtualDisplay(intent);
-        } if ("off".equals(command)) {
-            mVirtualDisplay.setSurface(null);
-        } else if ("on".equals(command)) {
-            mVirtualDisplay.setSurface(mReader.getSurface());
-        } else if ("destroy".equals(command)) {
-            destroyVirtualDisplay();
-            stopSelf();
-        }
-
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    private void createVirtualDisplay(Intent intent) {
-        mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
-
-        final DisplayManager displayManager = getSystemService(DisplayManager.class);
-        final String name = "CtsVirtualDisplay";
-
-        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-        if (intent.getBooleanExtra("show_content_when_locked", false /* defaultValue */)) {
-            flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
-        }
-        mVirtualDisplay = displayManager.createVirtualDisplay(
-                name, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
-    }
-
-    private void destroyVirtualDisplay() {
-        mVirtualDisplay.release();
-        mReader.close();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk b/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk
deleted file mode 100644
index 0df59e7..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-
-LOCAL_MODULE := cts-display-service-app-util
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java b/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java
deleted file mode 100644
index 89683a0..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package android.server.displayservice;
-
-import static junit.framework.Assert.assertTrue;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-public class DisplayHelper {
-    private static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
-    private static final String VIRTUAL_DISPLAY_SERVICE =
-            "android.server.displayservice/.VirtualDisplayService";
-    private static final Pattern mDisplayDevicePattern = Pattern.compile(
-            ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
-
-    private boolean mCreated;
-    private final ITestDevice mDevice;
-
-    public DisplayHelper(ITestDevice device) {
-        mDevice = device;
-    }
-
-    public void createAndWaitForDisplay(boolean external, boolean requestShowWhenLocked)
-            throws DeviceNotAvailableException {
-        StringBuilder command =
-                new StringBuilder("am startfgservice -n " + VIRTUAL_DISPLAY_SERVICE);
-        command.append(" --es command create");
-        if (external) {
-            command.append(" --ez external_display true");
-        }
-        if (requestShowWhenLocked) {
-            command.append(" --ez show_content_when_locked true");
-        }
-        mDevice.executeShellCommand(command.toString());
-
-        waitForDisplayState(mDevice, false /* default */, true /* exists */, true /* on */);
-        mCreated = true;
-    }
-
-    public void turnDisplayOff() throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(
-                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command off");
-        waitForDisplayState(mDevice, false /* default */, true /* exists */, false /* on */);
-    }
-
-    public void turnDisplayOn() throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(
-                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command on");
-        waitForDisplayState(mDevice, false /* default */, true /* exists */, true /* on */);
-    }
-
-    public void releaseDisplay() throws DeviceNotAvailableException {
-        if (mCreated) {
-            mDevice.executeShellCommand(
-                    "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command destroy");
-            waitForDisplayState(mDevice, false /* default */, false /* exists */, true /* on */);
-        }
-        mCreated = false;
-    }
-
-    public static void waitForDefaultDisplayState(ITestDevice device, boolean wantOn)
-            throws DeviceNotAvailableException {
-        waitForDisplayState(device, true /* default */, true /* exists */, wantOn);
-    }
-
-    public static boolean getDefaultDisplayState(ITestDevice device)
-            throws DeviceNotAvailableException {
-        return getDisplayState(device, true);
-    }
-
-    private static void waitForDisplayState(
-            ITestDevice device, boolean defaultDisplay, boolean wantExists, boolean wantOn)
-            throws DeviceNotAvailableException {
-        int tries = 0;
-        boolean done = false;
-        do {
-            if (tries > 0) {
-                try {
-                    Thread.sleep(500);
-                } catch (InterruptedException e) {
-                    // Oh well
-                }
-            }
-
-            Boolean state = getDisplayState(device, defaultDisplay);
-            done = (!wantExists && state == null)
-                    || (wantExists && state != null && state == wantOn);
-
-            tries++;
-        } while (tries < 10 && !done);
-
-        assertTrue(done);
-    }
-
-    private static Boolean getDisplayState(ITestDevice device, boolean defaultDisplay)
-            throws DeviceNotAvailableException {
-        final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        device.executeShellCommand("dumpsys display", outputReceiver);
-        String dump = outputReceiver.getOutput();
-
-        boolean displayExists = false;
-        boolean displayOn = false;
-        for (String line : dump.split("\\n")) {
-            Matcher matcher = mDisplayDevicePattern.matcher(line);
-            if (matcher.matches()) {
-                if ((defaultDisplay && line.contains("FLAG_DEFAULT_DISPLAY"))
-                        || (!defaultDisplay && VIRTUAL_DISPLAY_NAME.equals(matcher.group(1)))) {
-                    return "ON".equals(matcher.group(2));
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/Android.mk b/hostsidetests/services/activityandwindowmanager/util/Android.mk
deleted file mode 100644
index 993ba94..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/Android.mk
+++ /dev/null
@@ -1,34 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-
-LOCAL_MODULE := cts-amwm-util
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
deleted file mode 100644
index a54ed19..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
+++ /dev/null
@@ -1,844 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.RESIZE_MODE_RESIZEABLE;
-import static android.server.cts.ActivityManagerTestBase.DOCKED_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.componentName;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-import android.server.cts.WindowManagerState.Display;
-import android.server.cts.WindowManagerState.WindowStack;
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.WindowManagerState.WindowTask;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import junit.framework.Assert;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.BiPredicate;
-import java.util.function.BooleanSupplier;
-import java.util.function.Predicate;
-
-/** Combined state of the activity manager and window manager. */
-public class ActivityAndWindowManagersState extends Assert {
-
-    // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
-    // (Needed in host-side tests to convert dp to px.)
-    private static final int DISPLAY_DENSITY_DEFAULT = 160;
-    public static final int DEFAULT_DISPLAY_ID = 0;
-
-    // Default minimal size of resizable task, used if none is set explicitly.
-    // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base.
-    private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220;
-
-    // Default minimal size of a resizable PiP task, used if none is set explicitly.
-    // Must be kept in sync with 'default_minimal_size_pip_resizable_task' dimen from
-    // frameworks/base.
-    private static final int DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP = 108;
-
-    private ActivityManagerState mAmState = new ActivityManagerState();
-    private WindowManagerState mWmState = new WindowManagerState();
-
-    private final List<WindowManagerState.WindowState> mTempWindowList = new ArrayList<>();
-
-    private boolean mUseActivityNames = true;
-
-    /**
-     * Compute AM and WM state of device, check sanity and bounds.
-     * WM state will include only visible windows, stack and task bounds will be compared.
-     *
-     * @param device test device.
-     * @param waitForActivitiesVisible array of activity names to wait for.
-     */
-    public void computeState(ITestDevice device, String[] waitForActivitiesVisible)
-            throws Exception {
-        computeState(device, waitForActivitiesVisible, true);
-    }
-
-    /**
-     * Compute AM and WM state of device, check sanity and bounds.
-     *
-     * @param device test device.
-     * @param waitForActivitiesVisible array of activity names to wait for.
-     * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
-     *                                  'false' otherwise.
-     */
-    void computeState(ITestDevice device, String[] waitForActivitiesVisible,
-                      boolean compareTaskAndStackBounds) throws Exception {
-        waitForValidState(device, waitForActivitiesVisible, null /* stackIds */,
-                compareTaskAndStackBounds);
-
-        assertSanity();
-        assertValidBounds(compareTaskAndStackBounds);
-    }
-
-    /**
-     * By default computeState allows you to pass only the activity name it and
-     * it will generate the full window name for the main activity window. In the
-     * case of secondary application windows though this isn't helpful, as they
-     * may follow a different format, so this method lets you disable that behavior,
-     * prior to calling a computeState variant
-     */
-    void setUseActivityNamesForWindowNames(boolean useActivityNames) {
-        mUseActivityNames = useActivityNames;
-    }
-
-    /**
-     * Compute AM and WM state of device, wait for the activity records to be added, and
-     * wait for debugger window to show up.
-     *
-     * This should only be used when starting with -D (debugger) option, where we pop up the
-     * waiting-for-debugger window, but real activity window won't show up since we're waiting
-     * for debugger.
-     */
-    void waitForDebuggerWindowVisible(
-            ITestDevice device, String[] waitForActivityRecords) throws Exception {
-        int retriesLeft = 5;
-        do {
-            mAmState.computeState(device);
-            mWmState.computeState(device);
-            if (shouldWaitForDebuggerWindow() ||
-                    shouldWaitForActivityRecords(waitForActivityRecords)) {
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-    }
-
-    /**
-     * Wait for the activity to appear and for valid state in AM and WM.
-     *
-     * @param device test device.
-     * @param waitForActivityVisible name of activity to wait for.
-     */
-    void waitForValidState(ITestDevice device, String waitForActivityVisible)
-            throws Exception {
-        waitForValidState(device, new String[]{waitForActivityVisible}, null /* stackIds */,
-                false /* compareTaskAndStackBounds */);
-    }
-
-    /**
-     * Wait for the activity to appear in proper stack and for valid state in AM and WM.
-     *
-     * @param device test device.
-     * @param waitForActivityVisible name of activity to wait for.
-     * @param stackId id of the stack where provided activity should be found.
-     */
-    void waitForValidState(ITestDevice device, String waitForActivityVisible, int stackId)
-            throws Exception {
-        waitForValidState(device, new String[]{waitForActivityVisible}, new int[]{stackId},
-                false /* compareTaskAndStackBounds */);
-    }
-
-    /**
-     * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
-     *
-     * @param device test device.
-     * @param waitForActivitiesVisible array of activity names to wait for.
-     * @param stackIds ids of stack where provided activities should be found.
-     *                 Pass null to skip this check.
-     * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
-     *                                  for equality.
-     */
-    void waitForValidState(ITestDevice device, String[] waitForActivitiesVisible, int[] stackIds,
-            boolean compareTaskAndStackBounds) throws Exception {
-        waitForValidState(device, waitForActivitiesVisible, stackIds, compareTaskAndStackBounds,
-                componentName);
-    }
-
-    /**
-     * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
-     *
-     * @param device test device.
-     * @param waitForActivitiesVisible array of activity names to wait for.
-     * @param stackIds ids of stack where provided activities should be found.
-     *                 Pass null to skip this check.
-     * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
-     *                                  for equality.
-     * @param packageName name of the package of activities that we're waiting for.
-     */
-    void waitForValidState(ITestDevice device, String[] waitForActivitiesVisible, int[] stackIds,
-            boolean compareTaskAndStackBounds, String packageName) throws Exception {
-        int retriesLeft = 5;
-        do {
-            // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
-            // requesting dump in some intermediate state.
-            mAmState.computeState(device);
-            mWmState.computeState(device);
-            if (shouldWaitForValidStacks(compareTaskAndStackBounds)
-                    || shouldWaitForActivities(waitForActivitiesVisible, stackIds, packageName)
-                    || shouldWaitForWindows()) {
-                log("***Waiting for valid stacks and activities states...");
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-    }
-
-    void waitForAllStoppedActivities(ITestDevice device) throws Exception {
-        int retriesLeft = 5;
-        do {
-            mAmState.computeState(device);
-            if (mAmState.containsStartedActivities()){
-                log("***Waiting for valid stacks and activities states...");
-                try {
-                    Thread.sleep(1500);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertFalse(mAmState.containsStartedActivities());
-    }
-
-    void waitForHomeActivityVisible(ITestDevice device) throws Exception {
-        waitForValidState(device, mAmState.getHomeActivityName());
-    }
-
-    /** @return true if recents activity is visible. Devices without recents will return false */
-    boolean waitForRecentsActivityVisible(ITestDevice device) throws Exception {
-        waitForWithAmState(device, ActivityManagerState::isRecentsActivityVisible,
-                "***Waiting for recents activity to be visible...");
-        return mAmState.isRecentsActivityVisible();
-    }
-
-    void waitForKeyguardShowingAndNotOccluded(ITestDevice device) throws Exception {
-        waitForWithAmState(device, state -> state.getKeyguardControllerState().keyguardShowing
-                        && !state.getKeyguardControllerState().keyguardOccluded,
-                "***Waiting for Keyguard showing...");
-    }
-
-    void waitForKeyguardShowingAndOccluded(ITestDevice device) throws Exception {
-        waitForWithAmState(device, state -> state.getKeyguardControllerState().keyguardShowing
-                        && state.getKeyguardControllerState().keyguardOccluded,
-                "***Waiting for Keyguard showing and occluded...");
-    }
-
-    void waitForKeyguardGone(ITestDevice device) throws Exception {
-        waitForWithAmState(device, state -> !state.getKeyguardControllerState().keyguardShowing,
-                "***Waiting for Keyguard gone...");
-    }
-
-    void waitForRotation(ITestDevice device, int rotation) throws Exception {
-        waitForWithWmState(device, state -> state.getRotation() == rotation,
-                "***Waiting for Rotation: " + rotation);
-    }
-
-    void waitForDisplayUnfrozen(ITestDevice device) throws Exception {
-        waitForWithWmState(device, state -> !state.isDisplayFrozen(),
-                "***Waiting for Display unfrozen");
-    }
-
-    void waitForActivityState(ITestDevice device, String activityName, String... activityStates)
-        throws Exception {
-        waitForWithAmState(device, state -> {
-                for (String activityState : activityStates) {
-                    if (state.hasActivityState(activityName, activityState)) {
-                        return true;
-                    }
-                }
-                return false;
-            },
-            "***Waiting for Activity State: " + activityStates);
-    }
-
-    void waitForFocusedStack(ITestDevice device, int stackId) throws Exception {
-        waitForWithAmState(device, state -> state.getFocusedStackId() == stackId,
-                "***Waiting for focused stack...");
-    }
-
-    void waitForAppTransitionIdle(ITestDevice device) throws Exception {
-        waitForWithWmState(device,
-                state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
-                "***Waiting for app transition idle...");
-    }
-
-    void waitForWithAmState(ITestDevice device, Predicate<ActivityManagerState> waitCondition,
-            String message) throws Exception{
-        waitFor(device, (amState, wmState) -> waitCondition.test(amState), message);
-    }
-
-    void waitForWithWmState(ITestDevice device, Predicate<WindowManagerState> waitCondition,
-            String message) throws Exception{
-        waitFor(device, (amState, wmState) -> waitCondition.test(wmState), message);
-    }
-
-    void waitFor(ITestDevice device,
-            BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message)
-            throws Exception {
-        waitFor(message, () -> {
-            try {
-                mAmState.computeState(device);
-                mWmState.computeState(device);
-            } catch (Exception e) {
-                logE(e.toString());
-                return false;
-            }
-            return waitCondition.test(mAmState, mWmState);
-        });
-    }
-
-    void waitFor(String message, BooleanSupplier waitCondition) throws Exception {
-        int retriesLeft = 5;
-        do {
-            if (!waitCondition.getAsBoolean()) {
-                log(message);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-    }
-
-    /** @return true if should wait for valid stacks state. */
-    private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) {
-        if (!taskListsInAmAndWmAreEqual()) {
-            // We want to wait for equal task lists in AM and WM in case we caught them in the
-            // middle of some state change operations.
-            log("***taskListsInAmAndWmAreEqual=false");
-            return true;
-        }
-        if (!stackBoundsInAMAndWMAreEqual()) {
-            // We want to wait a little for the stacks in AM and WM to have equal bounds as there
-            // might be a transition animation ongoing when we got the states from WM AM separately.
-            log("***stackBoundsInAMAndWMAreEqual=false");
-            return true;
-        }
-        try {
-            // Temporary fix to avoid catching intermediate state with different task bounds in AM
-            // and WM.
-            assertValidBounds(compareTaskAndStackBounds);
-        } catch (AssertionError e) {
-            log("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage());
-            return true;
-        }
-        final int stackCount = mAmState.getStackCount();
-        if (stackCount == 0) {
-            log("***stackCount=" + stackCount);
-            return true;
-        }
-        final int resumedActivitiesCount = mAmState.getResumedActivitiesCount();
-        if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) {
-            log("***resumedActivitiesCount=" + resumedActivitiesCount);
-            return true;
-        }
-        if (mAmState.getFocusedActivity() == null) {
-            log("***focusedActivity=null");
-            return true;
-        }
-        return false;
-    }
-
-    /** @return true if should wait for some activities to become visible. */
-    private boolean shouldWaitForActivities(String[] waitForActivitiesVisible, int[] stackIds,
-            String packageName) {
-        if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) {
-            return false;
-        }
-        // If the caller is interested in us waiting for some particular activity windows to be
-        // visible before compute the state. Check for the visibility of those activity windows
-        // and for placing them in correct stacks (if requested).
-        boolean allActivityWindowsVisible = true;
-        boolean tasksInCorrectStacks = true;
-        List<WindowManagerState.WindowState> matchingWindowStates = new ArrayList<>();
-        for (int i = 0; i < waitForActivitiesVisible.length; i++) {
-            // Check if window is visible - it should be represented as one of the window states.
-            final String windowName = mUseActivityNames ?
-                    ActivityManagerTestBase.getWindowName(packageName, waitForActivitiesVisible[i])
-                    : waitForActivitiesVisible[i];
-            final String activityComponentName =
-                    ActivityManagerTestBase.getActivityComponentName(packageName,
-                            waitForActivitiesVisible[i]);
-
-            mWmState.getMatchingVisibleWindowState(windowName, matchingWindowStates);
-            boolean activityWindowVisible = !matchingWindowStates.isEmpty();
-            if (!activityWindowVisible) {
-                log("Activity window not visible: " + windowName);
-                allActivityWindowsVisible = false;
-            } else if (!mAmState.isActivityVisible(activityComponentName)) {
-                log("Activity not visible: " + activityComponentName);
-                allActivityWindowsVisible = false;
-            } else if (stackIds != null) {
-                // Check if window is already in stack requested by test.
-                boolean windowInCorrectStack = false;
-                for (WindowManagerState.WindowState ws : matchingWindowStates) {
-                    if (ws.getStackId() == stackIds[i]) {
-                        windowInCorrectStack = true;
-                        break;
-                    }
-                }
-                if (!windowInCorrectStack) {
-                    log("Window in incorrect stack: " + waitForActivitiesVisible[i]);
-                    tasksInCorrectStacks = false;
-                }
-            }
-        }
-        return !allActivityWindowsVisible || !tasksInCorrectStacks;
-    }
-
-    /** @return true if should wait valid windows state. */
-    private boolean shouldWaitForWindows() {
-        if (mWmState.getFrontWindow() == null) {
-            log("***frontWindow=null");
-            return true;
-        }
-        if (mWmState.getFocusedWindow() == null) {
-            log("***focusedWindow=null");
-            return true;
-        }
-        if (mWmState.getFocusedApp() == null) {
-            log("***focusedApp=null");
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean shouldWaitForDebuggerWindow() {
-        List<WindowManagerState.WindowState> matchingWindowStates = new ArrayList<>();
-        mWmState.getMatchingVisibleWindowState("android.server.cts", matchingWindowStates);
-        for (WindowState ws : matchingWindowStates) {
-            if (ws.isDebuggerWindow()) {
-                return false;
-            }
-        }
-        log("Debugger window not available yet");
-        return true;
-    }
-
-    private boolean shouldWaitForActivityRecords(String[] waitForActivityRecords) {
-        if (waitForActivityRecords == null || waitForActivityRecords.length == 0) {
-            return false;
-        }
-        // Check if the activity records we're looking for is already added.
-        for (int i = 0; i < waitForActivityRecords.length; i++) {
-            if (!mAmState.isActivityVisible(waitForActivityRecords[i])) {
-                log("ActivityRecord " + waitForActivityRecords[i] + " not visible yet");
-                return true;
-            }
-        }
-        return false;
-    }
-
-    ActivityManagerState getAmState() {
-        return mAmState;
-    }
-
-    public WindowManagerState getWmState() {
-        return mWmState;
-    }
-
-    void assertSanity() throws Exception {
-        assertTrue("Must have stacks", mAmState.getStackCount() > 0);
-        if (!mAmState.getKeyguardControllerState().keyguardShowing) {
-            assertEquals("There should be one and only one resumed activity in the system.",
-                    1, mAmState.getResumedActivitiesCount());
-        }
-        assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
-
-        for (ActivityStack aStack : mAmState.getStacks()) {
-            final int stackId = aStack.mStackId;
-            for (ActivityTask aTask : aStack.getTasks()) {
-                assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId);
-            }
-        }
-
-        assertNotNull("Must have front window.", mWmState.getFrontWindow());
-        assertNotNull("Must have focused window.", mWmState.getFocusedWindow());
-        assertNotNull("Must have app.", mWmState.getFocusedApp());
-    }
-
-    void assertContainsStack(String msg, int stackId) throws Exception {
-        assertTrue(msg, mAmState.containsStack(stackId));
-        assertTrue(msg, mWmState.containsStack(stackId));
-    }
-
-    void assertDoesNotContainStack(String msg, int stackId) throws Exception {
-        assertFalse(msg, mAmState.containsStack(stackId));
-        assertFalse(msg, mWmState.containsStack(stackId));
-    }
-
-    void assertFrontStack(String msg, int stackId) throws Exception {
-        assertEquals(msg, stackId, mAmState.getFrontStackId(DEFAULT_DISPLAY_ID));
-        assertEquals(msg, stackId, mWmState.getFrontStackId(DEFAULT_DISPLAY_ID));
-    }
-
-    void assertFocusedStack(String msg, int stackId) throws Exception {
-        assertEquals(msg, stackId, mAmState.getFocusedStackId());
-    }
-
-    void assertNotFocusedStack(String msg, int stackId) throws Exception {
-        if (stackId == mAmState.getFocusedStackId()) {
-            failNotEquals(msg, stackId, mAmState.getFocusedStackId());
-        }
-    }
-
-    void assertFocusedActivity(String msg, String activityName) throws Exception {
-        assertFocusedActivity(msg, componentName, activityName);
-    }
-
-    void assertFocusedActivity(String msg, String packageName, String activityName)
-            throws Exception {
-        final String componentName = ActivityManagerTestBase.getActivityComponentName(packageName,
-                activityName);
-        assertEquals(msg, componentName, mAmState.getFocusedActivity());
-        assertEquals(msg, componentName, mWmState.getFocusedApp());
-    }
-
-    void assertNotFocusedActivity(String msg, String activityName) throws Exception {
-        final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
-        if (mAmState.getFocusedActivity().equals(componentName)) {
-            failNotEquals(msg, mAmState.getFocusedActivity(), componentName);
-        }
-        if (mWmState.getFocusedApp().equals(componentName)) {
-            failNotEquals(msg, mWmState.getFocusedApp(), componentName);
-        }
-    }
-
-    void assertResumedActivity(String msg, String activityName) throws Exception {
-        final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
-        assertEquals(msg, componentName, mAmState.getResumedActivity());
-    }
-
-    void assertNotResumedActivity(String msg, String activityName) throws Exception {
-        final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
-        if (mAmState.getResumedActivity().equals(componentName)) {
-            failNotEquals(msg, mAmState.getResumedActivity(), componentName);
-        }
-    }
-
-    void assertFocusedWindow(String msg, String windowName) {
-        assertEquals(msg, windowName, mWmState.getFocusedWindow());
-    }
-
-    void assertNotFocusedWindow(String msg, String windowName) {
-        if (mWmState.getFocusedWindow().equals(windowName)) {
-            failNotEquals(msg, mWmState.getFocusedWindow(), windowName);
-        }
-    }
-
-    void assertFrontWindow(String msg, String windowName) {
-        assertEquals(msg, windowName, mWmState.getFrontWindow());
-    }
-
-    void assertVisibility(String activityName, boolean visible) {
-        final String activityComponentName =
-                ActivityManagerTestBase.getActivityComponentName(activityName);
-        final String windowName =
-                ActivityManagerTestBase.getWindowName(activityName);
-        assertVisibility(activityComponentName, windowName, visible);
-    }
-
-    private void assertVisibility(String activityComponentName, String windowName,
-            boolean visible) {
-        final boolean activityVisible = mAmState.isActivityVisible(activityComponentName);
-        final boolean windowVisible = mWmState.isWindowVisible(windowName);
-
-        if (visible) {
-            assertTrue("Activity=" + activityComponentName + " must be visible.", activityVisible);
-            assertTrue("Window=" + windowName + " must be visible.", windowVisible);
-        } else {
-            assertFalse("Activity=" + activityComponentName + " must NOT be visible.",
-                    activityVisible);
-            assertFalse("Window=" + windowName + " must NOT be visible.", windowVisible);
-        }
-    }
-
-    void assertHomeActivityVisible(boolean visible) {
-        String name = mAmState.getHomeActivityName();
-        assertNotNull(name);
-        assertVisibility(name, getWindowNameForActivityName(name), visible);
-    }
-
-    /**
-     * Asserts that the device default display minimim width is larger than the minimum task width.
-     */
-    void assertDeviceDefaultDisplaySize(ITestDevice device, String errorMessage) throws Exception {
-        computeState(device, null);
-        final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY_ID);
-        final Display display = getWmState().getDisplay(DEFAULT_DISPLAY_ID);
-        final Rectangle displayRect = display.getDisplayRect();
-        if (Math.min(displayRect.width, displayRect.height) < minTaskSizePx) {
-            fail(errorMessage);
-        }
-    }
-
-    private String getWindowNameForActivityName(String activityName) {
-        return activityName.replaceAll("(.*)\\/\\.", "$1/$1.");
-    }
-
-    boolean taskListsInAmAndWmAreEqual() {
-        for (ActivityStack aStack : mAmState.getStacks()) {
-            final int stackId = aStack.mStackId;
-            final WindowStack wStack = mWmState.getStack(stackId);
-            if (wStack == null) {
-                log("Waiting for stack setup in WM, stackId=" + stackId);
-                return false;
-            }
-
-            for (ActivityTask aTask : aStack.getTasks()) {
-                if (wStack.getTask(aTask.mTaskId) == null) {
-                    log("Task is in AM but not in WM, waiting for it to settle, taskId="
-                            + aTask.mTaskId);
-                    return false;
-                }
-            }
-
-            for (WindowTask wTask : wStack.mTasks) {
-                if (aStack.getTask(wTask.mTaskId) == null) {
-                    log("Task is in WM but not in AM, waiting for it to settle, taskId="
-                            + wTask.mTaskId);
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    int getStackPosition(int stackId) {
-        int wmStackIndex = mWmState.getStackPosition(stackId);
-        int amStackIndex = mAmState.getStackPosition(stackId);
-        assertEquals("Window and activity manager must have the same stack position index",
-                amStackIndex, wmStackIndex);
-        return wmStackIndex;
-    }
-
-    boolean stackBoundsInAMAndWMAreEqual() {
-        for (ActivityStack aStack : mAmState.getStacks()) {
-            final int stackId = aStack.mStackId;
-            final WindowStack wStack = mWmState.getStack(stackId);
-            if (aStack.isFullscreen() != wStack.isFullscreen()) {
-                log("Waiting for correct fullscreen state, stackId=" + stackId);
-                return false;
-            }
-
-            final Rectangle aStackBounds = aStack.getBounds();
-            final Rectangle wStackBounds = wStack.getBounds();
-
-            if (aStack.isFullscreen()) {
-                if (aStackBounds != null) {
-                    log("Waiting for correct stack state in AM, stackId=" + stackId);
-                    return false;
-                }
-            } else if (!Objects.equals(aStackBounds, wStackBounds)) {
-                // If stack is not fullscreen - comparing bounds. Not doing it always because
-                // for fullscreen stack bounds in WM can be either null or equal to display size.
-                log("Waiting for stack bound equality in AM and WM, stackId=" + stackId);
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /** Check task bounds when docked to top/left. */
-    void assertDockedTaskBounds(int taskWidth, int taskHeight, String activityName) {
-        // Task size can be affected by default minimal size.
-        int defaultMinimalTaskSize = defaultMinimalTaskSize(
-                mAmState.getStackById(ActivityManagerTestBase.DOCKED_STACK_ID).mDisplayId);
-        int targetWidth = Math.max(taskWidth, defaultMinimalTaskSize);
-        int targetHeight = Math.max(taskHeight, defaultMinimalTaskSize);
-
-        assertEquals(new Rectangle(0, 0, targetWidth, targetHeight),
-                mAmState.getTaskByActivityName(activityName).getBounds());
-    }
-
-    void assertValidBounds(boolean compareTaskAndStackBounds) {
-        // Cycle through the stacks and tasks to figure out if the home stack is resizable
-        final ActivityTask homeTask = mAmState.getHomeTask();
-        final boolean homeStackIsResizable = homeTask != null
-                && homeTask.getResizeMode().equals(RESIZE_MODE_RESIZEABLE);
-
-        for (ActivityStack aStack : mAmState.getStacks()) {
-            final int stackId = aStack.mStackId;
-            final WindowStack wStack = mWmState.getStack(stackId);
-            assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack);
-
-            assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId,
-                    aStack.isFullscreen(), wStack.isFullscreen());
-
-            final Rectangle aStackBounds = aStack.getBounds();
-            final Rectangle wStackBounds = wStack.getBounds();
-
-            if (aStack.isFullscreen()) {
-                assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds);
-            } else {
-                assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId,
-                        aStackBounds, wStackBounds);
-            }
-
-            for (ActivityTask aTask : aStack.getTasks()) {
-                final int taskId = aTask.mTaskId;
-                final WindowTask wTask = wStack.getTask(taskId);
-                assertNotNull(
-                        "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask);
-
-                final boolean aTaskIsFullscreen = aTask.isFullscreen();
-                final boolean wTaskIsFullscreen = wTask.isFullscreen();
-                assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId
-                        + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen);
-
-                final Rectangle aTaskBounds = aTask.getBounds();
-                final Rectangle wTaskBounds = wTask.getBounds();
-                final Rectangle displayRect = mWmState.getDisplay(aStack.mDisplayId)
-                        .getDisplayRect();
-
-                if (aTaskIsFullscreen) {
-                    assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
-                            aTaskBounds);
-                } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized()
-                        && displayRect.getWidth() > displayRect.getHeight()) {
-                    // When minimized using non-resizable launcher in landscape mode, it will move
-                    // the task offscreen in the negative x direction unlike portrait that crops.
-                    // The x value in the task bounds will not match the stack bounds since the
-                    // only the task was moved.
-                    assertEquals("Task bounds in AM and WM must match width taskId=" + taskId
-                            + ", stackId" + stackId, aTaskBounds.getWidth(),
-                            wTaskBounds.getWidth());
-                    assertEquals("Task bounds in AM and WM must match height taskId=" + taskId
-                                    + ", stackId" + stackId, aTaskBounds.getHeight(),
-                            wTaskBounds.getHeight());
-                    assertEquals("Task bounds must match stack bounds y taskId=" + taskId
-                                    + ", stackId" + stackId, aTaskBounds.getY(),
-                            wTaskBounds.getY());
-                    assertEquals("Task and stack bounds must match width taskId=" + taskId
-                                    + ", stackId" + stackId, aStackBounds.getWidth(),
-                            wTaskBounds.getWidth());
-                    assertEquals("Task and stack bounds must match height taskId=" + taskId
-                                    + ", stackId" + stackId, aStackBounds.getHeight(),
-                            wTaskBounds.getHeight());
-                    assertEquals("Task and stack bounds must match y taskId=" + taskId
-                                    + ", stackId" + stackId, aStackBounds.getY(),
-                            wTaskBounds.getY());
-                } else {
-                    assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
-                            + ", stackId=" + stackId, aTaskBounds, wTaskBounds);
-
-                    if (compareTaskAndStackBounds && stackId != FREEFORM_WORKSPACE_STACK_ID) {
-                        int aTaskMinWidth = aTask.getMinWidth();
-                        int aTaskMinHeight = aTask.getMinHeight();
-
-                        if (aTaskMinWidth == -1 || aTaskMinHeight == -1) {
-                            // Minimal dimension(s) not set for task - it should be using defaults.
-                            int defaultMinimalSize = (stackId == PINNED_STACK_ID)
-                                    ? defaultMinimalPinnedTaskSize(aStack.mDisplayId)
-                                    : defaultMinimalTaskSize(aStack.mDisplayId);
-
-                            if (aTaskMinWidth == -1) {
-                                aTaskMinWidth = defaultMinimalSize;
-                            }
-                            if (aTaskMinHeight == -1) {
-                                aTaskMinHeight = defaultMinimalSize;
-                            }
-                        }
-
-                        if (aStackBounds.getWidth() >= aTaskMinWidth
-                                && aStackBounds.getHeight() >= aTaskMinHeight
-                                || stackId == PINNED_STACK_ID) {
-                            // Bounds are not smaller then minimal possible, so stack and task
-                            // bounds must be equal.
-                            assertEquals("Task bounds must be equal to stack bounds taskId="
-                                    + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
-                        } else if (stackId == DOCKED_STACK_ID && homeStackIsResizable
-                                && mWmState.isDockedStackMinimized()) {
-                            // Portrait if the display height is larger than the width
-                            if (displayRect.getHeight() > displayRect.getWidth()) {
-                                assertEquals("Task width must be equal to stack width taskId="
-                                        + taskId + ", stackId=" + stackId,
-                                        aStackBounds.getWidth(), wTaskBounds.getWidth());
-                                assertTrue("Task height must be greater than stack height "
-                                        + "taskId=" + taskId + ", stackId=" + stackId,
-                                        aStackBounds.getHeight() < wTaskBounds.getHeight());
-                                assertEquals("Task and stack x position must be equal taskId="
-                                        + taskId + ", stackId=" + stackId,
-                                        wTaskBounds.getX(), wStackBounds.getX());
-                            } else {
-                                assertTrue("Task width must be greater than stack width taskId="
-                                        + taskId + ", stackId=" + stackId,
-                                        aStackBounds.getWidth() < wTaskBounds.getWidth());
-                                assertEquals("Task height must be equal to stack height taskId="
-                                        + taskId + ", stackId=" + stackId,
-                                        aStackBounds.getHeight(), wTaskBounds.getHeight());
-                                assertEquals("Task and stack y position must be equal taskId="
-                                        + taskId + ", stackId=" + stackId, wTaskBounds.getY(),
-                                        wStackBounds.getY());
-                            }
-                        } else {
-                            // Minimal dimensions affect task size, so bounds of task and stack must
-                            // be different - will compare dimensions instead.
-                            int targetWidth = (int) Math.max(aTaskMinWidth,
-                                    aStackBounds.getWidth());
-                            assertEquals("Task width must be set according to minimal width"
-                                            + " taskId=" + taskId + ", stackId=" + stackId,
-                                    targetWidth, (int) wTaskBounds.getWidth());
-                            int targetHeight = (int) Math.max(aTaskMinHeight,
-                                    aStackBounds.getHeight());
-                            assertEquals("Task height must be set according to minimal height"
-                                            + " taskId=" + taskId + ", stackId=" + stackId,
-                                    targetHeight, (int) wTaskBounds.getHeight());
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    static int dpToPx(float dp, int densityDpi){
-        return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
-    }
-
-    private int defaultMinimalTaskSize(int displayId) {
-        return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
-    }
-
-    private int defaultMinimalPinnedTaskSize(int displayId) {
-        return dpToPx(DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
deleted file mode 100644
index fa79c5e..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
+++ /dev/null
@@ -1,910 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.awt.Rectangle;
-import java.lang.Integer;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.RECENTS_STACK_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-class ActivityManagerState {
-    public static final int DUMP_MODE_ACTIVITIES = 0;
-
-    public static final String STATE_RESUMED = "RESUMED";
-    public static final String STATE_PAUSED = "PAUSED";
-    public static final String STATE_STOPPED = "STOPPED";
-    public static final String STATE_DESTROYED = "DESTROYED";
-
-    public static final String RESIZE_MODE_RESIZEABLE = "RESIZE_MODE_RESIZEABLE";
-
-    private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities";
-
-    // Copied from ActivityRecord.java
-    private static final int APPLICATION_ACTIVITY_TYPE = 0;
-    private static final int HOME_ACTIVITY_TYPE = 1;
-    private static final int RECENTS_ACTIVITY_TYPE = 2;
-
-    private final Pattern mDisplayIdPattern = Pattern.compile("Display #(\\d+).*");
-    private final Pattern mStackIdPattern = Pattern.compile("Stack #(\\d+)\\:");
-    private final Pattern mResumedActivityPattern =
-            Pattern.compile("ResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
-    private final Pattern mFocusedStackPattern =
-            Pattern.compile("mFocusedStack=ActivityStack\\{(.+) stackId=(\\d+), (.+)\\}(.+)");
-
-    private final Pattern[] mExtractStackExitPatterns =
-            { mStackIdPattern, mResumedActivityPattern, mFocusedStackPattern, mDisplayIdPattern };
-
-    // 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();
-    // Stacks on all attached displays, in z-order with the top most at the front of the list.
-    private final Map<Integer, List<ActivityStack>> mDisplayStacks = new HashMap<>();
-    private KeyguardControllerState mKeyguardControllerState;
-    private int mFocusedStackId = -1;
-    private String mResumedActivityRecord = null;
-    private final List<String> mResumedActivities = new ArrayList();
-    private final LinkedList<String> mSysDump = new LinkedList();
-
-    void computeState(ITestDevice device) throws DeviceNotAvailableException {
-        computeState(device, DUMP_MODE_ACTIVITIES);
-    }
-
-    void computeState(ITestDevice device, int dumpMode) throws DeviceNotAvailableException {
-        // It is possible the system is in the middle of transition to the right state when we get
-        // the dump. We try a few times to get the information we need before giving up.
-        int retriesLeft = 3;
-        boolean retry = false;
-        String dump = null;
-
-        log("==============================");
-        log("     ActivityManagerState     ");
-        log("==============================");
-
-        do {
-            if (retry) {
-                log("***Incomplete AM state. Retrying...");
-                // Wait half a second between retries for activity manager to finish transitioning.
-                try {
-                    Thread.sleep(500);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            }
-
-            final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-            String dumpsysCmd = "";
-            switch (dumpMode) {
-                case DUMP_MODE_ACTIVITIES:
-                    dumpsysCmd = DUMPSYS_ACTIVITY_ACTIVITIES; break;
-            }
-            device.executeShellCommand(dumpsysCmd, outputReceiver);
-            dump = outputReceiver.getOutput();
-            parseSysDump(dump);
-
-            retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
-                    || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
-        } while (retry && retriesLeft-- > 0);
-
-        if (retry) {
-            log(dump);
-        }
-
-        if (mStacks.isEmpty()) {
-            logE("No stacks found...");
-        }
-        if (mFocusedStackId == -1) {
-            logE("No focused stack found...");
-        }
-        if (mResumedActivityRecord == null) {
-            logE("No focused activity found...");
-        }
-        if (mResumedActivities.isEmpty()) {
-            logE("No resumed activities found...");
-        }
-    }
-
-    private void parseSysDump(String sysDump) {
-        reset();
-
-        Collections.addAll(mSysDump, sysDump.split("\\n"));
-
-        int currentDisplayId = 0;
-        while (!mSysDump.isEmpty()) {
-            final ActivityStack stack = ActivityStack.create(mSysDump, mStackIdPattern,
-                    mExtractStackExitPatterns, currentDisplayId);
-
-            if (stack != null) {
-                mStacks.add(stack);
-                mDisplayStacks.get(currentDisplayId).add(stack);
-                if (stack.mResumedActivity != null) {
-                    mResumedActivities.add(stack.mResumedActivity);
-                }
-                continue;
-            }
-
-            KeyguardControllerState controller = KeyguardControllerState.create(
-                    mSysDump, new Pattern[0]);
-            if (controller != null) {
-                mKeyguardControllerState = controller;
-                continue;
-            }
-
-            final String line = mSysDump.pop().trim();
-
-            Matcher matcher = mFocusedStackPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String stackId = matcher.group(2);
-                log(stackId);
-                mFocusedStackId = Integer.parseInt(stackId);
-                continue;
-            }
-
-            matcher = mResumedActivityPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mResumedActivityRecord = matcher.group(3);
-                log(mResumedActivityRecord);
-                continue;
-            }
-
-            matcher = mDisplayIdPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String displayId = matcher.group(1);
-                log(displayId);
-                currentDisplayId = Integer.parseInt(displayId);
-                mDisplayStacks.put(currentDisplayId, new ArrayList<>());
-            }
-        }
-    }
-
-    private void reset() {
-        mStacks.clear();
-        mFocusedStackId = -1;
-        mResumedActivityRecord = null;
-        mResumedActivities.clear();
-        mSysDump.clear();
-        mKeyguardControllerState = null;
-    }
-
-    int getFrontStackId(int displayId) {
-        return mDisplayStacks.get(displayId).get(0).mStackId;
-    }
-
-    int getFocusedStackId() {
-        return mFocusedStackId;
-    }
-
-    String getFocusedActivity() {
-        return mResumedActivityRecord;
-    }
-
-    String getResumedActivity() {
-        return mResumedActivities.get(0);
-    }
-
-    int getResumedActivitiesCount() {
-        return mResumedActivities.size();
-    }
-
-    public KeyguardControllerState getKeyguardControllerState() {
-        return mKeyguardControllerState;
-    }
-
-    boolean containsStack(int stackId) {
-        return getStackById(stackId) != null;
-    }
-
-    ActivityStack getStackById(int stackId) {
-        for (ActivityStack stack : mStacks) {
-            if (stackId == stack.mStackId) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
-    int getStackPosition(int stackId) {
-        for (Integer displayId : mDisplayStacks.keySet()) {
-            List<ActivityStack> stacks = mDisplayStacks.get(displayId);
-            for (int i = 0; i < stacks.size(); i++) {
-                if (stackId == stacks.get(i).mStackId) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    List<ActivityStack> getStacks() {
-        return new ArrayList(mStacks);
-    }
-
-    int getStackCount() {
-        return mStacks.size();
-    }
-
-    boolean containsActivity(String activityName) {
-        for (ActivityStack stack : mStacks) {
-            for (ActivityTask task : stack.mTasks) {
-                for (Activity activity : task.mActivities) {
-                    if (activity.name.equals(activityName)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    boolean isActivityVisible(String activityName) {
-        for (ActivityStack stack : mStacks) {
-            for (ActivityTask task : stack.mTasks) {
-               for (Activity activity : task.mActivities) {
-                   if (activity.name.equals(activityName)) {
-                       return activity.visible;
-                   }
-               }
-            }
-        }
-        return false;
-    }
-
-    boolean containsStartedActivities() {
-        for (ActivityStack stack : mStacks) {
-            for (ActivityTask task : stack.mTasks) {
-                for (Activity activity : task.mActivities) {
-                    if (!activity.state.equals(STATE_STOPPED)
-                            && !activity.state.equals(STATE_DESTROYED)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    boolean hasActivityState(String activityName, String activityState) {
-        String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
-        for (ActivityStack stack : mStacks) {
-            for (ActivityTask task : stack.mTasks) {
-                for (Activity activity : task.mActivities) {
-                    if (activity.name.equals(fullName)) {
-                        return activity.state.equals(activityState);
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    int getActivityProcId(String activityName) {
-        for (ActivityStack stack : mStacks) {
-            for (ActivityTask task : stack.mTasks) {
-               for (Activity activity : task.mActivities) {
-                   if (activity.name.equals(activityName)) {
-                       return activity.procId;
-                   }
-               }
-            }
-        }
-        return -1;
-    }
-
-    boolean isHomeActivityVisible() {
-        final Activity homeActivity = getHomeActivity();
-        return homeActivity != null && homeActivity.visible;
-    }
-
-    boolean isRecentsActivityVisible() {
-        final Activity recentsActivity = getRecentsActivity();
-        return recentsActivity != null && recentsActivity.visible;
-    }
-
-    String getHomeActivityName() {
-        Activity activity = getHomeActivity();
-        if (activity == null) {
-            return null;
-        }
-        return activity.name;
-    }
-
-    ActivityTask getHomeTask() {
-        ActivityStack homeStack = getStackById(HOME_STACK_ID);
-        if (homeStack != null) {
-            for (ActivityTask task : homeStack.mTasks) {
-                if (task.mTaskType == HOME_ACTIVITY_TYPE) {
-                    return task;
-                }
-            }
-            return null;
-        }
-        return null;
-    }
-
-    ActivityTask getRecentsTask() {
-        ActivityStack recentsStack = getStackById(RECENTS_STACK_ID);
-        if (recentsStack != null) {
-            for (ActivityTask task : recentsStack.mTasks) {
-                if (task.mTaskType == RECENTS_ACTIVITY_TYPE) {
-                    return task;
-                }
-            }
-            return null;
-        }
-        return null;
-    }
-
-    private Activity getHomeActivity() {
-        final ActivityTask homeTask = getHomeTask();
-        return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
-    }
-
-    private Activity getRecentsActivity() {
-        final ActivityTask recentsTask = getRecentsTask();
-        return recentsTask != null ? recentsTask.mActivities.get(recentsTask.mActivities.size() - 1)
-                : null;
-    }
-
-    ActivityTask getTaskByActivityName(String activityName) {
-        return getTaskByActivityName(activityName, -1);
-    }
-
-    ActivityTask getTaskByActivityName(String activityName, int stackId) {
-        String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
-        for (ActivityStack stack : mStacks) {
-            if (stackId == -1 || stackId == stack.mStackId) {
-                for (ActivityTask task : stack.mTasks) {
-                    for (Activity activity : task.mActivities) {
-                        if (activity.name.equals(fullName)) {
-                            return task;
-                        }
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    static class ActivityStack extends ActivityContainer {
-
-        private static final Pattern TASK_ID_PATTERN = Pattern.compile("Task id #(\\d+)");
-        private static final Pattern RESUMED_ACTIVITY_PATTERN = Pattern.compile(
-                "mResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
-        private static final Pattern SLEEPING_PATTERN = Pattern.compile("isSleeping=(\\S+)");
-
-        int mDisplayId;
-        int mStackId;
-        String mResumedActivity;
-        Boolean mSleeping; // A Boolean to trigger an NPE if it's not initialized
-        ArrayList<ActivityTask> mTasks = new ArrayList();
-
-        private ActivityStack() {
-        }
-
-        static ActivityStack create(LinkedList<String> dump, Pattern stackIdPattern,
-                                    Pattern[] exitPatterns, int displayId) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = stackIdPattern.matcher(line);
-            if (!matcher.matches()) {
-                // Not a stack.
-                return null;
-            }
-            // For the stack Id line we just read.
-            dump.pop();
-
-            final ActivityStack stack = new ActivityStack();
-            stack.mDisplayId = displayId;
-            log(line);
-            final String stackId = matcher.group(1);
-            log(stackId);
-            stack.mStackId = Integer.parseInt(stackId);
-            stack.extract(dump, exitPatterns);
-            return stack;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
-            final List<Pattern> taskExitPatterns = new ArrayList();
-            Collections.addAll(taskExitPatterns, exitPatterns);
-            taskExitPatterns.add(TASK_ID_PATTERN);
-            taskExitPatterns.add(RESUMED_ACTIVITY_PATTERN);
-            final Pattern[] taskExitPatternsArray =
-                    taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
-
-            while (!doneExtracting(dump, exitPatterns)) {
-                final ActivityTask task =
-                        ActivityTask.create(dump, TASK_ID_PATTERN, taskExitPatternsArray);
-
-                if (task != null) {
-                    mTasks.add(task);
-                    continue;
-                }
-
-                final String line = dump.pop().trim();
-
-                if (extractFullscreen(line)) {
-                    continue;
-                }
-
-                if (extractBounds(line)) {
-                    continue;
-                }
-
-                Matcher matcher = RESUMED_ACTIVITY_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mResumedActivity = matcher.group(3);
-                    log(mResumedActivity);
-                    continue;
-                }
-
-                matcher = SLEEPING_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mSleeping = "true".equals(matcher.group(1));
-                    continue;
-                }
-            }
-        }
-
-        /**
-         * @return the bottom task in the stack.
-         */
-        ActivityTask getBottomTask() {
-            if (!mTasks.isEmpty()) {
-                // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
-                //       so the indices are inverted
-                return mTasks.get(mTasks.size() - 1);
-            }
-            return null;
-        }
-
-        /**
-         * @return the top task in the stack.
-         */
-        ActivityTask getTopTask() {
-            if (!mTasks.isEmpty()) {
-                // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
-                //       so the indices are inverted
-                return mTasks.get(0);
-            }
-            return null;
-        }
-
-        List<ActivityTask> getTasks() {
-            return new ArrayList(mTasks);
-        }
-
-        ActivityTask getTask(int taskId) {
-            for (ActivityTask task : mTasks) {
-                if (taskId == task.mTaskId) {
-                    return task;
-                }
-            }
-            return null;
-        }
-    }
-
-    static class ActivityTask extends ActivityContainer {
-        private static final Pattern TASK_RECORD_PATTERN = Pattern.compile("\\* TaskRecord\\"
-                + "{(\\S+) #(\\d+) (\\S+)=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
-
-        private static final Pattern LAST_NON_FULLSCREEN_BOUNDS_PATTERN = Pattern.compile(
-                "mLastNonFullscreenBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
-
-        private static final Pattern ORIG_ACTIVITY_PATTERN = Pattern.compile("origActivity=(\\S+)");
-        private static final Pattern REAL_ACTIVITY_PATTERN = Pattern.compile("realActivity=(\\S+)");
-
-        private static final Pattern ACTIVITY_NAME_PATTERN = Pattern.compile(
-                "\\* Hist #(\\d+)\\: ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}");
-
-        private static final Pattern TASK_TYPE_PATTERN = Pattern.compile("autoRemoveRecents=(\\S+) "
-                + "isPersistable=(\\S+) numFullscreen=(\\d+) taskType=(\\d+) "
-                + "mTaskToReturnTo=(\\d+)");
-
-        private static final Pattern RESIZABLE_PATTERN = Pattern.compile(
-                ".*mResizeMode=([^\\s]+).*");
-
-        int mTaskId;
-        int mStackId;
-        Rectangle mLastNonFullscreenBounds;
-        String mRealActivity;
-        String mOrigActivity;
-        ArrayList<Activity> mActivities = new ArrayList();
-        int mTaskType = -1;
-        int mReturnToType = -1;
-        private String mResizeMode;
-
-        private ActivityTask() {
-        }
-
-        static ActivityTask create(
-                LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = taskIdPattern.matcher(line);
-            if (!matcher.matches()) {
-                // Not a task.
-                return null;
-            }
-            // For the task Id line we just read.
-            dump.pop();
-
-            final ActivityTask task = new ActivityTask();
-            log(line);
-            final String taskId = matcher.group(1);
-            log(taskId);
-            task.mTaskId = Integer.parseInt(taskId);
-            task.extract(dump, exitPatterns);
-            return task;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-            final List<Pattern> activityExitPatterns = new ArrayList();
-            Collections.addAll(activityExitPatterns, exitPatterns);
-            activityExitPatterns.add(ACTIVITY_NAME_PATTERN);
-            final Pattern[] activityExitPatternsArray =
-                    activityExitPatterns.toArray(new Pattern[activityExitPatterns.size()]);
-
-            while (!doneExtracting(dump, exitPatterns)) {
-                final Activity activity =
-                        Activity.create(dump, ACTIVITY_NAME_PATTERN, activityExitPatternsArray);
-
-                if (activity != null) {
-                    mActivities.add(activity);
-                    continue;
-                }
-
-                final String line = dump.pop().trim();
-
-                if (extractFullscreen(line)) {
-                    continue;
-                }
-
-                if (extractBounds(line)) {
-                    continue;
-                }
-
-                if (extractMinimalSize(line)) {
-                    continue;
-                }
-
-                Matcher matcher = TASK_RECORD_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String stackId = matcher.group(6);
-                    mStackId = Integer.valueOf(stackId);
-                    log(stackId);
-                    continue;
-                }
-
-                matcher = LAST_NON_FULLSCREEN_BOUNDS_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mLastNonFullscreenBounds = extractBounds(matcher);
-                }
-
-                matcher = REAL_ACTIVITY_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    if (mRealActivity == null) {
-                        log(line);
-                        mRealActivity = matcher.group(1);
-                        log(mRealActivity);
-                    }
-                    continue;
-                }
-
-                matcher = ORIG_ACTIVITY_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    if (mOrigActivity == null) {
-                        log(line);
-                        mOrigActivity = matcher.group(1);
-                        log(mOrigActivity);
-                    }
-                    continue;
-                }
-
-                matcher = TASK_TYPE_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mTaskType = Integer.valueOf(matcher.group(4));
-                    mReturnToType = Integer.valueOf(matcher.group(5));
-                    continue;
-                }
-
-                matcher = RESIZABLE_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mResizeMode = matcher.group(1);
-                    log(mResizeMode);
-                    continue;
-                }
-            }
-        }
-
-        public String getResizeMode() {
-            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;
-        }
-    }
-
-    static class Activity {
-        private static final Pattern STATE_PATTERN = Pattern.compile("state=(\\S+).*");
-        private static final Pattern VISIBILITY_PATTERN = Pattern.compile("keysPaused=(\\S+) "
-                + "inHistory=(\\S+) visible=(\\S+) sleeping=(\\S+) idle=(\\S+) "
-                + "mStartingWindowState=(\\S+)");
-        private static final Pattern FRONT_OF_TASK_PATTERN = Pattern.compile("frontOfTask=(\\S+) "
-                + "task=TaskRecord\\{(\\S+) #(\\d+) A=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
-        private static final Pattern PROCESS_RECORD_PATTERN = Pattern.compile(
-                "app=ProcessRecord\\{(\\S+) (\\d+):(\\S+)/(.+)\\}");
-
-        String name;
-        String state;
-        boolean visible;
-        boolean frontOfTask;
-        int procId = -1;
-
-        private Activity() {
-        }
-
-        static Activity create(
-                LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = activityNamePattern.matcher(line);
-            if (!matcher.matches()) {
-                // Not an activity.
-                return null;
-            }
-            // For the activity name line we just read.
-            dump.pop();
-
-            final Activity activity = new Activity();
-            log(line);
-            activity.name = matcher.group(4);
-            log(activity.name);
-            activity.extract(dump, exitPatterns);
-            return activity;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
-            while (!doneExtracting(dump, exitPatterns)) {
-                final String line = dump.pop().trim();
-
-                // Break the activity extraction once we hit an empty line
-                if (line.isEmpty()) {
-                    break;
-                }
-
-                Matcher matcher = VISIBILITY_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String visibleString = matcher.group(3);
-                    visible = Boolean.valueOf(visibleString);
-                    log(visibleString);
-                    continue;
-                }
-
-                matcher = STATE_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    state = matcher.group(1);
-                    log(state);
-                    continue;
-                }
-
-                matcher = PROCESS_RECORD_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String procIdString = matcher.group(2);
-                    procId = Integer.valueOf(procIdString);
-                    log(procIdString);
-                    continue;
-                }
-
-                matcher = FRONT_OF_TASK_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String frontOfTaskString = matcher.group(1);
-                    frontOfTask = Boolean.valueOf(frontOfTaskString);
-                    log(frontOfTaskString);
-                    continue;
-                }
-            }
-        }
-    }
-
-    static abstract class ActivityContainer {
-        protected static final Pattern FULLSCREEN_PATTERN = Pattern.compile("mFullscreen=(\\S+)");
-        protected static final Pattern BOUNDS_PATTERN =
-                Pattern.compile("mBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
-        protected static final Pattern MIN_WIDTH_PATTERN =
-                Pattern.compile("mMinWidth=(\\d+)");
-        protected static final Pattern MIN_HEIGHT_PATTERN =
-                Pattern.compile("mMinHeight=(\\d+)");
-
-        protected boolean mFullscreen;
-        protected Rectangle mBounds;
-        protected int mMinWidth = -1;
-        protected int mMinHeight = -1;
-
-        boolean extractFullscreen(String line) {
-            final Matcher matcher = FULLSCREEN_PATTERN.matcher(line);
-            if (!matcher.matches()) {
-                return false;
-            }
-            log(line);
-            final String fullscreen = matcher.group(1);
-            log(fullscreen);
-            mFullscreen = Boolean.valueOf(fullscreen);
-            return true;
-        }
-
-        boolean extractBounds(String line) {
-            final Matcher matcher = BOUNDS_PATTERN.matcher(line);
-            if (!matcher.matches()) {
-                return false;
-            }
-            log(line);
-            mBounds = extractBounds(matcher);
-            return true;
-        }
-
-        static Rectangle extractBounds(Matcher matcher) {
-            final int left = Integer.valueOf(matcher.group(1));
-            final int top = Integer.valueOf(matcher.group(2));
-            final int right = Integer.valueOf(matcher.group(3));
-            final int bottom = Integer.valueOf(matcher.group(4));
-            final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
-
-            log(rect.toString());
-            return rect;
-        }
-
-        boolean extractMinimalSize(String line) {
-            final Matcher minWidthMatcher = MIN_WIDTH_PATTERN.matcher(line);
-            final Matcher minHeightMatcher = MIN_HEIGHT_PATTERN.matcher(line);
-
-            if (minWidthMatcher.matches()) {
-                log(line);
-                mMinWidth = Integer.valueOf(minWidthMatcher.group(1));
-            } else if (minHeightMatcher.matches()) {
-                log(line);
-                mMinHeight = Integer.valueOf(minHeightMatcher.group(1));
-            } else {
-                return false;
-            }
-            return true;
-        }
-
-        Rectangle getBounds() {
-            return mBounds;
-        }
-
-        boolean isFullscreen() {
-            return mFullscreen;
-        }
-
-        int getMinWidth() {
-            return mMinWidth;
-        }
-
-        int getMinHeight() {
-            return mMinHeight;
-        }
-    }
-
-    static class KeyguardControllerState {
-        private static final Pattern NAME_PATTERN = Pattern.compile("KeyguardController:");
-        private static final Pattern SHOWING_PATTERN = Pattern.compile("mKeyguardShowing=(\\S+)");
-        private static final Pattern OCCLUDED_PATTERN = Pattern.compile("mOccluded=(\\S+)");
-
-        boolean keyguardShowing;
-        boolean keyguardOccluded;
-
-        private KeyguardControllerState() {
-        }
-
-        static KeyguardControllerState create(LinkedList<String> dump, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = NAME_PATTERN.matcher(line);
-            if (!matcher.matches()) {
-                // Not KeyguardController
-                return null;
-            }
-
-            // For the KeyguardController line we just read.
-            dump.pop();
-
-            final KeyguardControllerState controller = new KeyguardControllerState();
-            controller.extract(dump, exitPatterns);
-            return controller;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
-            while (!doneExtracting(dump, exitPatterns)) {
-                final String line = dump.pop().trim();
-
-                Matcher matcher = SHOWING_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String showingString = matcher.group(1);
-                    keyguardShowing = Boolean.valueOf(showingString);
-                    log(showingString);
-                    continue;
-                }
-
-                matcher = OCCLUDED_PATTERN.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String occludedString = matcher.group(1);
-                    keyguardOccluded = Boolean.valueOf(occludedString);
-                    log(occludedString);
-                    continue;
-                }
-            }
-        }
-    }
-
-    static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
-        if (dump.isEmpty()) {
-            return true;
-        }
-        final String line = dump.peek().trim();
-
-        for (Pattern pattern : exitPatterns) {
-            if (pattern.matcher(line).matches()) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
deleted file mode 100644
index 9ba292f..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ /dev/null
@@ -1,1459 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-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;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.awt.image.BufferedImage;
-import java.lang.Exception;
-import java.lang.Integer;
-import java.lang.String;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-
-import javax.imageio.ImageIO;
-
-public abstract class ActivityManagerTestBase extends DeviceTestCase {
-    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";
-
-    // Constants copied from ActivityManager.StackId. If they are changed there, these must be
-    // updated.
-    /** Invalid stack ID. */
-    public static final int INVALID_STACK_ID = -1;
-
-    /** First static stack ID. */
-    public static final int FIRST_STATIC_STACK_ID = 0;
-
-    /** Home activity stack ID. */
-    public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
-
-    /** ID of stack where fullscreen activities are normally launched into. */
-    public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
-    /** ID of stack where freeform/resized activities are normally launched into. */
-    public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
-    /** ID of stack that occupies a dedicated region of the screen. */
-    public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
-    /** ID of stack that always on top (always visible) when it exist. */
-    public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
-    /** Recents activity stack ID. */
-    public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
-
-    /** Assistant activity stack ID.  This stack is fullscreen and non-resizeable. */
-    public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
-
-    protected static final int[] ALL_STACK_IDS_BUT_HOME = {
-            FULLSCREEN_WORKSPACE_STACK_ID, FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID,
-            PINNED_STACK_ID, ASSISTANT_STACK_ID
-    };
-
-    protected static final int[] ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN = {
-            FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID, PINNED_STACK_ID, ASSISTANT_STACK_ID
-    };
-
-    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.cts";
-    private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
-            = "am force-stop android.server.cts.second";
-    private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
-            = "am force-stop android.server.cts.third";
-
-    private static final String AM_REMOVE_STACK = "am stack remove ";
-
-    protected static final String AM_START_HOME_ACTIVITY_COMMAND =
-            "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
-
-    protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
-            "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
-
-    static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
-    static final String ALT_LAUNCHING_ACTIVITY = "AltLaunchingActivity";
-    static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
-
-    /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
-    static final String FINISH_ACTIVITY_BROADCAST
-            = "am broadcast -a trigger_broadcast --ez finish true";
-
-    /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
-    static final String MOVE_TASK_TO_BACK_BROADCAST
-            = "am broadcast -a trigger_broadcast --ez moveToBack true";
-
-    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_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
-            "am supports-split-screen-multi-window";
-    private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
-
-    private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
-    private static final String INPUT_KEYEVENT_BACK = "input keyevent 4";
-    private static final String INPUT_KEYEVENT_APP_SWITCH = "input keyevent 187";
-    public static final String INPUT_KEYEVENT_WINDOW = "input keyevent 171";
-
-    private static final String LOCK_CREDENTIAL = "1234";
-
-    private static final int INVALID_DISPLAY_ID = -1;
-
-    private static final String DEFAULT_COMPONENT_NAME = "android.server.cts";
-
-    private static final int UI_MODE_TYPE_MASK = 0x0f;
-    private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
-
-    static String componentName = DEFAULT_COMPONENT_NAME;
-
-    protected static final int INVALID_DEVICE_ROTATION = -1;
-
-    /** A reference to the device under test. */
-    protected ITestDevice mDevice;
-
-    private HashSet<String> mAvailableFeatures;
-
-    private boolean mLockCredentialsSet;
-
-    private boolean mLockDisabled;
-
-    protected static String getAmStartCmd(final String activityName) {
-        return "am start -n " + getActivityComponentName(activityName);
-    }
-
-    /**
-     * @return the am command to start the given activity with the following extra key/value pairs.
-     *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
-     */
-    protected static String getAmStartCmd(final String activityName,
-            final String... keyValuePairs) {
-        String base = getAmStartCmd(activityName);
-        if (keyValuePairs.length % 2 != 0) {
-            throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
-        }
-        for (int i = 0; i < keyValuePairs.length; i += 2) {
-            base += " --es " + keyValuePairs[i] + " " + keyValuePairs[i + 1];
-        }
-        return base;
-    }
-
-    protected static String getAmStartCmd(final String activityName, final int displayId) {
-        return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
-                + " --display " + displayId;
-    }
-
-    protected static String getAmStartCmdInNewTask(final String activityName) {
-        return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000";
-    }
-
-    protected static String getAmStartCmdOverHome(final String activityName) {
-        return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
-    }
-
-    protected static String getOrientationBroadcast(int orientation) {
-        return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
-    }
-
-    static String getActivityComponentName(final String activityName) {
-        return getActivityComponentName(componentName, activityName);
-    }
-
-    private static boolean isFullyQualifiedActivityName(String name) {
-        return name != null && name.contains(".");
-    }
-
-    static String getActivityComponentName(final String packageName, final String activityName) {
-        return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
-                activityName;
-    }
-
-    // A little ugly, but lets avoid having to strip static everywhere for
-    // now.
-    public static void setComponentName(String name) {
-        componentName = name;
-    }
-
-    protected static void setDefaultComponentName() {
-        setComponentName(DEFAULT_COMPONENT_NAME);
-    }
-
-    static String getBaseWindowName() {
-        return getBaseWindowName(componentName);
-    }
-
-    static String getBaseWindowName(final String packageName) {
-        return getBaseWindowName(packageName, true /*prependPackageName*/);
-    }
-
-    static String getBaseWindowName(final String packageName, boolean prependPackageName) {
-        return packageName + "/" + (prependPackageName ? packageName + "." : "");
-    }
-
-    static String getWindowName(final String activityName) {
-        return getWindowName(componentName, activityName);
-    }
-
-    static String getWindowName(final String packageName, final String activityName) {
-        return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
-                + activityName;
-    }
-
-    protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
-
-    private int mInitialAccelerometerRotation;
-    private int mUserRotation;
-    private float mFontScale;
-
-    private SurfaceTraceReceiver mSurfaceTraceReceiver;
-    private Thread mSurfaceTraceThread;
-
-    void installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer) {
-        mSurfaceTraceReceiver = new SurfaceTraceReceiver(observer);
-        mSurfaceTraceThread = new Thread() {
-            @Override
-            public void run() {
-                try {
-                    mDevice.executeShellCommand("wm surface-trace", mSurfaceTraceReceiver);
-                } catch (DeviceNotAvailableException e) {
-                    logE("Device not available: " + e.toString());
-                }
-            }
-        };
-        mSurfaceTraceThread.start();
-    }
-
-    void removeSurfaceObserver() {
-        mSurfaceTraceReceiver.cancel();
-        mSurfaceTraceThread.interrupt();
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        setDefaultComponentName();
-
-        // Get the device, this gives a handle to run commands and install APKs.
-        mDevice = getDevice();
-        wakeUpAndUnlockDevice();
-        pressHomeButton();
-        // Remove special stacks.
-        removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
-        // Store rotation settings.
-        mInitialAccelerometerRotation = getAccelerometerRotation();
-        mUserRotation = getUserRotation();
-        mFontScale = getFontScale();
-        mLockCredentialsSet = false;
-        mLockDisabled = isLockDisabled();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        try {
-            setLockDisabled(mLockDisabled);
-            executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
-            executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
-            executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
-            // Restore rotation settings to the state they were before test.
-            setAccelerometerRotation(mInitialAccelerometerRotation);
-            setUserRotation(mUserRotation);
-            setFontScale(mFontScale);
-            setWindowTransitionAnimationDurationScale(1);
-            // Remove special stacks.
-            removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
-            wakeUpAndUnlockDevice();
-            pressHomeButton();
-        } catch (DeviceNotAvailableException e) {
-        }
-    }
-
-    protected void removeStacks(int... stackIds) {
-        try {
-            for (Integer stackId : stackIds) {
-                executeShellCommand(AM_REMOVE_STACK + stackId);
-            }
-        } catch (DeviceNotAvailableException e) {
-        }
-    }
-
-    protected String executeShellCommand(String command) throws DeviceNotAvailableException {
-        return executeShellCommand(mDevice, command);
-    }
-
-    protected static String executeShellCommand(ITestDevice device, String command)
-            throws DeviceNotAvailableException {
-        log("adb shell " + command);
-        return device.executeShellCommand(command);
-    }
-
-    protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
-            throws DeviceNotAvailableException {
-        log("adb shell " + command);
-        mDevice.executeShellCommand(command, outputReceiver);
-    }
-
-    protected BufferedImage takeScreenshot() throws Exception {
-        final InputStreamSource stream = mDevice.getScreenshot("PNG", false /* rescale */);
-        if (stream == null) {
-            fail("Failed to take screenshot of device");
-        }
-        return ImageIO.read(stream.createInputStream());
-    }
-
-    protected void launchActivityInComponent(final String componentName,
-            final String targetActivityName, final String... keyValuePairs) throws Exception {
-        final String originalComponentName = ActivityManagerTestBase.componentName;
-        setComponentName(componentName);
-        launchActivity(targetActivityName, keyValuePairs);
-        setComponentName(originalComponentName);
-    }
-
-    protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
-            throws Exception {
-        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
-        mAmWmState.waitForValidState(mDevice, targetActivityName);
-    }
-
-    protected void launchActivityNoWait(final String targetActivityName,
-            final String... keyValuePairs) throws Exception {
-        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
-    }
-
-    protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
-        executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
-        mAmWmState.waitForValidState(mDevice, targetActivityName);
-    }
-
-    /**
-     * Starts an activity in a new stack.
-     * @return the stack id of the newly created stack.
-     */
-    protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
-        HashSet<Integer> stackIds = getStackIds();
-        executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
-                + " " + getActivityComponentName(activityName));
-        HashSet<Integer> newStackIds = getStackIds();
-        newStackIds.removeAll(stackIds);
-        if (newStackIds.isEmpty()) {
-            return INVALID_STACK_ID;
-        } else {
-            assertTrue(newStackIds.size() == 1);
-            return newStackIds.iterator().next();
-        }
-    }
-
-    /**
-     * Returns the set of stack ids.
-     */
-    private HashSet<Integer> getStackIds() throws Exception {
-        mAmWmState.computeState(mDevice, null);
-        final List<ActivityStack> stacks = mAmWmState.getAmState().getStacks();
-        final HashSet<Integer> stackIds = new HashSet<>();
-        for (ActivityStack s : stacks) {
-            stackIds.add(s.mStackId);
-        }
-        return stackIds;
-    }
-
-    protected void launchHomeActivity()
-            throws Exception {
-        executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
-        mAmWmState.waitForHomeActivityVisible(mDevice);
-    }
-
-    protected void launchActivityOnDisplay(String targetActivityName, int displayId)
-            throws Exception {
-        executeShellCommand(getAmStartCmd(targetActivityName, displayId));
-
-        mAmWmState.waitForValidState(mDevice, targetActivityName);
-    }
-
-    /**
-     * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
-     * that one should be started first.
-     * @param toSide Launch to side in split-screen.
-     * @param randomData Make intent URI random by generating random data.
-     * @param multipleTask Allow multiple task launch.
-     * @param targetActivityName Target activity to be launched. Only class name should be provided,
-     *                           package name of {@link #LAUNCHING_ACTIVITY} will be added
-     *                           automatically.
-     * @param displayId Display id where target activity should be launched.
-     * @throws Exception
-     */
-    protected void launchActivityFromLaunching(boolean toSide, boolean randomData,
-            boolean multipleTask, String targetActivityName, int displayId) throws Exception {
-        StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
-        commandBuilder.append(" -f 0x20000000");
-        if (toSide) {
-            commandBuilder.append(" --ez launch_to_the_side true");
-        }
-        if (randomData) {
-            commandBuilder.append(" --ez random_data true");
-        }
-        if (multipleTask) {
-            commandBuilder.append(" --ez multiple_task true");
-        }
-        if (targetActivityName != null) {
-            commandBuilder.append(" --es target_activity ").append(targetActivityName);
-        }
-        if (displayId != INVALID_DISPLAY_ID) {
-            commandBuilder.append(" --ei display_id ").append(displayId);
-        }
-        executeShellCommand(commandBuilder.toString());
-
-        mAmWmState.waitForValidState(mDevice, targetActivityName);
-    }
-
-    protected void launchActivityInStack(String activityName, int stackId,
-            final String... keyValuePairs) throws Exception {
-        executeShellCommand(getAmStartCmd(activityName, keyValuePairs) + " --stack " + stackId);
-
-        mAmWmState.waitForValidState(mDevice, activityName, stackId);
-    }
-
-    protected void launchActivityInDockStack(String activityName) throws Exception {
-        launchActivity(activityName);
-        // TODO(b/36279415): The way we launch an activity into the docked stack is different from
-        // what the user actually does. Long term we should use
-        // "adb shell input keyevent --longpress _app_swich_key_code_" to trigger a long press on
-        // the recents button which is consistent with what the user does. However, currently sys-ui
-        // does handle FLAG_LONG_PRESS for the app switch key. It just listens for long press on the
-        // view. We need to fix that in sys-ui before we can change this.
-        moveActivityToDockStack(activityName);
-
-        mAmWmState.waitForValidState(mDevice, activityName, DOCKED_STACK_ID);
-    }
-
-    protected void launchActivityToSide(boolean randomData, boolean multipleTaskFlag,
-            String targetActivity) throws Exception {
-        final String activityToLaunch = targetActivity != null ? targetActivity : "TestActivity";
-        getLaunchActivityBuilder().setToSide(true).setRandomData(randomData)
-                .setMultipleTask(multipleTaskFlag).setTargetActivityName(activityToLaunch)
-                .execute();
-
-        mAmWmState.waitForValidState(mDevice, activityToLaunch, FULLSCREEN_WORKSPACE_STACK_ID);
-    }
-
-    protected void moveActivityToDockStack(String activityName) throws Exception {
-        moveActivityToStack(activityName, DOCKED_STACK_ID);
-    }
-
-    protected void moveActivityToStack(String activityName, int stackId) throws Exception {
-        final int taskId = getActivityTaskId(activityName);
-        final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
-        executeShellCommand(cmd);
-
-        mAmWmState.waitForValidState(mDevice, activityName, stackId);
-    }
-
-    protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
-            throws Exception {
-        final int taskId = getActivityTaskId(activityName);
-        final String cmd = "am task resize "
-                + taskId + " " + left + " " + top + " " + right + " " + bottom;
-        executeShellCommand(cmd);
-    }
-
-    protected void resizeDockedStack(
-            int stackWidth, int stackHeight, int taskWidth, int taskHeight)
-                    throws DeviceNotAvailableException {
-        executeShellCommand(AM_RESIZE_DOCKED_STACK
-                + "0 0 " + stackWidth + " " + stackHeight
-                + " 0 0 " + taskWidth + " " + taskHeight);
-    }
-
-    protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
-            int stackHeight) throws DeviceNotAvailableException {
-        executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
-                stackTop, stackWidth, stackHeight));
-    }
-
-    protected void pressHomeButton() throws DeviceNotAvailableException {
-        executeShellCommand(INPUT_KEYEVENT_HOME);
-    }
-
-    protected void pressBackButton() throws DeviceNotAvailableException {
-        executeShellCommand(INPUT_KEYEVENT_BACK);
-    }
-
-    protected void pressAppSwitchButton() throws DeviceNotAvailableException {
-        executeShellCommand(INPUT_KEYEVENT_APP_SWITCH);
-    }
-
-    // Utility method for debugging, not used directly here, but useful, so kept around.
-    protected void printStacksAndTasks() throws DeviceNotAvailableException {
-        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        executeShellCommand(AM_STACK_LIST, outputReceiver);
-        String output = outputReceiver.getOutput();
-        for (String line : output.split("\\n")) {
-            CLog.logAndDisplay(LogLevel.INFO, line);
-        }
-    }
-
-    protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
-        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        executeShellCommand(AM_STACK_LIST, outputReceiver);
-        final String output = outputReceiver.getOutput();
-        final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
-        for (String line : output.split("\\n")) {
-            Matcher matcher = activityPattern.matcher(line);
-            if (matcher.matches()) {
-                for (String word : line.split("\\s+")) {
-                    if (word.startsWith(TASK_ID_PREFIX)) {
-                        final String withColon = word.split("=")[1];
-                        return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
-                    }
-                }
-            }
-        }
-        return -1;
-    }
-
-    protected boolean supportsVrMode() throws DeviceNotAvailableException {
-        return hasDeviceFeature("android.software.vr.mode") &&
-                hasDeviceFeature("android.hardware.vr.high_performance");
-    }
-
-    protected boolean supportsPip() throws DeviceNotAvailableException {
-        return hasDeviceFeature("android.software.picture_in_picture")
-                || PRETEND_DEVICE_SUPPORTS_PIP;
-    }
-
-    protected boolean supportsFreeform() throws DeviceNotAvailableException {
-        return hasDeviceFeature("android.software.freeform_window_management")
-                || PRETEND_DEVICE_SUPPORTS_FREEFORM;
-    }
-
-    protected boolean isHandheld() throws DeviceNotAvailableException {
-        return !hasDeviceFeature("android.software.leanback")
-                && !hasDeviceFeature("android.hardware.type.watch")
-                && !hasDeviceFeature("android.hardware.type.embedded");
-    }
-
-    // TODO: Switch to using a feature flag, when available.
-    protected boolean isUiModeLockedToVrHeadset() throws DeviceNotAvailableException {
-        final String output = runCommandAndPrintOutput("dumpsys uimode");
-
-        Integer curUiMode = null;
-        Boolean uiModeLocked = null;
-        for (String line : output.split("\\n")) {
-            line = line.trim();
-            Matcher matcher = sCurrentUiModePattern.matcher(line);
-            if (matcher.find()) {
-                curUiMode = Integer.parseInt(matcher.group(1), 16);
-            }
-            matcher = sUiModeLockedPattern.matcher(line);
-            if (matcher.find()) {
-                uiModeLocked = matcher.group(1).equals("true");
-            }
-        }
-
-        boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
-                && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
-
-        if (uiModeLockedToVrHeadset) {
-            CLog.logAndDisplay(LogLevel.INFO, "UI mode is locked to VR headset");
-        }
-
-        return uiModeLockedToVrHeadset;
-    }
-
-    protected boolean supportsSplitScreenMultiWindow() throws DeviceNotAvailableException {
-        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW, outputReceiver);
-        String output = outputReceiver.getOutput();
-        return !output.startsWith("false");
-    }
-
-    protected boolean noHomeScreen() throws DeviceNotAvailableException {
-        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        executeShellCommand(AM_NO_HOME_SCREEN, outputReceiver);
-        String output = outputReceiver.getOutput();
-        return output.startsWith("true");
-    }
-
-    /**
-     * Rotation support is indicated by explicitly having both landscape and portrait
-     * features or not listing either at all.
-     */
-    protected boolean supportsRotation() throws DeviceNotAvailableException {
-        return (hasDeviceFeature("android.hardware.screen.landscape")
-                    && hasDeviceFeature("android.hardware.screen.portrait"))
-            || (!hasDeviceFeature("android.hardware.screen.landscape")
-                    && !hasDeviceFeature("android.hardware.screen.portrait"));
-    }
-
-    protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
-        if (mAvailableFeatures == null) {
-            // TODO: Move this logic to ITestDevice.
-            final String output = runCommandAndPrintOutput("pm list features");
-
-            // Extract the id of the new user.
-            mAvailableFeatures = new HashSet<>();
-            for (String feature: output.split("\\s+")) {
-                // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
-                String[] tokens = feature.split(":");
-                assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
-                        tokens.length > 1);
-                assertEquals(feature, "feature", tokens[0]);
-                mAvailableFeatures.add(tokens[1]);
-            }
-        }
-        boolean result = mAvailableFeatures.contains(requiredFeature);
-        if (!result) {
-            CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
-        }
-        return result;
-    }
-
-    protected boolean isDisplayOn() throws DeviceNotAvailableException {
-        final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        mDevice.executeShellCommand("dumpsys power", outputReceiver);
-
-        for (String line : outputReceiver.getOutput().split("\\n")) {
-            line = line.trim();
-
-            final Matcher matcher = sDisplayStatePattern.matcher(line);
-            if (matcher.matches()) {
-                final String state = matcher.group(1);
-                log("power state=" + state);
-                return "ON".equals(state);
-            }
-        }
-        log("power state :(");
-        return false;
-    }
-
-    protected void sleepDevice() throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        runCommandAndPrintOutput("input keyevent SLEEP");
-        do {
-            if (isDisplayOn()) {
-                log("***Waiting for display to turn off...");
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-    }
-
-    protected void wakeUpAndUnlockDevice() throws DeviceNotAvailableException {
-        wakeUpDevice();
-        unlockDevice();
-    }
-
-    protected void wakeUpAndRemoveLock() throws DeviceNotAvailableException {
-        wakeUpDevice();
-        setLockDisabled(true);
-    }
-
-    protected void wakeUpDevice() throws DeviceNotAvailableException {
-        runCommandAndPrintOutput("input keyevent WAKEUP");
-    }
-
-    protected void unlockDevice() throws DeviceNotAvailableException {
-        runCommandAndPrintOutput("input keyevent 82");
-    }
-
-    protected void unlockDeviceWithCredential() throws Exception {
-        runCommandAndPrintOutput("input keyevent 82");
-        try {
-            Thread.sleep(3000);
-        } catch (InterruptedException e) {
-            //ignored
-        }
-        enterAndConfirmLockCredential();
-    }
-
-    protected void enterAndConfirmLockCredential() throws Exception {
-        // TODO: This should use waitForIdle..but there ain't such a thing on hostside tests, boo :(
-        Thread.sleep(500);
-
-        runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
-        runCommandAndPrintOutput("input keyevent KEYCODE_ENTER");
-    }
-
-    protected void gotoKeyguard() throws Exception {
-        sleepDevice();
-        wakeUpDevice();
-        mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-    }
-
-    protected void setLockCredential() throws DeviceNotAvailableException {
-        mLockCredentialsSet = true;
-        runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
-    }
-
-    private void removeLockCredential() throws DeviceNotAvailableException {
-        runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
-    }
-
-    /**
-     * Returns whether the lock screen is disabled.
-     * @return true if the lock screen is disabled, false otherwise.
-     */
-    private boolean isLockDisabled() throws DeviceNotAvailableException {
-        final String isLockDisabled = runCommandAndPrintOutput("locksettings get-disabled").trim();
-        if ("null".equals(isLockDisabled)) {
-            return false;
-        }
-        return Boolean.parseBoolean(isLockDisabled);
-
-    }
-
-    /**
-     * Disable the lock screen.
-     * @param lockDisabled true if should disable, false otherwise.
-     */
-    void setLockDisabled(boolean lockDisabled) throws DeviceNotAvailableException {
-        runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
-    }
-
-    /**
-     * Sets the device rotation, value corresponds to one of {@link Surface.ROTATION_0},
-     * {@link Surface.ROTATION_90}, {@link Surface.ROTATION_180}, {@link Surface.ROTATION_270}.
-     */
-    protected void setDeviceRotation(int rotation) throws Exception {
-        setAccelerometerRotation(0);
-        setUserRotation(rotation);
-        mAmWmState.waitForRotation(mDevice, rotation);
-    }
-
-    protected int getDeviceRotation(int displayId) throws DeviceNotAvailableException {
-        final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
-        Pattern pattern = Pattern.compile(
-                "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
-                        + "(rotation)(\\s+)(\\d+)");
-        Matcher matcher = pattern.matcher(displays);
-        while (matcher.find()) {
-            final String match = matcher.group(7);
-            return Integer.parseInt(match);
-        }
-
-        return INVALID_DEVICE_ROTATION;
-    }
-
-    private int getAccelerometerRotation() throws DeviceNotAvailableException {
-        final String rotation =
-                runCommandAndPrintOutput("settings get system accelerometer_rotation");
-        return Integer.parseInt(rotation.trim());
-    }
-
-    private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
-        runCommandAndPrintOutput(
-                "settings put system accelerometer_rotation " + rotation);
-    }
-
-    protected int getUserRotation() throws DeviceNotAvailableException {
-        final String rotation =
-                runCommandAndPrintOutput("settings get system user_rotation").trim();
-        if ("null".equals(rotation)) {
-            return -1;
-        }
-        return Integer.parseInt(rotation);
-    }
-
-    private void setUserRotation(int rotation) throws DeviceNotAvailableException {
-        if (rotation == -1) {
-            runCommandAndPrintOutput(
-                    "settings delete system user_rotation");
-        } else {
-            runCommandAndPrintOutput(
-                    "settings put system user_rotation " + rotation);
-        }
-    }
-
-    protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
-        if (fontScale == 0.0f) {
-            runCommandAndPrintOutput(
-                    "settings delete system font_scale");
-        } else {
-            runCommandAndPrintOutput(
-                    "settings put system font_scale " + fontScale);
-        }
-    }
-
-    protected void setWindowTransitionAnimationDurationScale(float animDurationScale)
-            throws DeviceNotAvailableException {
-        runCommandAndPrintOutput(
-                "settings put global transition_animation_scale " + animDurationScale);
-    }
-
-    protected float getFontScale() throws DeviceNotAvailableException {
-        try {
-            final String fontScale =
-                    runCommandAndPrintOutput("settings get system font_scale").trim();
-            return Float.parseFloat(fontScale);
-        } catch (NumberFormatException e) {
-            // If we don't have a valid font scale key, return 0.0f now so
-            // that we delete the key in tearDown().
-            return 0.0f;
-        }
-    }
-
-    protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
-        final String output = executeShellCommand(command);
-        log(output);
-        return output;
-    }
-
-    /**
-     * Tries to clear logcat and inserts log separator in case clearing didn't succeed, so we can
-     * always find the starting point from where to evaluate following logs.
-     * @return Unique log separator.
-     */
-    protected String clearLogcat() throws DeviceNotAvailableException {
-        mDevice.executeAdbCommand("logcat", "-c");
-        final String uniqueString = UUID.randomUUID().toString();
-        executeShellCommand("log -t " + LOG_SEPARATOR + " " + uniqueString);
-        return uniqueString;
-    }
-
-    void assertActivityLifecycle(String activityName, boolean relaunched,
-            String logSeparator) throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = verifyLifecycleCondition(activityName, logSeparator, relaunched);
-            if (resultString != null) {
-                log("***Waiting for valid lifecycle state: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    /** @return Error string if lifecycle counts don't match, null if everything is fine. */
-    private String verifyLifecycleCondition(String activityName, String logSeparator,
-            boolean relaunched) throws DeviceNotAvailableException {
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
-        if (relaunched) {
-            if (lifecycleCounts.mDestroyCount < 1) {
-                return activityName + " must have been destroyed. mDestroyCount="
-                        + lifecycleCounts.mDestroyCount;
-            }
-            if (lifecycleCounts.mCreateCount < 1) {
-                return activityName + " must have been (re)created. mCreateCount="
-                        + lifecycleCounts.mCreateCount;
-            }
-        } else {
-            if (lifecycleCounts.mDestroyCount > 0) {
-                return activityName + " must *NOT* have been destroyed. mDestroyCount="
-                        + lifecycleCounts.mDestroyCount;
-            }
-            if (lifecycleCounts.mCreateCount > 0) {
-                return activityName + " must *NOT* have been (re)created. mCreateCount="
-                        + lifecycleCounts.mCreateCount;
-            }
-            if (lifecycleCounts.mConfigurationChangedCount < 1) {
-                return activityName + " must have received configuration changed. "
-                        + "mConfigurationChangedCount="
-                        + lifecycleCounts.mConfigurationChangedCount;
-            }
-        }
-        return null;
-    }
-
-    protected void assertRelaunchOrConfigChanged(
-            String activityName, int numRelaunch, int numConfigChange, String logSeparator)
-            throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = verifyRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
-                    logSeparator);
-            if (resultString != null) {
-                log("***Waiting for relaunch or config changed: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    /** @return Error string if lifecycle counts don't match, null if everything is fine. */
-    private String verifyRelaunchOrConfigChanged(String activityName, int numRelaunch,
-            int numConfigChange, String logSeparator) throws DeviceNotAvailableException {
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
-
-        if (lifecycleCounts.mDestroyCount != numRelaunch) {
-            return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
-                    + " time(s), expecting " + numRelaunch;
-        } else if (lifecycleCounts.mCreateCount != numRelaunch) {
-            return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
-                    + " time(s), expecting " + numRelaunch;
-        } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
-            return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
-                    + " onConfigurationChanged() calls, expecting " + numConfigChange;
-        }
-        return null;
-    }
-
-    protected void assertActivityDestroyed(String activityName, String logSeparator)
-            throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = verifyActivityDestroyed(activityName, logSeparator);
-            if (resultString != null) {
-                log("***Waiting for activity destroyed: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    /** @return Error string if lifecycle counts don't match, null if everything is fine. */
-    private String verifyActivityDestroyed(String activityName, String logSeparator)
-            throws DeviceNotAvailableException {
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
-
-        if (lifecycleCounts.mDestroyCount != 1) {
-            return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
-                    + " time(s), expecting single destruction.";
-        } else if (lifecycleCounts.mCreateCount != 0) {
-            return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
-                    + " time(s), not expecting any.";
-        } else if (lifecycleCounts.mConfigurationChangedCount != 0) {
-            return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
-                    + " onConfigurationChanged() calls, not expecting any.";
-        }
-        return null;
-    }
-
-    protected String[] getDeviceLogsForComponent(String componentName, String logSeparator)
-            throws DeviceNotAvailableException {
-        return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
-    }
-
-    protected String[] getDeviceLogsForComponents(final String[] componentNames,
-            String logSeparator) throws DeviceNotAvailableException {
-        String filters = LOG_SEPARATOR + ":I ";
-        for (String component : componentNames) {
-            filters += component + ":I ";
-        }
-        final String[] result = mDevice.executeAdbCommand(
-                "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
-        if (logSeparator == null) {
-            return result;
-        }
-
-        // Make sure that we only check logs after the separator.
-        int i = 0;
-        boolean lookingForSeparator = true;
-        while (i < result.length && lookingForSeparator) {
-            if (result[i].contains(logSeparator)) {
-                lookingForSeparator = false;
-            }
-            i++;
-        }
-        final String[] filteredResult = new String[result.length - i];
-        for (int curPos = 0; i < result.length; curPos++, i++) {
-            filteredResult[curPos] = result[i];
-        }
-        return filteredResult;
-    }
-
-    void assertSingleLaunch(String activityName, String logSeparator) throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
-                    1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
-                    0 /* destroyCount */);
-            if (resultString != null) {
-                log("***Waiting for valid lifecycle state: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    public void assertSingleLaunchAndStop(String activityName, String logSeparator) throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
-                    1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
-                    0 /* destroyCount */);
-            if (resultString != null) {
-                log("***Waiting for valid lifecycle state: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    public void assertSingleStartAndStop(String activityName, String logSeparator) throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString =  validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
-                    1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
-                    0 /* destroyCount */);
-            if (resultString != null) {
-                log("***Waiting for valid lifecycle state: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    void assertSingleStart(String activityName, String logSeparator) throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        String resultString;
-        do {
-            resultString = validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
-                    1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
-                    0 /* destroyCount */);
-            if (resultString != null) {
-                log("***Waiting for valid lifecycle state: " + resultString);
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-
-        assertNull(resultString, resultString);
-    }
-
-    private String validateLifecycleCounts(String activityName, String logSeparator,
-            int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
-            int destroyCount) throws DeviceNotAvailableException {
-
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
-
-        if (lifecycleCounts.mCreateCount != createCount) {
-            return activityName + " created " + lifecycleCounts.mCreateCount + " times.";
-        }
-        if (lifecycleCounts.mStartCount != startCount) {
-            return activityName + " started " + lifecycleCounts.mStartCount + " times.";
-        }
-        if (lifecycleCounts.mResumeCount != resumeCount) {
-            return activityName + " resumed " + lifecycleCounts.mResumeCount + " times.";
-        }
-        if (lifecycleCounts.mPauseCount != pauseCount) {
-            return activityName + " paused " + lifecycleCounts.mPauseCount + " times.";
-        }
-        if (lifecycleCounts.mStopCount != stopCount) {
-            return activityName + " stopped " + lifecycleCounts.mStopCount + " times.";
-        }
-        if (lifecycleCounts.mDestroyCount != destroyCount) {
-            return activityName + " destroyed " + lifecycleCounts.mDestroyCount + " times.";
-        }
-        return null;
-    }
-
-    private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
-    private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
-    private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
-    private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
-    private static final Pattern sConfigurationChangedPattern =
-            Pattern.compile("(.+): onConfigurationChanged");
-    private static final Pattern sMovedToDisplayPattern =
-            Pattern.compile("(.+): onMovedToDisplay");
-    private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
-    private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
-    private static final Pattern sMultiWindowModeChangedPattern =
-            Pattern.compile("(.+): onMultiWindowModeChanged");
-    private static final Pattern sPictureInPictureModeChangedPattern =
-            Pattern.compile("(.+): onPictureInPictureModeChanged");
-    private static final Pattern sNewConfigPattern = Pattern.compile(
-            "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
-            + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
-            + " orientation=(\\d+)");
-    private static final Pattern sDisplayStatePattern =
-            Pattern.compile("Display Power: state=(.+)");
-    private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
-    private static final Pattern sUiModeLockedPattern =
-            Pattern.compile("mUiModeLocked=(true|false)");
-
-    class ReportedSizes {
-        int widthDp;
-        int heightDp;
-        int displayWidth;
-        int displayHeight;
-        int metricsWidth;
-        int metricsHeight;
-        int smallestWidthDp;
-        int densityDpi;
-        int orientation;
-
-        @Override
-        public String toString() {
-            return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
-                    + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
-                    + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
-                    + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
-                    + " orientation=" + orientation + "}";
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if ( this == obj ) return true;
-            if ( !(obj instanceof ReportedSizes) ) return false;
-            ReportedSizes that = (ReportedSizes) obj;
-            return widthDp == that.widthDp
-                    && heightDp == that.heightDp
-                    && displayWidth == that.displayWidth
-                    && displayHeight == that.displayHeight
-                    && metricsWidth == that.metricsWidth
-                    && metricsHeight == that.metricsHeight
-                    && smallestWidthDp == that.smallestWidthDp
-                    && densityDpi == that.densityDpi
-                    && orientation == that.orientation;
-        }
-    }
-
-    ReportedSizes getLastReportedSizesForActivity(String activityName, String logSeparator)
-            throws DeviceNotAvailableException {
-        int retriesLeft = 5;
-        ReportedSizes result;
-        do {
-            result = readLastReportedSizes(activityName, logSeparator);
-            if (result == null) {
-                log("***Waiting for sizes to be reported...");
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            } else {
-                break;
-            }
-        } while (retriesLeft-- > 0);
-        return result;
-    }
-
-    private ReportedSizes readLastReportedSizes(String activityName, String logSeparator)
-            throws DeviceNotAvailableException {
-        final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sNewConfigPattern.matcher(line);
-            if (matcher.matches()) {
-                ReportedSizes details = new ReportedSizes();
-                details.widthDp = Integer.parseInt(matcher.group(2));
-                details.heightDp = Integer.parseInt(matcher.group(3));
-                details.displayWidth = Integer.parseInt(matcher.group(4));
-                details.displayHeight = Integer.parseInt(matcher.group(5));
-                details.metricsWidth = Integer.parseInt(matcher.group(6));
-                details.metricsHeight = Integer.parseInt(matcher.group(7));
-                details.smallestWidthDp = Integer.parseInt(matcher.group(8));
-                details.densityDpi = Integer.parseInt(matcher.group(9));
-                details.orientation = Integer.parseInt(matcher.group(10));
-                return details;
-            }
-        }
-        return null;
-    }
-
-    class ActivityLifecycleCounts {
-        int mCreateCount;
-        int mStartCount;
-        int mResumeCount;
-        int mConfigurationChangedCount;
-        int mLastConfigurationChangedLineIndex;
-        int mMovedToDisplayCount;
-        int mMultiWindowModeChangedCount;
-        int mLastMultiWindowModeChangedLineIndex;
-        int mPictureInPictureModeChangedCount;
-        int mLastPictureInPictureModeChangedLineIndex;
-        int mPauseCount;
-        int mStopCount;
-        int mLastStopLineIndex;
-        int mDestroyCount;
-
-        public ActivityLifecycleCounts(String activityName, String logSeparator)
-                throws DeviceNotAvailableException {
-            int lineIndex = 0;
-            for (String line : getDeviceLogsForComponent(activityName, logSeparator)) {
-                line = line.trim();
-                lineIndex++;
-
-                Matcher matcher = sCreatePattern.matcher(line);
-                if (matcher.matches()) {
-                    mCreateCount++;
-                    continue;
-                }
-
-                matcher = sStartPattern.matcher(line);
-                if (matcher.matches()) {
-                    mStartCount++;
-                    continue;
-                }
-
-                matcher = sResumePattern.matcher(line);
-                if (matcher.matches()) {
-                    mResumeCount++;
-                    continue;
-                }
-
-                matcher = sConfigurationChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mConfigurationChangedCount++;
-                    mLastConfigurationChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sMovedToDisplayPattern.matcher(line);
-                if (matcher.matches()) {
-                    mMovedToDisplayCount++;
-                    continue;
-                }
-
-                matcher = sMultiWindowModeChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mMultiWindowModeChangedCount++;
-                    mLastMultiWindowModeChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sPictureInPictureModeChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mPictureInPictureModeChangedCount++;
-                    mLastPictureInPictureModeChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sPausePattern.matcher(line);
-                if (matcher.matches()) {
-                    mPauseCount++;
-                    continue;
-                }
-
-                matcher = sStopPattern.matcher(line);
-                if (matcher.matches()) {
-                    mStopCount++;
-                    mLastStopLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sDestroyPattern.matcher(line);
-                if (matcher.matches()) {
-                    mDestroyCount++;
-                    continue;
-                }
-            }
-        }
-    }
-
-    protected void stopTestCase() throws Exception {
-        executeShellCommand("am force-stop " + componentName);
-    }
-
-    protected LaunchActivityBuilder getLaunchActivityBuilder() {
-        return new LaunchActivityBuilder(mAmWmState, mDevice);
-    }
-
-    protected static class LaunchActivityBuilder {
-        private final ActivityAndWindowManagersState mAmWmState;
-        private final ITestDevice mDevice;
-
-        private String mTargetActivityName;
-        private String mTargetPackage = componentName;
-        private boolean mToSide;
-        private boolean mRandomData;
-        private boolean mNewTask;
-        private boolean mMultipleTask;
-        private int mDisplayId = INVALID_DISPLAY_ID;
-        private String mLaunchingActivityName = LAUNCHING_ACTIVITY;
-        private boolean mReorderToFront;
-        private boolean mWaitForLaunched;
-
-        public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState,
-                                     ITestDevice device) {
-            mAmWmState = amWmState;
-            mDevice = device;
-            mWaitForLaunched = true;
-        }
-
-        public LaunchActivityBuilder setToSide(boolean toSide) {
-            mToSide = toSide;
-            return this;
-        }
-
-        public LaunchActivityBuilder setRandomData(boolean randomData) {
-            mRandomData = randomData;
-            return this;
-        }
-
-        public LaunchActivityBuilder setNewTask(boolean newTask) {
-            mNewTask = newTask;
-            return this;
-        }
-
-        public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
-            mMultipleTask = multipleTask;
-            return this;
-        }
-
-        public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
-            mReorderToFront = reorderToFront;
-            return this;
-        }
-
-        public LaunchActivityBuilder setTargetActivityName(String name) {
-            mTargetActivityName = name;
-            return this;
-        }
-
-        public LaunchActivityBuilder setTargetPackage(String pkg) {
-            mTargetPackage = pkg;
-            return this;
-        }
-
-        public LaunchActivityBuilder setDisplayId(int id) {
-            mDisplayId = id;
-            return this;
-        }
-
-        public LaunchActivityBuilder setLaunchingActivityName(String name) {
-            mLaunchingActivityName = name;
-            return this;
-        }
-
-        public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
-            mWaitForLaunched = shouldWait;
-            return this;
-        }
-
-        public void execute() throws Exception {
-            StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(mLaunchingActivityName));
-            commandBuilder.append(" -f 0x20000000");
-
-            // Add a flag to ensure we actually mean to launch an activity.
-            commandBuilder.append(" --ez launch_activity true");
-
-            if (mToSide) {
-                commandBuilder.append(" --ez launch_to_the_side true");
-            }
-            if (mRandomData) {
-                commandBuilder.append(" --ez random_data true");
-            }
-            if (mNewTask) {
-                commandBuilder.append(" --ez new_task true");
-            }
-            if (mMultipleTask) {
-                commandBuilder.append(" --ez multiple_task true");
-            }
-            if (mReorderToFront) {
-                commandBuilder.append(" --ez reorder_to_front true");
-            }
-            if (mTargetActivityName != null) {
-                commandBuilder.append(" --es target_activity ").append(mTargetActivityName);
-                commandBuilder.append(" --es package_name ").append(mTargetPackage);
-            }
-            if (mDisplayId != INVALID_DISPLAY_ID) {
-                commandBuilder.append(" --ei display_id ").append(mDisplayId);
-            }
-            executeShellCommand(mDevice, commandBuilder.toString());
-
-            if (mWaitForLaunched) {
-                mAmWmState.waitForValidState(mDevice, new String[]{mTargetActivityName},
-                        null /* stackIds */, false /* compareTaskAndStackBounds */, mTargetPackage);
-            }
-        }
-    }
-
-    void tearDownLockCredentials() throws Exception {
-        if (!mLockCredentialsSet) {
-            return;
-        }
-
-        removeLockCredential();
-        // Dismiss active keyguard after credential is cleared, so
-        // keyguard doesn't ask for the stale credential.
-        pressBackButton();
-        sleepDevice();
-        wakeUpAndUnlockDevice();
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java
deleted file mode 100644
index 335f26c..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.log.LogUtil.CLog;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-import static com.android.ddmlib.Log.LogLevel.ERROR;
-
-/**
- * Util class to perform simple state logging.
- */
-public class StateLogger {
-    private static final boolean DEBUG = false;
-
-    /** Simple info-level logging gated by {@link #DEBUG} flag */
-    public static void log(String logText) {
-        if (DEBUG) {
-            CLog.logAndDisplay(INFO, logText);
-        }
-    }
-
-    public static void logE(String logText) {
-        CLog.logAndDisplay(ERROR, logText);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java
deleted file mode 100644
index 8026e80..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.awt.Rectangle;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.lang.System;
-import junit.framework.Assert;
-
-import static android.server.cts.StateLogger.logE;
-
-// Parses a trace of surface commands from the WM (in real time)
-// and dispenses them via the SurfaceObserver interface.
-//
-// Data enters through addOutput
-public class SurfaceTraceReceiver implements IShellOutputReceiver {
-    final SurfaceObserver mObserver;
-
-    private State mState = State.CMD;
-    private String mCurrentWindowName = null;
-    private int mArgPosition = 0;
-    private float[] mTmpFloats = new float[10];
-    private int[] mTmpInts = new int[10];
-    private Rectangle.Float mTmpRect = new Rectangle.Float();
-    private byte[] mUnprocessedBytes = new byte[16384];
-    private byte[] mFullData = new byte[32768];
-    private int mUnprocessedBytesLength;
-
-    private boolean mCancelled = false;
-
-    interface SurfaceObserver {
-        default void setAlpha(String windowName, float alpha) {}
-        default void setLayer(String windowName, int layer) {}
-        default void setPosition(String windowName, float x, float y) {}
-        default void setSize(String widnowName, int width, int height) {}
-        default void setLayerStack(String windowName, int layerStack) {}
-        default void setMatrix(String windowName, float dsdx, float dtdx, float dsdy, float dtdy) {}
-        default void setCrop(String windowName, Rectangle.Float crop) {}
-        default void setFinalCrop(String windowName, Rectangle.Float finalCrop) {}
-        default void hide(String windowName) {}
-        default void show(String windowName) {}
-        default void setGeometryAppliesWithResize(String windowName) {}
-        default void openTransaction() {}
-        default void closeTransaction() {}
-    };
-
-    enum State {
-        CMD,
-        SET_ALPHA,
-        SET_LAYER,
-        SET_POSITION,
-        SET_SIZE,
-        SET_CROP,
-        SET_FINAL_CROP,
-        SET_LAYER_STACK,
-        SET_MATRIX,
-        HIDE,
-        SHOW,
-        GEOMETRY_APPLIES_WITH_RESIZE
-    };
-
-    SurfaceTraceReceiver(SurfaceObserver observer) {
-        mObserver = observer;
-    }
-
-    // Reset state and prepare to accept a new command.
-    void nextCmd(DataInputStream d) {
-        mState = State.CMD;
-        mCurrentWindowName = null;
-        mArgPosition = 0;
-
-        try {
-            // Consume the sigil
-            d.readByte();
-            d.readByte();
-            d.readByte();
-            d.readByte();
-        } catch (Exception e) {
-            logE("Exception consuming sigil: " + e);
-        }
-    }
-
-    // When the command parsing functions below are called, the window name
-    // will already be parsed. The responsibility of these functions
-    // is to parse other arguments 1 by 1 and accumlate them until the appropriate number
-    // is reached. At that point the parser should emit an event to the observer and
-    // call nextCmd
-    void parseAlpha(DataInputStream d) throws IOException {
-        float alpha = d.readFloat();
-        mObserver.setAlpha(mCurrentWindowName, alpha);
-        nextCmd(d);
-    }
-
-    void parseLayer(DataInputStream d) throws IOException {
-        int layer = d.readInt();
-        mObserver.setLayer(mCurrentWindowName, layer);
-        nextCmd(d);
-    }
-
-    void parsePosition(DataInputStream d) throws IOException {
-        mTmpFloats[mArgPosition] = d.readFloat();
-        mArgPosition++;
-        if (mArgPosition == 2)  {
-            mObserver.setPosition(mCurrentWindowName, mTmpFloats[0], mTmpFloats[1]);
-            nextCmd(d);
-        }
-    }
-
-    void parseSize(DataInputStream d) throws IOException {
-        mTmpInts[mArgPosition] = d.readInt();
-        mArgPosition++;
-        if (mArgPosition == 2) {
-            mObserver.setSize(mCurrentWindowName, mTmpInts[0], mTmpInts[1]);
-            nextCmd(d);
-        }
-    }
-
-    // Careful Android rectangle rep is top-left-right-bottom awt is top-left-width-height
-    void parseCrop(DataInputStream d) throws IOException {
-        mTmpFloats[mArgPosition] = d.readFloat();
-        mArgPosition++;
-        if (mArgPosition == 4) {
-            mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
-                    mTmpFloats[3]-mTmpFloats[1]);
-            mObserver.setCrop(mCurrentWindowName, mTmpRect);
-            nextCmd(d);
-        }
-    }
-
-    void parseFinalCrop(DataInputStream d) throws IOException {
-        mTmpFloats[mArgPosition] = d.readInt();
-        mArgPosition++;
-        if (mArgPosition == 4) {
-            mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
-                    mTmpFloats[3]-mTmpFloats[1]);
-            mObserver.setFinalCrop(mCurrentWindowName, mTmpRect);
-            nextCmd(d);
-        }
-    }
-
-    void parseLayerStack(DataInputStream d) throws IOException {
-        int layerStack = d.readInt();
-        mObserver.setLayerStack(mCurrentWindowName, layerStack);
-        nextCmd(d);
-    }
-
-    void parseSetMatrix(DataInputStream d) throws IOException {
-        mTmpFloats[mArgPosition] = d.readFloat();
-        mArgPosition++;
-        if (mArgPosition == 4) {
-            mObserver.setMatrix(mCurrentWindowName, mTmpFloats[0],
-                    mTmpFloats[1], mTmpFloats[2], mTmpFloats[3]);
-            nextCmd(d);
-        }
-    }
-
-    void parseHide(DataInputStream d) throws IOException {
-        mObserver.hide(mCurrentWindowName);
-        nextCmd(d);
-    }
-
-    void parseShow(DataInputStream d) throws IOException {
-        mObserver.show(mCurrentWindowName);
-        nextCmd(d);
-    }
-
-    void parseGeometryAppliesWithResize(DataInputStream d) throws IOException {
-        mObserver.setGeometryAppliesWithResize(mCurrentWindowName);
-        nextCmd(d);
-    }
-
-    public int indexAfterLastSigil(byte[] data, int offset, int length) {
-        int idx = offset + length - 1;
-        int sigilsNeeded = 4;
-        byte sigil = (byte)0xfc;
-        while (idx > offset) {
-            if (data[idx] == sigil) {
-                sigilsNeeded--;
-                if (sigilsNeeded == 0) {
-                    return idx+4;
-                }
-            } else {
-                sigilsNeeded = 4;
-            }
-            idx--;
-        }
-        return idx; // idx == offset at this point
-    }
-
-    // The tricky bit here is ADB may break up our words, and not send us complete messages,
-    // or even complete integers! To ensure we process the data in appropciate chunks,
-    // We look for a sigil (0xfcfcfcfc) and only process data when it ends in as igil.
-    // Otherwise we save it and wait to receive a sigil, then process the merged data.
-    public void addOutput(byte[] data, int offset, int length) {
-        byte[] combinedData = data;
-
-        // First we have to merge any unprocessed bytes from the last call in to
-        // a combined array.
-        if (mUnprocessedBytesLength > 0) {
-            System.arraycopy(mUnprocessedBytes, 0, mFullData, 0, mUnprocessedBytesLength);
-            System.arraycopy(data, offset, mFullData, mUnprocessedBytesLength, length);
-            combinedData = mFullData;
-            length = mUnprocessedBytesLength + length;
-            offset = 0;
-            mUnprocessedBytesLength = 0;
-        }
-
-        // Now we find the last sigil in our combined array. Everything before this index is
-        // a properly terminated message ready to be parsed.
-        int completedIndex = indexAfterLastSigil(combinedData, offset, length);
-        // If there are any bytes left after the last sigil, save them for next time.
-        if (completedIndex != length + offset) {
-            mUnprocessedBytesLength = (length+offset)-(completedIndex);
-            System.arraycopy(combinedData, completedIndex,
-                    mUnprocessedBytes, 0, mUnprocessedBytesLength);
-        }
-        //  If there was no sigil, we have nothing to process yet.
-        if (completedIndex <= offset) {
-            return;
-        }
-        ByteArrayInputStream b = new ByteArrayInputStream(combinedData, offset, completedIndex - offset);
-        DataInputStream d = new DataInputStream(b);
-
-        // We may not receive an entire message at once (for example we may receive
-        // a command without its arguments), so we track our current state, over multiple
-        // addOutput calls. When we are in State.CMD it means we next expect a new command.
-        // If we are not expecting a command, then all commands with arguments, begin with
-        // a window name. Once we have the window name, individual parseAlpha,
-        // parseLayer, etc...statements will parse command arguments one at a time. Once
-        // the appropriate number of arguments is collected the observer will be invoked
-        // and the state reset. For commands which have no arguments (e.g. open/close transaction),
-        // parseCmd can emit the observer event and call nextCmd() right away.
-        try {
-            while (b.available() > 0) {
-                if (mState != State.CMD && mCurrentWindowName == null) {
-                    mCurrentWindowName = d.readUTF();
-                    if (b.available() == 0) {
-                        return;
-                    }
-                }
-                switch (mState) {
-                case CMD: {
-                    String cmd = d.readUTF();
-                    parseCmd(d, cmd);
-                    break;
-                }
-                case SET_ALPHA: {
-                    parseAlpha(d);
-                    break;
-                }
-                case SET_LAYER: {
-                    parseLayer(d);
-                    break;
-                }
-                case SET_POSITION: {
-                    parsePosition(d);
-                    break;
-                }
-                case SET_SIZE: {
-                    parseSize(d);
-                    break;
-                }
-                case SET_CROP: {
-                    parseCrop(d);
-                    break;
-                }
-                case SET_FINAL_CROP: {
-                    parseFinalCrop(d);
-                    break;
-                }
-                case SET_LAYER_STACK: {
-                    parseLayerStack(d);
-                    break;
-                }
-                case SET_MATRIX: {
-                    parseSetMatrix(d);
-                    break;
-                }
-                case HIDE: {
-                    parseHide(d);
-                    break;
-                }
-                case SHOW: {
-                    parseShow(d);
-                    break;
-                }
-                case GEOMETRY_APPLIES_WITH_RESIZE: {
-                    parseGeometryAppliesWithResize(d);
-                    break;
-                }
-                }
-            }
-        } catch (Exception e) {
-            logE("Error in surface trace receiver: " + e.toString());
-        }
-    }
-
-    void parseCmd(DataInputStream d, String cmd) {
-        switch (cmd) {
-        case "Alpha":
-            mState = State.SET_ALPHA;
-            break;
-        case "Layer":
-            mState = State.SET_LAYER;
-            break;
-        case "Position":
-            mState = State.SET_POSITION;
-            break;
-        case "Size":
-            mState = State.SET_SIZE;
-            break;
-        case "Crop":
-            mState = State.SET_CROP;
-            break;
-        case "FinalCrop":
-            mState = State.SET_FINAL_CROP;
-            break;
-        case "LayerStack":
-            mState = State.SET_LAYER_STACK;
-            break;
-        case "Matrix":
-            mState = State.SET_MATRIX;
-            break;
-        case "Hide":
-            mState = State.HIDE;
-            break;
-        case "Show":
-            mState = State.SHOW;
-            break;
-        case "GeometryAppliesWithResize":
-            mState = State.GEOMETRY_APPLIES_WITH_RESIZE;
-            break;
-        case "OpenTransaction":
-            mObserver.openTransaction();
-            nextCmd(d);
-            break;
-        case "CloseTransaction":
-            mObserver.closeTransaction();
-            nextCmd(d);
-            break;
-        default:
-            Assert.fail("Unexpected surface command: " + cmd);
-            break;
-        }
-    }
-
-    @Override
-    public void flush() {
-    }
-
-    void cancel() {
-        mCancelled = true;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return mCancelled;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
deleted file mode 100644
index c112a14..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
+++ /dev/null
@@ -1,1152 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class WindowManagerState {
-
-    public static final String TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN";
-    public static final String TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE";
-    public static final String TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN";
-    public static final String TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE";
-
-    public static final String TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN";
-    public static final String TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE";
-    public static final String TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN";
-    public static final String TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE";
-
-    public static final String TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY";
-    public static final String TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
-            "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
-    public static final String TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE";
-    public static final String TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE";
-
-    public static final String APP_STATE_IDLE = "APP_STATE_IDLE";
-
-    private static final String DUMPSYS_WINDOW = "dumpsys window -a";
-
-    private static final Pattern sWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:");
-    private static final Pattern sStartingWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:");
-    private static final Pattern sExitingWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:");
-    private static final Pattern sDebuggerWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger: (.+)\\}\\:");
-
-    private static final Pattern sFocusedWindowPattern = Pattern.compile(
-            "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
-    private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
-            "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
-    private static final Pattern sWaitingForDebuggerFocusedWindowPattern = Pattern.compile(
-            "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger\\: (\\S+)\\}");
-
-    private static final Pattern sFocusedAppPattern =
-            Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
-                    + "ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)");
-    private static final Pattern sStableBoundsPattern = Pattern.compile(
-            "mStable=\\((\\d+),(\\d+)\\)-\\((\\d+),(\\d+)\\)");
-    private static final Pattern sDefaultPinnedStackBoundsPattern = Pattern.compile(
-            "defaultBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
-    private static final Pattern sPinnedStackMovementBoundsPattern = Pattern.compile(
-            "movementBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
-    private static final Pattern sRotationPattern = Pattern.compile(
-            "mRotation=(\\d).*");
-    private static final Pattern sLastOrientationPattern = Pattern.compile(
-            ".*mLastOrientation=(\\d)");
-
-    private static final Pattern sLastAppTransitionPattern =
-            Pattern.compile("mLastUsedAppTransition=(.+)");
-    private static final Pattern sAppTransitionStatePattern =
-            Pattern.compile("mAppTransitionState=(.+)");
-
-    private static final Pattern sStackIdPattern = Pattern.compile("mStackId=(\\d+)");
-
-    private static final Pattern sInputMethodWindowPattern =
-            Pattern.compile("mInputMethodWindow=Window\\{([0-9a-fA-F]+) u\\d+ .+\\}.*");
-
-    private static final Pattern sDisplayIdPattern =
-            Pattern.compile("Display: mDisplayId=(\\d+)");
-
-    private static final Pattern sDisplayFrozenPattern =
-            Pattern.compile("mDisplayFrozen=([a-z]*) .*");
-
-    private static final Pattern sDockedStackMinimizedPattern =
-            Pattern.compile("mMinimizedDock=([a-z]*)");
-
-    private static final Pattern[] sExtractStackExitPatterns = {
-            sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
-            sDebuggerWindowPattern, sFocusedWindowPattern, sAppErrorFocusedWindowPattern,
-            sWaitingForDebuggerFocusedWindowPattern,
-            sFocusedAppPattern, sLastAppTransitionPattern, sDefaultPinnedStackBoundsPattern,
-            sPinnedStackMovementBoundsPattern, sDisplayIdPattern, sDockedStackMinimizedPattern};
-
-    // 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.
-    private final List<WindowStack> mStacks = new ArrayList();
-    // Stacks on all attached displays, in z-order with the top most at the front of the list.
-    private final Map<Integer, List<WindowStack>> mDisplayStacks
-            = new HashMap<>();
-    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 Rectangle mStableBounds = new Rectangle();
-    private final Rectangle mDefaultPinnedStackBounds = new Rectangle();
-    private final Rectangle mPinnedStackMovementBounds = new Rectangle();
-    private final LinkedList<String> mSysDump = new LinkedList();
-    private int mRotation;
-    private int mLastOrientation;
-    private boolean mDisplayFrozen;
-    private boolean mIsDockedStackMinimized;
-
-    void computeState(ITestDevice device) throws DeviceNotAvailableException {
-        // It is possible the system is in the middle of transition to the right state when we get
-        // the dump. We try a few times to get the information we need before giving up.
-        int retriesLeft = 3;
-        boolean retry = false;
-        String dump = null;
-
-        log("==============================");
-        log("      WindowManagerState      ");
-        log("==============================");
-        do {
-            if (retry) {
-                log("***Incomplete WM state. Retrying...");
-                // Wait half a second between retries for window manager to finish transitioning...
-                try {
-                    Thread.sleep(500);
-                } catch (InterruptedException e) {
-                    log(e.toString());
-                    // Well I guess we are not waiting...
-                }
-            }
-
-            final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-            device.executeShellCommand(DUMPSYS_WINDOW, outputReceiver);
-            dump = outputReceiver.getOutput();
-            parseSysDump(dump);
-
-            retry = mWindowStates.isEmpty() || mFocusedApp == null;
-        } while (retry && retriesLeft-- > 0);
-
-        if (retry) {
-            log(dump);
-        }
-
-        if (mWindowStates.isEmpty()) {
-            logE("No Windows found...");
-        }
-        if (mFocusedWindow == null) {
-            logE("No Focused Window...");
-        }
-        if (mFocusedApp == null) {
-            logE("No Focused App...");
-        }
-    }
-
-    private void parseSysDump(String sysDump) {
-        reset();
-
-        Collections.addAll(mSysDump, sysDump.split("\\n"));
-
-        int currentDisplayId = DEFAULT_DISPLAY_ID;
-        while (!mSysDump.isEmpty()) {
-            final Display display =
-                    Display.create(mSysDump, sExtractStackExitPatterns);
-            if (display != null) {
-                log(display.toString());
-                mDisplays.add(display);
-                currentDisplayId = display.mDisplayId;
-                mDisplayStacks.put(currentDisplayId, new ArrayList<>());
-                continue;
-            }
-
-            final WindowStack stack =
-                    WindowStack.create(mSysDump, sStackIdPattern, sExtractStackExitPatterns);
-
-            if (stack != null) {
-                mStacks.add(stack);
-                mDisplayStacks.get(currentDisplayId).add(stack);
-                continue;
-            }
-
-
-            final WindowState ws = WindowState.create(mSysDump, sExtractStackExitPatterns);
-            if (ws != null) {
-                log(ws.toString());
-
-                // Check to see if we are in the middle of transitioning. If we are, we want to
-                // skip dumping until window manager is done transitioning windows.
-                if (ws.isStartingWindow()) {
-                    log("Skipping dump due to starting window transition...");
-                    return;
-                }
-
-                if (ws.isExitingWindow()) {
-                    log("Skipping dump due to exiting window transition...");
-                    return;
-                }
-
-                mWindowStates.add(ws);
-                continue;
-            }
-
-            final String line = mSysDump.pop().trim();
-
-            Matcher matcher = sFocusedWindowPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String focusedWindow = matcher.group(3);
-                log(focusedWindow);
-                mFocusedWindow = focusedWindow;
-                continue;
-            }
-
-            matcher = sAppErrorFocusedWindowPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String focusedWindow = matcher.group(3);
-                log(focusedWindow);
-                mFocusedWindow = focusedWindow;
-                continue;
-            }
-
-            matcher = sWaitingForDebuggerFocusedWindowPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String focusedWindow = matcher.group(3);
-                log(focusedWindow);
-                mFocusedWindow = focusedWindow;
-                continue;
-            }
-
-            matcher = sFocusedAppPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String focusedApp = matcher.group(5);
-                log(focusedApp);
-                mFocusedApp = focusedApp;
-                continue;
-            }
-
-            matcher = sAppTransitionStatePattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String appTransitionState = matcher.group(1);
-                log(appTransitionState);
-                mAppTransitionState = appTransitionState;
-                continue;
-            }
-
-            matcher = sLastAppTransitionPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                final String lastAppTransitionPattern = matcher.group(1);
-                log(lastAppTransitionPattern);
-                mLastTransition = lastAppTransitionPattern;
-                continue;
-            }
-
-            matcher = sStableBoundsPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                int left = Integer.parseInt(matcher.group(1));
-                int top = Integer.parseInt(matcher.group(2));
-                int right = Integer.parseInt(matcher.group(3));
-                int bottom = Integer.parseInt(matcher.group(4));
-                mStableBounds.setBounds(left, top, right - left, bottom - top);
-                log(mStableBounds.toString());
-                continue;
-            }
-
-            matcher = sDefaultPinnedStackBoundsPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                int left = Integer.parseInt(matcher.group(1));
-                int top = Integer.parseInt(matcher.group(2));
-                int right = Integer.parseInt(matcher.group(3));
-                int bottom = Integer.parseInt(matcher.group(4));
-                mDefaultPinnedStackBounds.setBounds(left, top, right - left, bottom - top);
-                log(mDefaultPinnedStackBounds.toString());
-                continue;
-            }
-
-            matcher = sPinnedStackMovementBoundsPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                int left = Integer.parseInt(matcher.group(1));
-                int top = Integer.parseInt(matcher.group(2));
-                int right = Integer.parseInt(matcher.group(3));
-                int bottom = Integer.parseInt(matcher.group(4));
-                mPinnedStackMovementBounds.setBounds(left, top, right - left, bottom - top);
-                log(mPinnedStackMovementBounds.toString());
-                continue;
-            }
-
-            matcher = sInputMethodWindowPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mInputMethodWindowAppToken = matcher.group(1);
-                log(mInputMethodWindowAppToken);
-                continue;
-            }
-
-            matcher = sRotationPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mRotation = Integer.parseInt(matcher.group(1));
-                continue;
-            }
-
-            matcher = sLastOrientationPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mLastOrientation = Integer.parseInt(matcher.group(1));
-                continue;
-            }
-
-            matcher = sDisplayFrozenPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mDisplayFrozen = Boolean.parseBoolean(matcher.group(1));
-                continue;
-            }
-
-            matcher = sDockedStackMinimizedPattern.matcher(line);
-            if (matcher.matches()) {
-                log(line);
-                mIsDockedStackMinimized = Boolean.parseBoolean(matcher.group(1));
-                continue;
-            }
-        }
-    }
-
-    void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
-        tokenList.clear();
-
-        for (WindowState ws : mWindowStates) {
-            if (windowName.equals(ws.getName())) {
-                tokenList.add(ws.getToken());
-            }
-        }
-    }
-
-    void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
-        windowList.clear();
-        for (WindowState ws : mWindowStates) {
-            if (ws.isShown() && windowName.equals(ws.getName())) {
-                windowList.add(ws);
-            }
-        }
-    }
-
-    void getPrefixMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
-        windowList.clear();
-        for (WindowState ws : mWindowStates) {
-            if (ws.isShown() && ws.getName().startsWith(windowName)) {
-                windowList.add(ws);
-            }
-        }
-    }
-
-    WindowState getWindowByPackageName(String packageName, int windowType) {
-        for (WindowState ws : mWindowStates) {
-            final String name = ws.getName();
-            if (name == null || !name.contains(packageName)) {
-                continue;
-            }
-            if (windowType != ws.getType()) {
-                continue;
-            }
-            return ws;
-        }
-
-        return null;
-    }
-
-    void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
-            List<WindowState> outWindowList) {
-        outWindowList.clear();
-        for (WindowState ws : mWindowStates) {
-            final String name = ws.getName();
-            if (name == null || !name.contains(packageName)) {
-                continue;
-            }
-            if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
-                continue;
-            }
-            outWindowList.add(ws);
-        }
-    }
-
-    void sortWindowsByLayer(List<WindowState> windows) {
-        windows.sort(Comparator.comparingInt(WindowState::getLayer));
-    }
-
-    WindowState getWindowStateForAppToken(String appToken) {
-        for (WindowState ws : mWindowStates) {
-            if (ws.getToken().equals(appToken)) {
-                return ws;
-            }
-        }
-        return null;
-    }
-
-    Display getDisplay(int displayId) {
-        for (Display display : mDisplays) {
-            if (displayId == display.getDisplayId()) {
-                return display;
-            }
-        }
-        return null;
-    }
-
-    String getFrontWindow() {
-        if (mWindowStates == null || mWindowStates.isEmpty()) {
-            return null;
-        }
-        return mWindowStates.get(0).getName();
-    }
-
-    String getFocusedWindow() {
-        return mFocusedWindow;
-    }
-
-    String getFocusedApp() {
-        return mFocusedApp;
-    }
-
-    String getLastTransition() {
-        return mLastTransition;
-    }
-
-    String getAppTransitionState() {
-        return mAppTransitionState;
-    }
-
-    int getFrontStackId(int displayId) {
-        return mDisplayStacks.get(displayId).get(0).mStackId;
-    }
-
-    public int getRotation() {
-        return mRotation;
-    }
-
-    int getLastOrientation() {
-        return mLastOrientation;
-    }
-
-    boolean containsStack(int stackId) {
-        for (WindowStack stack : mStacks) {
-            if (stackId == stack.mStackId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Check if there exists a window record with matching windowName. */
-    boolean containsWindow(String windowName) {
-        for (WindowState window : mWindowStates) {
-            if (window.getName().equals(windowName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Check if at least one window which matches provided window name is visible. */
-    boolean isWindowVisible(String windowName) {
-        for (WindowState window : mWindowStates) {
-            if (window.getName().equals(windowName)) {
-                if (window.isShown()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    boolean allWindowsVisible(String windowName) {
-        boolean allVisible = false;
-        for (WindowState window : mWindowStates) {
-            if (window.getName().equals(windowName)) {
-                if (!window.isShown()) {
-                    log("[VISIBLE] not visible" + windowName);
-                    return false;
-                }
-                log("[VISIBLE] visible" + windowName);
-                allVisible = true;
-            }
-        }
-        return allVisible;
-    }
-
-    WindowStack getStack(int stackId) {
-        for (WindowStack stack : mStacks) {
-            if (stackId == stack.mStackId) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
-
-    int getStackPosition(int stackId) {
-        for (Integer displayId : mDisplayStacks.keySet()) {
-            List<WindowStack> stacks = mDisplayStacks.get(displayId);
-            for (int i = 0; i < stacks.size(); i++) {
-                if (stackId == stacks.get(i).mStackId) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    WindowState getInputMethodWindowState() {
-        return getWindowStateForAppToken(mInputMethodWindowAppToken);
-    }
-
-    Rectangle getStableBounds() {
-        return mStableBounds;
-    }
-
-    Rectangle getDefaultPinnedStackBounds() {
-        return mDefaultPinnedStackBounds;
-    }
-
-    Rectangle getPinnedStackMomentBounds() {
-        return mPinnedStackMovementBounds;
-    }
-
-    WindowState findFirstWindowWithType(int type) {
-        for (WindowState window : mWindowStates) {
-            if (window.getType() == type) {
-                return window;
-            }
-        }
-        return null;
-    }
-
-    public boolean isDisplayFrozen() {
-        return mDisplayFrozen;
-    }
-
-    public boolean isDockedStackMinimized() {
-        return mIsDockedStackMinimized;
-    }
-
-    private void reset() {
-        mSysDump.clear();
-        mStacks.clear();
-        mDisplays.clear();
-        mWindowStates.clear();
-        mFocusedWindow = null;
-        mFocusedApp = null;
-        mInputMethodWindowAppToken = null;
-    }
-
-    static class WindowStack extends WindowContainer {
-
-        private static final Pattern sTaskIdPattern = Pattern.compile("taskId=(\\d+)");
-        private static final Pattern sWindowAnimationBackgroundSurfacePattern =
-                Pattern.compile("mWindowAnimationBackgroundSurface:");
-
-        int mStackId;
-        ArrayList<WindowTask> mTasks = new ArrayList();
-        boolean mWindowAnimationBackgroundSurfaceShowing;
-
-        private WindowStack() {
-
-        }
-
-        static WindowStack create(
-                LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = stackIdPattern.matcher(line);
-            if (!matcher.matches()) {
-                // Not a stack.
-                return null;
-            }
-            // For the stack Id line we just read.
-            dump.pop();
-
-            final WindowStack stack = new WindowStack();
-            log(line);
-            final String stackId = matcher.group(1);
-            log(stackId);
-            stack.mStackId = Integer.parseInt(stackId);
-            stack.extract(dump, exitPatterns);
-            return stack;
-        }
-
-        void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
-            final List<Pattern> taskExitPatterns = new ArrayList();
-            Collections.addAll(taskExitPatterns, exitPatterns);
-            taskExitPatterns.add(sTaskIdPattern);
-            taskExitPatterns.add(sWindowAnimationBackgroundSurfacePattern);
-            final Pattern[] taskExitPatternsArray =
-                    taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
-
-            while (!doneExtracting(dump, exitPatterns)) {
-                final WindowTask task =
-                        WindowTask.create(dump, sTaskIdPattern, taskExitPatternsArray);
-
-                if (task != null) {
-                    mTasks.add(task);
-                    continue;
-                }
-
-                final String line = dump.pop().trim();
-
-                if (extractFullscreen(line)) {
-                    continue;
-                }
-
-                if (extractBounds(line)) {
-                    continue;
-                }
-
-                if (extractWindowAnimationBackgroundSurface(line)) {
-                    continue;
-                }
-            }
-        }
-
-        boolean extractWindowAnimationBackgroundSurface(String line) {
-            if (sWindowAnimationBackgroundSurfacePattern.matcher(line).matches()) {
-                log(line);
-                mWindowAnimationBackgroundSurfaceShowing = true;
-                return true;
-            }
-            return false;
-        }
-
-        WindowTask getTask(int taskId) {
-            for (WindowTask task : mTasks) {
-                if (taskId == task.mTaskId) {
-                    return task;
-                }
-            }
-            return null;
-        }
-
-        boolean isWindowAnimationBackgroundSurfaceShowing() {
-            return mWindowAnimationBackgroundSurfaceShowing;
-        }
-    }
-
-    static class WindowTask extends WindowContainer {
-        private static final Pattern sTempInsetBoundsPattern =
-                Pattern.compile("mTempInsetBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
-
-        private static final Pattern sAppTokenPattern = Pattern.compile(
-                "Activity #(\\d+) AppWindowToken\\{(\\S+) token=Token\\{(\\S+) "
-                + "ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}\\}\\}");
-
-
-        int mTaskId;
-        Rectangle mTempInsetBounds;
-        List<String> mAppTokens = new ArrayList();
-
-        private WindowTask() {
-        }
-
-        static WindowTask create(
-                LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            final Matcher matcher = taskIdPattern.matcher(line);
-            if (!matcher.matches()) {
-                // Not a task.
-                return null;
-            }
-            // For the task Id line we just read.
-            dump.pop();
-
-            final WindowTask task = new WindowTask();
-            log(line);
-            final String taskId = matcher.group(1);
-            log(taskId);
-            task.mTaskId = Integer.parseInt(taskId);
-            task.extract(dump, exitPatterns);
-            return task;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-            while (!doneExtracting(dump, exitPatterns)) {
-                final String line = dump.pop().trim();
-
-                if (extractFullscreen(line)) {
-                    continue;
-                }
-
-                if (extractBounds(line)) {
-                    continue;
-                }
-
-                Matcher matcher = sTempInsetBoundsPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    mTempInsetBounds = extractBounds(matcher);
-                }
-
-                matcher = sAppTokenPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(line);
-                    final String appToken = matcher.group(6);
-                    log(appToken);
-                    mAppTokens.add(appToken);
-                    continue;
-                }
-            }
-        }
-    }
-
-    static abstract class WindowContainer {
-        protected static final Pattern sFullscreenPattern = Pattern.compile("mFillsParent=(\\S+)");
-        protected static final Pattern sBoundsPattern =
-                Pattern.compile("mBounds=\\[(-?\\d+),(-?\\d+)\\]\\[(-?\\d+),(-?\\d+)\\]");
-
-        protected boolean mFullscreen;
-        protected Rectangle mBounds;
-
-        static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
-            if (dump.isEmpty()) {
-                return true;
-            }
-            final String line = dump.peek().trim();
-
-            for (Pattern pattern : exitPatterns) {
-                if (pattern.matcher(line).matches()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        boolean extractFullscreen(String line) {
-            final Matcher matcher = sFullscreenPattern.matcher(line);
-            if (!matcher.matches()) {
-                return false;
-            }
-            log(line);
-            final String fullscreen = matcher.group(1);
-            log(fullscreen);
-            mFullscreen = Boolean.valueOf(fullscreen);
-            return true;
-        }
-
-        boolean extractBounds(String line) {
-            final Matcher matcher = sBoundsPattern.matcher(line);
-            if (!matcher.matches()) {
-                return false;
-            }
-            log(line);
-            mBounds = extractBounds(matcher);
-            return true;
-        }
-
-        static Rectangle extractBounds(Matcher matcher) {
-            final int left = Integer.valueOf(matcher.group(1));
-            final int top = Integer.valueOf(matcher.group(2));
-            final int right = Integer.valueOf(matcher.group(3));
-            final int bottom = Integer.valueOf(matcher.group(4));
-            final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
-
-            log(rect.toString());
-            return rect;
-        }
-
-        static void extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList) {
-            for (Rectangle rect : rectList) {
-                if (rect == null) {
-                    return;
-                }
-                final int left = Integer.valueOf(matcher.group(groupIndex++));
-                final int top = Integer.valueOf(matcher.group(groupIndex++));
-                final int right = Integer.valueOf(matcher.group(groupIndex++));
-                final int bottom = Integer.valueOf(matcher.group(groupIndex++));
-                rect.setBounds(left, top, right - left, bottom - top);
-            }
-        }
-
-        Rectangle getBounds() {
-            return mBounds;
-        }
-
-        boolean isFullscreen() {
-            return mFullscreen;
-        }
-    }
-
-    static class Display extends WindowContainer {
-        private static final String TAG = "[Display] ";
-
-        private static final Pattern sDisplayInfoPattern =
-                Pattern.compile("(.+) (\\d+)dpi cur=(\\d+)x(\\d+) app=(\\d+)x(\\d+) (.+)");
-
-        private final int mDisplayId;
-        private Rectangle mDisplayRect = new Rectangle();
-        private Rectangle mAppRect = new Rectangle();
-        private int mDpi;
-
-        private Display(int displayId) {
-            mDisplayId = displayId;
-        }
-
-        int getDisplayId() {
-            return mDisplayId;
-        }
-
-        int getDpi() {
-            return mDpi;
-        }
-
-        Rectangle getDisplayRect() {
-            return mDisplayRect;
-        }
-
-        Rectangle getAppRect() {
-            return mAppRect;
-        }
-
-        static Display create(LinkedList<String> dump, Pattern[] exitPatterns) {
-            // TODO: exit pattern for displays?
-            final String line = dump.peek().trim();
-
-            Matcher matcher = sDisplayIdPattern.matcher(line);
-            if (!matcher.matches()) {
-                return null;
-            }
-
-            log(TAG + "DISPLAY_ID: " + line);
-            dump.pop();
-
-            final int displayId = Integer.valueOf(matcher.group(1));
-            final Display display = new Display(displayId);
-            display.extract(dump, exitPatterns);
-            return display;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-            while (!doneExtracting(dump, exitPatterns)) {
-                final String line = dump.pop().trim();
-
-                final Matcher matcher = sDisplayInfoPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "DISPLAY_INFO: " + line);
-                    mDpi = Integer.valueOf(matcher.group(2));
-
-                    final int displayWidth = Integer.valueOf(matcher.group(3));
-                    final int displayHeight = Integer.valueOf(matcher.group(4));
-                    mDisplayRect.setBounds(0, 0, displayWidth, displayHeight);
-
-                    final int appWidth = Integer.valueOf(matcher.group(5));
-                    final int appHeight = Integer.valueOf(matcher.group(6));
-                    mAppRect.setBounds(0, 0, appWidth, appHeight);
-
-                    // break as we don't need other info for now
-                    break;
-                }
-                // Extract other info here if needed
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect
-                    + " mAppRect=" + mAppRect;
-        }
-    }
-
-    public static class WindowState extends WindowContainer {
-        private static final String TAG = "[WindowState] ";
-
-        public static final int TYPE_WALLPAPER = 2013;
-
-        private static final int WINDOW_TYPE_NORMAL   = 0;
-        private static final int WINDOW_TYPE_STARTING = 1;
-        private static final int WINDOW_TYPE_EXITING  = 2;
-        private static final int WINDOW_TYPE_DEBUGGER = 3;
-
-        private static final String RECT_STR = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]";
-        private static final String NEGATIVE_VALUES_ALLOWED_RECT_STR =
-                "\\[([-\\d]+),([-\\d]+)\\]\\[([-\\d]+),([-\\d]+)\\]";
-        private static final Pattern sMainFramePattern = Pattern.compile("mFrame=" + RECT_STR + ".+");
-        private static final Pattern sFramePattern =
-                Pattern.compile("Frames: containing=" + RECT_STR + " parent=" + RECT_STR);
-        private static final Pattern sContentFramePattern =
-            Pattern.compile("content=" + RECT_STR + " .+");
-        private static final Pattern sWindowAssociationPattern =
-                Pattern.compile("mDisplayId=(\\d+) stackId=(\\d+) (.+)");
-        private static final Pattern sSurfaceInsetsPattern =
-            Pattern.compile("Cur insets.+surface=" + RECT_STR + ".+");
-        private static final Pattern sContentInsetsPattern =
-                Pattern.compile("Cur insets.+content=" + NEGATIVE_VALUES_ALLOWED_RECT_STR + ".+");
-        private static final Pattern sGivenContentInsetsPattern =
-                Pattern.compile("mGivenContentInsets=" + RECT_STR + ".+");
-        private static final Pattern sCropPattern =
-            Pattern.compile(".+mLastClipRect=" + RECT_STR + ".*");
-        private static final Pattern sSurfacePattern =
-                Pattern.compile("Surface: shown=(\\S+) layer=(\\d+) alpha=[\\d.]+ rect=\\([\\d.-]+,[\\d.-]+\\) [\\d.]+ x [\\d.]+.*");
-        private static final Pattern sAttrsPattern=
-                Pattern.compile("mAttrs=WM\\.LayoutParams\\{.*ty=(\\d+).*\\}");
-
-
-        private final String mName;
-        private final String mAppToken;
-        private final int mWindowType;
-        private int mType;
-        private int mDisplayId;
-        private int mStackId;
-        private int mLayer;
-        private boolean mShown;
-        private Rectangle mContainingFrame = new Rectangle();
-        private Rectangle mParentFrame = new Rectangle();
-        private Rectangle mContentFrame = new Rectangle();
-        private Rectangle mFrame = new Rectangle();
-        private Rectangle mSurfaceInsets = new Rectangle();
-        private Rectangle mContentInsets = new Rectangle();
-        private Rectangle mGivenContentInsets = new Rectangle();
-        private Rectangle mCrop = new Rectangle();
-
-
-        private WindowState(Matcher matcher, int windowType) {
-            mName = matcher.group(4);
-            mAppToken = matcher.group(2);
-            mWindowType = windowType;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        String getToken() {
-            return mAppToken;
-        }
-
-        boolean isStartingWindow() {
-            return mWindowType == WINDOW_TYPE_STARTING;
-        }
-
-        boolean isExitingWindow() {
-            return mWindowType == WINDOW_TYPE_EXITING;
-        }
-
-        boolean isDebuggerWindow() {
-            return mWindowType == WINDOW_TYPE_DEBUGGER;
-        }
-
-        int getDisplayId() {
-            return mDisplayId;
-        }
-
-        int getStackId() {
-            return mStackId;
-        }
-
-        int getLayer() {
-            return mLayer;
-        }
-
-        Rectangle getContainingFrame() {
-            return mContainingFrame;
-        }
-
-        Rectangle getFrame() {
-            return mFrame;
-        }
-
-        Rectangle getSurfaceInsets() {
-            return mSurfaceInsets;
-        }
-
-        Rectangle getContentInsets() {
-            return mContentInsets;
-        }
-
-        Rectangle getGivenContentInsets() {
-            return mGivenContentInsets;
-        }
-
-        Rectangle getContentFrame() {
-            return mContentFrame;
-        }
-
-        Rectangle getParentFrame() {
-            return mParentFrame;
-        }
-
-        Rectangle getCrop() {
-            return mCrop;
-        }
-
-        boolean isShown() {
-            return mShown;
-        }
-
-        int getType() {
-            return mType;
-        }
-
-        static WindowState create(LinkedList<String> dump, Pattern[] exitPatterns) {
-            final String line = dump.peek().trim();
-
-            Matcher matcher = sWindowPattern.matcher(line);
-            if (!matcher.matches()) {
-                return null;
-            }
-
-            log(TAG + "WINDOW: " + line);
-            dump.pop();
-
-            final WindowState window;
-            Matcher specialMatcher;
-            if ((specialMatcher = sStartingWindowPattern.matcher(line)).matches()) {
-                log(TAG + "STARTING: " + line);
-                window = new WindowState(specialMatcher, WINDOW_TYPE_STARTING);
-            } else if ((specialMatcher = sExitingWindowPattern.matcher(line)).matches()) {
-                log(TAG + "EXITING: " + line);
-                window = new WindowState(specialMatcher, WINDOW_TYPE_EXITING);
-            } else if ((specialMatcher = sDebuggerWindowPattern.matcher(line)).matches()) {
-                log(TAG + "DEBUGGER: " + line);
-                window = new WindowState(specialMatcher, WINDOW_TYPE_DEBUGGER);
-            } else {
-                window = new WindowState(matcher, WINDOW_TYPE_NORMAL);
-            }
-
-            window.extract(dump, exitPatterns);
-            return window;
-        }
-
-        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-            while (!doneExtracting(dump, exitPatterns)) {
-                final String line = dump.pop().trim();
-
-                Matcher matcher = sWindowAssociationPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "WINDOW_ASSOCIATION: " + line);
-                    mDisplayId = Integer.valueOf(matcher.group(1));
-                    mStackId = Integer.valueOf(matcher.group(2));
-                    continue;
-                }
-
-                matcher = sMainFramePattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "MAIN WINDOW FRAME: " + line);
-                    mFrame = extractBounds(matcher);
-                    continue;
-                }
-
-                matcher = sFramePattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "FRAME: " + line);
-                    extractMultipleBounds(matcher, 1, mContainingFrame, mParentFrame);
-                    continue;
-                }
-
-                matcher = sContentFramePattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "CONTENT FRAME: " + line);
-                    mContentFrame = extractBounds(matcher);
-                }
-
-                matcher = sSurfaceInsetsPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "INSETS: " + line);
-                    mSurfaceInsets = extractBounds(matcher);
-                }
-
-                matcher = sContentInsetsPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "CONTENT INSETS: " + line);
-                    mContentInsets = extractBounds(matcher);
-                }
-
-                matcher = sCropPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "CROP: " + line);
-                    mCrop = extractBounds(matcher);
-                }
-
-                matcher = sSurfacePattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "SURFACE: " + line);
-                    mShown = Boolean.valueOf(matcher.group(1));
-                    mLayer = Integer.valueOf(matcher.group(2));
-                }
-
-                matcher = sAttrsPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "ATTRS: " + line);
-                    mType = Integer.valueOf(matcher.group(1));
-                }
-
-                matcher = sGivenContentInsetsPattern.matcher(line);
-                if (matcher.matches()) {
-                    log(TAG + "GIVEN CONTENT INSETS: " + line);
-                    mGivenContentInsets = extractBounds(matcher);
-                }
-
-                // Extract other info here if needed
-            }
-        }
-
-        private static String getWindowTypeSuffix(int windowType) {
-            switch (windowType) {
-            case WINDOW_TYPE_STARTING: return " STARTING";
-            case WINDOW_TYPE_EXITING: return " EXITING";
-            case WINDOW_TYPE_DEBUGGER: return " DEBUGGER";
-            default: break;
-            }
-            return "";
-        }
-
-        @Override
-        public String toString() {
-            return "WindowState: {" + mAppToken + " " + mName
-                    + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
-                    + " cf=" + mContainingFrame + " pf=" + mParentFrame;
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk
deleted file mode 100644
index 0739743..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk
+++ /dev/null
@@ -1,38 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE := CtsWindowManagerHostTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed CtsServicesHostTestCases
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    cts-amwm-util \
-    platform-test-annotations-host
-
-LOCAL_CTS_TEST_PACKAGE := android.server.cts
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-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/services/activityandwindowmanager/windowmanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
deleted file mode 100644
index 82f0099..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS window manager host test cases">
-    <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="CtsDragAndDropSourceApp.apk" />
-        <option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
-        <option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk" />
-        <option name="test-file-name" value="CtsDeviceWindowFramesTestApp.apk" />
-        <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk" />
-        <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk" />
-    </target_preparer>
-    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="CtsWindowManagerHostTestCases.jar" />
-        <option name="runtime-hint" value="20m40s" />
-    </test>
-</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
deleted file mode 100644
index dc2485d..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
+++ /dev/null
@@ -1,33 +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)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, ../alertwindowappsdk25/src) \
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
deleted file mode 100755
index 9c6a6ad..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +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"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.alertwindowapp">
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
-    <application android:label="CtsAlertWindow">
-        <activity android:name=".AlertWindowTestActivity"
-                  android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
-            <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/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
deleted file mode 100644
index db007e2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowapp;
-
-import android.os.Bundle;
-import android.server.alertwindowappsdk25.AlertWindowTestBaseActivity;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
-public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
-    private static final int[] ALERT_WINDOW_TYPES = {
-            TYPE_PHONE,
-            TYPE_PRIORITY_PHONE,
-            TYPE_SYSTEM_ALERT,
-            TYPE_SYSTEM_ERROR,
-            TYPE_SYSTEM_OVERLAY,
-            TYPE_APPLICATION_OVERLAY
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        createAllAlertWindows(getPackageName());
-    }
-
-    @Override
-    protected int[] getAlertWindowTypes() {
-        return ALERT_WINDOW_TYPES;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
deleted file mode 100644
index 5246c94..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
+++ /dev/null
@@ -1,32 +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)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := 25
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestAppSdk25
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
deleted file mode 100755
index efc80ea..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +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.server.alertwindowappsdk25">
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
-    <application android:label="CtsAlertWindowSdk25">
-        <activity android:name=".AlertWindowTestActivitySdk25"
-                  android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
-            <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/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
deleted file mode 100644
index 046879f..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowappsdk25;
-
-import android.os.Bundle;
-
-import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
-public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
-    private static final int[] ALERT_WINDOW_TYPES = {
-            TYPE_PHONE,
-            TYPE_PRIORITY_PHONE,
-            TYPE_SYSTEM_ALERT,
-            TYPE_SYSTEM_ERROR,
-            TYPE_SYSTEM_OVERLAY
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        createAllAlertWindows(getPackageName());
-    }
-
-    @Override
-    protected int[] getAlertWindowTypes() {
-        return ALERT_WINDOW_TYPES;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
deleted file mode 100644
index 2d0dad0..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowappsdk25;
-
-import android.app.Activity;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.util.Log;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-
-public abstract class AlertWindowTestBaseActivity extends Activity {
-
-    protected void createAllAlertWindows(String windowName) {
-        final int[] alertWindowTypes = getAlertWindowTypes();
-        for (int type : alertWindowTypes) {
-            try {
-                createAlertWindow(type, windowName);
-            } catch (Exception e) {
-                Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
-            }
-        }
-    }
-
-    protected void createAlertWindow(int type) {
-        createAlertWindow(type, getPackageName());
-    }
-
-    protected void createAlertWindow(int type, String windowName) {
-        if (!isSystemAlertWindowType(type)) {
-            throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
-        }
-
-        final Point size = new Point();
-        final WindowManager wm = getSystemService(WindowManager.class);
-        wm.getDefaultDisplay().getSize(size);
-
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
-        params.width = size.x / 3;
-        params.height = size.y / 3;
-        params.gravity = TOP | LEFT;
-        params.setTitle(windowName);
-
-        final TextView view = new TextView(this);
-        view.setText(windowName + "   type=" + type);
-        view.setBackgroundColor(Color.RED);
-        wm.addView(view, params);
-    }
-
-    private boolean isSystemAlertWindowType(int type) {
-        final int[] alertWindowTypes = getAlertWindowTypes();
-        for (int current : alertWindowTypes) {
-            if (current == type) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    protected abstract int[] getAlertWindowTypes();
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk
deleted file mode 100644
index 12a04ed..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-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
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml
deleted file mode 100644
index 296a979..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.wm.cts.dndsourceapp">
-    <application android:label="CtsDnDSource">
-        <activity android:name="android.wm.cts.dndsourceapp.DragSource">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <provider android:name="android.wm.cts.dndsourceapp.DragSourceContentProvider"
-                  android:authorities="android.wm.cts.dndsource.contentprovider"
-                  android:grantUriPermissions="true"/>
-    </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
deleted file mode 100644
index 7cb7b8b..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
+++ /dev/null
@@ -1,131 +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.wm.cts.dndsourceapp;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.FileUriExposedException;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
-
-import java.io.File;
-
-public class DragSource extends Activity{
-    private static final String LOG_TAG = "DragSource";
-
-    private static final String RESULT_KEY_START_DRAG = "START_DRAG";
-    private static final String RESULT_KEY_DETAILS = "DETAILS";
-    private static final String RESULT_OK = "OK";
-    private static final String RESULT_EXCEPTION = "Exception";
-
-    private static final String URI_PREFIX =
-            "content://" + DragSourceContentProvider.AUTHORITY + "/data";
-
-    private static final String MAGIC_VALUE = "42";
-    private static final long TIMEOUT_CANCEL = 150;
-
-    private TextView mTextView;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        View view = getLayoutInflater().inflate(R.layout.source_activity, null);
-        setContentView(view);
-
-        final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
-
-        setUpDragSource("disallow_global", plainUri, 0);
-        setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
-
-        setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
-        setUpDragSource("grant_read", plainUri,
-                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
-        setUpDragSource("grant_write", plainUri,
-                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_WRITE);
-        setUpDragSource("grant_read_persistable", plainUri,
-                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
-                        View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION);
-
-        final Uri prefixUri = Uri.parse(URI_PREFIX);
-
-        setUpDragSource("grant_read_prefix", prefixUri,
-                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
-                        View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
-        setUpDragSource("grant_read_noprefix", prefixUri,
-                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
-
-        final Uri fileUri = Uri.fromFile(new File("/sdcard/sample.jpg"));
-
-        setUpDragSource("file_local", fileUri, 0);
-        setUpDragSource("file_global", fileUri, View.DRAG_FLAG_GLOBAL);
-    }
-
-    private void setUpDragSource(String mode, final Uri uri, final int flags) {
-        if (!mode.equals(getIntent().getStringExtra("mode"))) {
-            return;
-        }
-        mTextView = (TextView) findViewById(R.id.drag_source);
-        mTextView.setText(mode);
-        mTextView.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                if (event.getAction() != MotionEvent.ACTION_DOWN) {
-                    return false;
-                }
-                try {
-                    final ClipDescription clipDescription = new ClipDescription("", new String[] {
-                            ClipDescription.MIMETYPE_TEXT_URILIST });
-                    PersistableBundle extras = new PersistableBundle(1);
-                    extras.putString("extraKey", "extraValue");
-                    clipDescription.setExtras(extras);
-                    final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri));
-                    v.startDragAndDrop(
-                            clipData,
-                            new View.DragShadowBuilder(v),
-                            null,
-                            flags);
-                    logResult(RESULT_KEY_START_DRAG, RESULT_OK);
-                } catch (FileUriExposedException e) {
-                    logResult(RESULT_KEY_DETAILS, e.getMessage());
-                    logResult(RESULT_KEY_START_DRAG, RESULT_EXCEPTION);
-                }
-                if (mode.equals("cancel_soon")) {
-                    new Handler().postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            v.cancelDragAndDrop();
-                        }
-                    }, TIMEOUT_CANCEL);
-                }
-                return true;
-            }
-        });
-    }
-
-    private void logResult(String key, String value) {
-        Log.i(LOG_TAG, key + "=" + value);
-        mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
deleted file mode 100644
index 06a94aa..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
+++ /dev/null
@@ -1,71 +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.wm.cts.dndsourceapp;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class DragSourceContentProvider extends ContentProvider {
-
-    public static final String AUTHORITY = "android.wm.cts.dndsource.contentprovider";
-
-    private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
-    private static final int URI_DATA = 1;
-
-    static {
-        sMatcher.addURI(AUTHORITY, "data/#", URI_DATA);
-    }
-
-    @Override
-    public boolean onCreate() {
-        return false;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                        String sortOrder) {
-        switch (sMatcher.match(uri)) {
-            case URI_DATA:
-                return new DragSourceCursor(uri.getLastPathSegment());
-        }
-        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/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
deleted file mode 100644
index c54df65..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
+++ /dev/null
@@ -1,80 +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.wm.cts.dndsourceapp;
-
-import android.database.AbstractCursor;
-
-public class DragSourceCursor extends AbstractCursor {
-    private static final String COLUMN_KEY = "key";
-
-    private final String mValue;
-
-    public DragSourceCursor(String value) {
-        mValue = value;
-    }
-
-    @Override
-    public int getCount() {
-        return 1;
-    }
-
-    @Override
-    public String[] getColumnNames() {
-        return new String[] {COLUMN_KEY};
-    }
-
-    @Override
-    public String getString(int column) {
-        if (getPosition() != 0) {
-            throw new IllegalArgumentException("Incorrect position: " + getPosition());
-        }
-        if (column != 0) {
-            throw new IllegalArgumentException("Incorrect column: " + column);
-        }
-        return mValue;
-    }
-
-    @Override
-    public short getShort(int column) {
-        return 0;
-    }
-
-    @Override
-    public int getInt(int column) {
-        return 0;
-    }
-
-    @Override
-    public long getLong(int column) {
-        return 0;
-    }
-
-    @Override
-    public float getFloat(int column) {
-        return 0;
-    }
-
-    @Override
-    public double getDouble(int column) {
-        return 0;
-    }
-
-    @Override
-    public boolean isNull(int column) {
-        return false;
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk
deleted file mode 100644
index cb43a8b..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-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
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml
deleted file mode 100644
index ed7b9c2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.wm.cts.dndtargetapp">
-    <application android:label="CtsDnDTarget">
-        <activity android:name="android.wm.cts.dndtargetapp.DropTarget">
-            <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/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
deleted file mode 100644
index 8892c6f..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
+++ /dev/null
@@ -1,308 +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.wm.cts.dndtargetapp;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.DragAndDropPermissions;
-import android.view.DragEvent;
-import android.view.View;
-import android.widget.TextView;
-
-public class DropTarget extends Activity {
-    public static final String LOG_TAG = "DropTarget";
-
-    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
-    private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
-    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
-    private static final String RESULT_KEY_DROP_RESULT = "DROP";
-    private static final String RESULT_KEY_DETAILS = "DETAILS";
-    private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
-    private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
-    private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
-    private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
-    private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
-
-    public static final String RESULT_OK = "OK";
-    public static final String RESULT_EXCEPTION = "Exception";
-    public static final String RESULT_MISSING = "MISSING";
-    public static final String RESULT_LEAKING = "LEAKING";
-
-    protected static final String MAGIC_VALUE = "42";
-
-    private TextView mTextView;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        View view = getLayoutInflater().inflate(R.layout.target_activity, null);
-        setContentView(view);
-
-        setUpDropTarget("request_none", new OnDragUriReadListener(false));
-        setUpDropTarget("request_read", new OnDragUriReadListener());
-        setUpDropTarget("request_write", new OnDragUriWriteListener());
-        setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
-        setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
-    }
-
-    private void setUpDropTarget(String mode, OnDragUriListener listener) {
-        if (!mode.equals(getIntent().getStringExtra("mode"))) {
-            return;
-        }
-        mTextView = (TextView)findViewById(R.id.drag_target);
-        mTextView.setText(mode);
-        mTextView.setOnDragListener(listener);
-    }
-
-    private String checkExtraValue(DragEvent event) {
-        PersistableBundle extras = event.getClipDescription().getExtras();
-        if (extras == null) {
-            return "Null";
-        }
-
-        final String value = extras.getString("extraKey");
-        if ("extraValue".equals(value)) {
-            return RESULT_OK;
-        }
-        return value;
-    }
-
-    private void logResult(String key, String value) {
-        Log.i(LOG_TAG, key + "=" + value);
-        mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
-    }
-
-    private abstract class OnDragUriListener implements View.OnDragListener {
-        private final boolean requestPermissions;
-
-        public OnDragUriListener(boolean requestPermissions) {
-            this.requestPermissions = requestPermissions;
-        }
-
-        @Override
-        public boolean onDrag(View v, DragEvent event) {
-            checkDragEvent(event);
-
-            switch (event.getAction()) {
-                case DragEvent.ACTION_DRAG_STARTED:
-                    logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
-                    logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
-                    return true;
-
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    return true;
-
-                case DragEvent.ACTION_DRAG_LOCATION:
-                    return true;
-
-                case DragEvent.ACTION_DRAG_EXITED:
-                    return true;
-
-                case DragEvent.ACTION_DROP:
-                    // Try accessing the Uri without the permissions grant.
-                    accessContent(event, RESULT_KEY_ACCESS_BEFORE, false);
-
-                    // Try accessing the Uri with the permission grant (if required);
-                    accessContent(event, RESULT_KEY_DROP_RESULT, requestPermissions);
-
-                    // Try accessing the Uri after the permissions have been released.
-                    accessContent(event, RESULT_KEY_ACCESS_AFTER, false);
-                    return true;
-
-                case DragEvent.ACTION_DRAG_ENDED:
-                    logResult(RESULT_KEY_DRAG_ENDED, RESULT_OK);
-                    return true;
-
-                default:
-                    return false;
-            }
-        }
-
-        private void accessContent(DragEvent event, String resultKey, boolean requestPermissions) {
-            String result;
-            try {
-                result = processDrop(event, requestPermissions);
-            } catch (SecurityException e) {
-                result = RESULT_EXCEPTION;
-                if (resultKey.equals(RESULT_KEY_DROP_RESULT)) {
-                    logResult(RESULT_KEY_DETAILS, e.getMessage());
-                }
-            }
-            logResult(resultKey, result);
-        }
-
-        private String processDrop(DragEvent event, boolean requestPermissions) {
-            final ClipData clipData = event.getClipData();
-            if (clipData == null) {
-                return "Null ClipData";
-            }
-            if (clipData.getItemCount() == 0) {
-                return "Empty ClipData";
-            }
-            ClipData.Item item = clipData.getItemAt(0);
-            if (item == null) {
-                return "Null ClipData.Item";
-            }
-            Uri uri = item.getUri();
-            if (uri == null) {
-                return "Null Uri";
-            }
-
-            DragAndDropPermissions permissions = null;
-            if (requestPermissions) {
-                permissions = requestDragAndDropPermissions(event);
-                if (permissions == null) {
-                    return "Null DragAndDropPermissions";
-                }
-            }
-
-            try {
-                return processUri(uri);
-            } finally {
-                if (permissions != null) {
-                    permissions.release();
-                }
-            }
-        }
-
-        abstract protected String processUri(Uri uri);
-    }
-
-    private void checkDragEvent(DragEvent event) {
-        final int action = event.getAction();
-
-        // ClipData should be available for ACTION_DROP only.
-        final ClipData clipData = event.getClipData();
-        if (action == DragEvent.ACTION_DROP) {
-            if (clipData == null) {
-                logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
-            }
-        } else {
-            if (clipData != null) {
-                logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_LEAKING + action);
-            }
-        }
-
-        // ClipDescription should be always available except for ACTION_DRAG_ENDED.
-        final ClipDescription clipDescription = event.getClipDescription();
-        if (action != DragEvent.ACTION_DRAG_ENDED) {
-            if (clipDescription == null) {
-                logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING + action);
-            }
-        } else {
-            if (clipDescription != null) {
-                logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_LEAKING);
-            }
-        }
-
-        // Local state should be always null for cross-app drags.
-        final Object localState = event.getLocalState();
-        if (localState != null) {
-            logResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_LEAKING + action);
-        }
-    }
-
-    private class OnDragUriReadListener extends OnDragUriListener {
-        OnDragUriReadListener(boolean requestPermissions) {
-            super(requestPermissions);
-        }
-
-        OnDragUriReadListener() {
-            super(true);
-        }
-
-        protected String processUri(Uri uri) {
-            return checkQueryResult(uri, MAGIC_VALUE);
-        }
-
-        protected String checkQueryResult(Uri uri, String expectedValue) {
-            Cursor cursor = null;
-            try {
-                cursor = getContentResolver().query(uri, null, null, null, null);
-                if (cursor == null) {
-                    return "Null Cursor";
-                }
-                cursor.moveToPosition(0);
-                String value = cursor.getString(0);
-                if (!expectedValue.equals(value)) {
-                    return "Wrong value: " + value;
-                }
-                return RESULT_OK;
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-    }
-
-    private class OnDragUriWriteListener extends OnDragUriListener {
-        OnDragUriWriteListener() {
-            super(true);
-        }
-
-        protected String processUri(Uri uri) {
-            ContentValues values = new ContentValues();
-            values.put("key", 100);
-            getContentResolver().update(uri, values, null, null);
-            return RESULT_OK;
-        }
-    }
-
-    private class OnDragUriReadPrefixListener extends OnDragUriReadListener {
-        @Override
-        protected String processUri(Uri uri) {
-            final String result1 = queryPrefixed(uri, "1");
-            if (!result1.equals(RESULT_OK)) {
-                return result1;
-            }
-            final String result2 = queryPrefixed(uri, "2");
-            if (!result2.equals(RESULT_OK)) {
-                return result2;
-            }
-            return queryPrefixed(uri, "3");
-        }
-
-        private String queryPrefixed(Uri uri, String selector) {
-            final Uri prefixedUri = Uri.parse(uri.toString() + "/" + selector);
-            return checkQueryResult(prefixedUri, selector);
-        }
-    }
-
-    private class OnDragUriTakePersistableListener extends OnDragUriListener {
-        OnDragUriTakePersistableListener() {
-            super(true);
-        }
-
-        @Override
-        protected String processUri(Uri uri) {
-            getContentResolver().takePersistableUriPermission(
-                    uri, View.DRAG_FLAG_GLOBAL_URI_READ);
-            getContentResolver().releasePersistableUriPermission(
-                    uri, View.DRAG_FLAG_GLOBAL_URI_READ);
-            return RESULT_OK;
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk
deleted file mode 100644
index a33e1bc..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 23
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropTargetAppSdk23
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml
deleted file mode 100644
index ea636a8..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.wm.cts.dndtargetappsdk23">
-    <application android:label="CtsDnDTarget">
-        <activity android:name="android.wm.cts.dndtargetappsdk23.DropTarget">
-            <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/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java
deleted file mode 100644
index 2cb7779..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java
+++ /dev/null
@@ -1,86 +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.wm.cts.dndtargetappsdk23;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * This application is compiled against SDK 23 and used to verify that apps targeting SDK 23 and
- * below do not receive global drags.
- */
-public class DropTarget extends Activity {
-    public static final String LOG_TAG = "DropTarget";
-
-    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
-    private static final String RESULT_KEY_DROP_RESULT = "DROP";
-
-    public static final String RESULT_OK = "OK";
-
-    private TextView mTextView;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        View view = getLayoutInflater().inflate(R.layout.target_activity, null);
-        setContentView(view);
-
-        mTextView = (TextView) findViewById(R.id.drag_target);
-        mTextView.setOnDragListener(new OnDragListener());
-    }
-
-    private void logResult(String key, String value) {
-        String result = key + "=" + value;
-        Log.i(LOG_TAG, result);
-        mTextView.setText(result);
-    }
-
-    private class OnDragListener implements View.OnDragListener {
-        @Override
-        public boolean onDrag(View v, DragEvent event) {
-            switch (event.getAction()) {
-                case DragEvent.ACTION_DRAG_STARTED:
-                    logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
-                    return true;
-
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    return true;
-
-                case DragEvent.ACTION_DRAG_LOCATION:
-                    return true;
-
-                case DragEvent.ACTION_DRAG_EXITED:
-                    return true;
-
-                case DragEvent.ACTION_DROP:
-                    logResult(RESULT_KEY_DROP_RESULT, RESULT_OK);
-                    return true;
-
-                case DragEvent.ACTION_DRAG_ENDED:
-                    return true;
-
-                default:
-                    return false;
-            }
-        }
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk
deleted file mode 100644
index d6e82d3..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk
+++ /dev/null
@@ -1,31 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDeviceWindowFramesTestApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml
deleted file mode 100755
index 7f899c2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.FrameTestApp">
-    <application android:theme="@android:style/Theme.Material">
-        <activity android:name=".DialogTestActivity"
-                android:exported="true"
-        />
-        <activity android:name=".MovingChildTestActivity"
-                  android:exported="true"
-        />
-    </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java
deleted file mode 100644
index 593cf34..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.FrameTestApp;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.view.Window;
-import android.view.Gravity;
-
-public class DialogTestActivity extends Activity {
-
-    AlertDialog mDialog;
-
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-    }
-
-    protected void onStop() {
-        super.onStop();
-        mDialog.dismiss();
-    }
-    protected void onResume() {
-        super.onResume();
-        setupTest(getIntent());
-    }
-
-    private void setupTest(Intent intent) {
-        String testCase = intent.getStringExtra(
-                "android.server.FrameTestApp.DialogTestCase");
-        switch (testCase) {
-           case "MatchParent": {
-               testMatchParent();
-               break;
-           } case "MatchParentLayoutInOverscan": {
-               testMatchParentLayoutInOverscan();
-           }  break;
-           case "ExplicitSize": {
-               testExplicitSize();
-               break;
-           }
-           case "ExplicitSizeTopLeftGravity": {
-               testExplicitSizeTopLeftGravity();
-               break;
-           }
-           case "ExplicitSizeBottomRightGravity": {
-               testExplicitSizeBottomRightGravity();
-               break;
-           }
-           case "OversizedDimensions": {
-               testOversizedDimensions();
-               break;
-           }
-           case "OversizedDimensionsNoLimits": {
-               testOversizedDimensionsNoLimits();
-               break;
-           }
-           case "ExplicitPositionMatchParent": {
-               testExplicitPositionMatchParent();
-               break;
-           }
-           case "ExplicitPositionMatchParentNoLimits": {
-               testExplicitPositionMatchParentNoLimits();
-               break;
-           }
-           case "NoFocus": {
-               testNoFocus();
-               break;
-           }
-           case "WithMargins": {
-               testWithMargins();
-               break;
-           }
-           default:
-               break;
-        }
-    }
-
-    interface DialogLayoutParamsTest {
-        void doSetup(WindowManager.LayoutParams p);
-    }
-
-    private void doLayoutParamTest(DialogLayoutParamsTest t) {
-        mDialog = new AlertDialog.Builder(this).create();
-
-        mDialog.setMessage("Testing is fun!");
-        mDialog.setTitle("android.server.FrameTestApp/android.server.FrameTestApp.TestDialog");
-        mDialog.create();
-
-        Window w = mDialog.getWindow();
-        final WindowManager.LayoutParams params = w.getAttributes();
-        t.doSetup(params);
-        w.setAttributes(params);
-
-        mDialog.show();
-    }
-
-    private void testMatchParent() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.MATCH_PARENT;
-        });
-    }
-
-    private void testMatchParentLayoutInOverscan() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.MATCH_PARENT;
-            params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-            params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
-        });
-    }
-
-    private void testExplicitSize() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = 200;
-            params.height = 200;
-        });
-    }
-
-    private void testExplicitSizeTopLeftGravity() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = 200;
-            params.height = 200;
-            params.gravity = Gravity.TOP | Gravity.LEFT;
-        });
-    }
-
-    private void testExplicitSizeBottomRightGravity() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = 200;
-            params.height = 200;
-            params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
-        });
-    }
-
-    private void testOversizedDimensions() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = 100000;
-            params.height = 100000;
-        });
-    }
-
-    private void testOversizedDimensionsNoLimits() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = 5000;
-            params.height = 5000;
-            params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-            params.gravity = Gravity.LEFT | Gravity.TOP;
-        });
-    }
-
-    private void testExplicitPositionMatchParent() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.MATCH_PARENT;
-            params.x = 100;
-            params.y = 100;
-        });
-    }
-
-    private void testExplicitPositionMatchParentNoLimits() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.MATCH_PARENT;
-            params.gravity = Gravity.LEFT | Gravity.TOP;
-            params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-            params.x = 100;
-            params.y = 100;
-        });
-    }
-
-    private void testNoFocus() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        });
-    }
-
-    private void testWithMargins() {
-        doLayoutParamTest((WindowManager.LayoutParams params) -> {
-            params.gravity = Gravity.LEFT | Gravity.TOP;
-            params.horizontalMargin = .25f;
-            params.verticalMargin = .35f;
-            params.width = 200;
-            params.height = 200;
-            params.x = 0;
-            params.y = 0;
-        });
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java
deleted file mode 100644
index de6f597..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.FrameTestApp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.view.Window;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.Space;
-import android.widget.Button;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-// This activity will parent a Child to the main window, and then move
-// the main window around. We can use this to verify the Child
-// is properly updated.
-public class MovingChildTestActivity extends Activity {
-    Space mView;
-    int mX = 0;
-    int mY = 0;
-
-    final Runnable moveWindow = new Runnable() {
-            @Override
-            public void run() {
-                final Window w = getWindow();
-                final WindowManager.LayoutParams attribs = w.getAttributes();
-                attribs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-                attribs.x = mX % 1000;
-                attribs.y = mY % 1000;
-                w.setAttributes(attribs);
-                mX += 5;
-                mY += 5;
-                mView.postDelayed(this, 50);
-            }
-    };
-
-    final Runnable makeChild = new Runnable() {
-            @Override
-            public void run() {
-                Button b = new Button(MovingChildTestActivity.this);
-                WindowManager.LayoutParams p = new WindowManager.LayoutParams(
-                        WindowManager.LayoutParams.TYPE_APPLICATION_PANEL);
-                p.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-                p.x = 0;
-                p.y = 0;
-                p.token = mView.getWindowToken();
-                p.setTitle("ChildWindow");
-
-                ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).addView(b, p);
-
-                mView.postDelayed(moveWindow, 50);
-            }
-    };
-
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final LayoutParams p = new LayoutParams(100, 100);
-        final Window w = getWindow();
-        w.setLayout(100, 100);
-        mView = new Space(this);
-
-        setContentView(mView, p);
-        mView.post(makeChild);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
deleted file mode 100644
index 3290ddb..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsWindowManagerHostTestCases android.server.cts.AlertWindowsTests
- */
-@Presubmit
-public class AlertWindowsTests extends ActivityManagerTestBase {
-
-    private static final String PACKAGE_NAME = "android.server.alertwindowapp";
-    private static final String ACTIVITY_NAME = "AlertWindowTestActivity";
-    private static final String SDK_25_PACKAGE_NAME = "android.server.alertwindowappsdk25";
-    private static final String SDK_25_ACTIVITY_NAME = "AlertWindowTestActivitySdk25";
-
-    // From WindowManager.java
-    private static final int TYPE_BASE_APPLICATION      = 1;
-    private static final int FIRST_SYSTEM_WINDOW        = 2000;
-
-    private static final int TYPE_PHONE                 = FIRST_SYSTEM_WINDOW + 2;
-    private static final int TYPE_SYSTEM_ALERT          = FIRST_SYSTEM_WINDOW + 3;
-    private static final int TYPE_SYSTEM_OVERLAY        = FIRST_SYSTEM_WINDOW + 6;
-    private static final int TYPE_PRIORITY_PHONE        = FIRST_SYSTEM_WINDOW + 7;
-    private static final int TYPE_SYSTEM_ERROR          = FIRST_SYSTEM_WINDOW + 10;
-    private static final int TYPE_APPLICATION_OVERLAY   = FIRST_SYSTEM_WINDOW + 38;
-
-    private static final int TYPE_STATUS_BAR            = FIRST_SYSTEM_WINDOW;
-    private static final int TYPE_INPUT_METHOD          = FIRST_SYSTEM_WINDOW + 11;
-    private static final int TYPE_NAVIGATION_BAR        = FIRST_SYSTEM_WINDOW + 19;
-
-    private final List<Integer> mAlertWindowTypes = Arrays.asList(
-            TYPE_PHONE,
-            TYPE_PRIORITY_PHONE,
-            TYPE_SYSTEM_ALERT,
-            TYPE_SYSTEM_ERROR,
-            TYPE_SYSTEM_OVERLAY,
-            TYPE_APPLICATION_OVERLAY);
-    private final List<Integer> mSystemWindowTypes = Arrays.asList(
-            TYPE_STATUS_BAR,
-            TYPE_INPUT_METHOD,
-            TYPE_NAVIGATION_BAR);
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        try {
-            setAlertWindowPermission(PACKAGE_NAME, false);
-            setAlertWindowPermission(SDK_25_PACKAGE_NAME, false);
-            executeShellCommand("am force-stop " + PACKAGE_NAME);
-            executeShellCommand("am force-stop " + SDK_25_PACKAGE_NAME);
-        } catch (DeviceNotAvailableException e) {
-        }
-    }
-
-    public void testAlertWindowAllowed() throws Exception {
-        runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, true /* hasAlertWindowPermission */,
-                true /* atLeastO */);
-    }
-
-    public void testAlertWindowDisallowed() throws Exception {
-        runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, false /* hasAlertWindowPermission */,
-                true /* atLeastO */);
-    }
-
-    public void testAlertWindowAllowedSdk25() throws Exception {
-        runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
-                true /* hasAlertWindowPermission */, false /* atLeastO */);
-    }
-
-    public void testAlertWindowDisallowedSdk25() throws Exception {
-        runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
-                false /* hasAlertWindowPermission */, false /* atLeastO */);
-    }
-
-    private void runAlertWindowTest(String packageName, String activityName,
-            boolean hasAlertWindowPermission, boolean atLeastO) throws Exception {
-        setComponentName(packageName);
-        setAlertWindowPermission(packageName, hasAlertWindowPermission);
-
-        executeShellCommand(getAmStartCmd(activityName));
-        mAmWmState.computeState(mDevice, new String[] { activityName });
-        mAmWmState.assertVisibility(activityName, true);
-
-        assertAlertWindows(packageName, hasAlertWindowPermission, atLeastO);
-    }
-
-    private boolean allWindowsHidden(ArrayList<WindowManagerState.WindowState> windows) {
-        for (WindowManagerState.WindowState ws : windows) {
-            if (ws.isShown()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void assertAlertWindows(String packageName, boolean hasAlertWindowPermission,
-            boolean atLeastO) throws DeviceNotAvailableException {
-        final WindowManagerState wMState = mAmWmState.getWmState();
-
-        final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList();
-        wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
-
-        if (!hasAlertWindowPermission) {
-            // When running in VR Mode, an App Op restriction is
-            // 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());
-                assertTrue("All alert windows should be hidden",
-                        allWindowsHidden(alertWindows));
-            } else {
-                assertTrue("Should be empty alertWindows=" + alertWindows, alertWindows.isEmpty());
-            }
-            return;
-        }
-
-        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);
-            }
-        }
-
-        final WindowManagerState.WindowState mainAppWindow =
-                wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
-
-        assertNotNull(mainAppWindow);
-
-        wMState.sortWindowsByLayer(alertWindows);
-        final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
-        final WindowManagerState.WindowState highestAlertWindow =
-                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, lowestAlertWindow.getLayer() > mainAppWindow.getLayer());
-
-        // 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,
-                    highestAlertWindow.getLayer() < appOverlayWindow.getLayer());
-        }
-
-        // Assert that alert windows are below key system windows.
-        final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList();
-        wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
-        if (!systemWindows.isEmpty()) {
-            wMState.sortWindowsByLayer(systemWindows);
-            final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
-            assertTrue("highestAlertWindow=" + highestAlertWindow
-                    + " greater than lowestSystemWindow=" + lowestSystemWindow,
-                    highestAlertWindow.getLayer() < lowestSystemWindow.getLayer());
-        }
-    }
-
-    private void setAlertWindowPermission(String packageName, boolean allow) throws Exception {
-        executeShellCommand("appops set " + packageName + " android:system_alert_window "
-                + (allow ? "allow" : "deny"));
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java
deleted file mode 100644
index 218fcfe..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import static android.server.cts.StateLogger.logE;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.awt.Rectangle;
-
-import com.android.ddmlib.Log.LogLevel;
-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;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import android.server.cts.ActivityManagerTestBase;
-import android.server.cts.WindowManagerState.WindowState;
-
-public class ChildMovementTests extends ParentChildTestBase {
-    private List<WindowState> mWindowList = new ArrayList();
-
-    @Override
-    String intentKey() {
-        return "android.server.FrameTestApp.ChildTestCase";
-    }
-
-    @Override
-    String activityName() {
-        return "MovingChildTestActivity";
-    }
-
-    WindowState getSingleWindow(String fullWindowName) {
-        try {
-            mAmWmState.getWmState().getMatchingVisibleWindowState(fullWindowName, mWindowList);
-            return mWindowList.get(0);
-        } catch (Exception e) {
-            CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + fullWindowName);
-            return null;
-        }
-    }
-
-    WindowState getSingleWindowByPrefix(String prefix) {
-        try {
-            mAmWmState.getWmState().getPrefixMatchingVisibleWindowState(prefix, mWindowList);
-            return mWindowList.get(0);
-        } catch (Exception e) {
-            CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + prefix);
-            return null;
-        }
-    }
-
-    void doSingleTest(ParentChildTest t) throws Exception {
-        String popupName = "ChildWindow";
-        final String[] waitForVisible = new String[] { popupName };
-
-        mAmWmState.setUseActivityNamesForWindowNames(false);
-        mAmWmState.computeState(mDevice, waitForVisible);
-        WindowState popup = getSingleWindowByPrefix(popupName);
-        WindowState parent = getSingleWindow(getBaseWindowName() + activityName());
-
-        t.doTest(parent, popup);
-    }
-
-
-    Object monitor = new Object();
-    boolean testPassed = false;
-    String popupName = null;
-    String mainName = null;
-
-    SurfaceTraceReceiver.SurfaceObserver observer = new SurfaceTraceReceiver.SurfaceObserver() {
-        int transactionCount = 0;
-        boolean sawChildMove = false;
-        boolean sawMainMove = false;
-        int timesSeen = 0;
-
-        @Override
-        public void openTransaction() {
-            transactionCount++;
-            if (transactionCount == 1) {
-                sawChildMove = false;
-                sawMainMove = false;
-            }
-        }
-
-        @Override
-        public void closeTransaction() {
-            transactionCount--;
-            if (transactionCount != 0) {
-                return;
-            }
-            synchronized (monitor) {
-                if (sawChildMove ^ sawMainMove ) {
-                    monitor.notifyAll();
-                    return;
-                }
-                if (timesSeen > 10) {
-                    testPassed = true;
-                    monitor.notifyAll();
-                }
-            }
-        }
-
-        @Override
-        public void setPosition(String windowName, float x, float y) {
-            if (windowName.equals(popupName)) {
-                sawChildMove = true;
-                timesSeen++;
-            } else if (windowName.equals(mainName)) {
-                sawMainMove = true;
-            }
-        }
-    };
-
-    /**
-     * Here we test that a Child moves in the same transaction
-     * as its parent. We launch an activity with a Child which will
-     * move around its own main window. Then we listen to WindowManager transactions.
-     * Since the Child is static within the window, if we ever see one of
-     * them move xor the other one we have a problem!
-     */
-    public void testSurfaceMovesWithParent() throws Exception {
-        doFullscreenTest("MovesWithParent",
-            (WindowState parent, WindowState popup) -> {
-                    popupName = popup.getName();
-                    mainName = parent.getName();
-                    installSurfaceObserver(observer);
-                    try {
-                        synchronized (monitor) {
-                            monitor.wait(5000);
-                        }
-                    } catch (InterruptedException e) {
-                    } finally {
-                        assertTrue(testPassed);
-                        removeSurfaceObserver();
-                    }
-            });
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java
deleted file mode 100644
index 17ede35..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-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;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class CrossAppDragAndDropTests extends DeviceTestCase {
-    // Constants copied from ActivityManager.StackId. If they are changed there, these must be
-    // updated.
-    /** ID of stack where fullscreen activities are normally launched into. */
-    private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
-    /** ID of stack where freeform/resized activities are normally launched into. */
-    private static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
-    /** ID of stack that occupies a dedicated region of the screen. */
-    private static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
-    /** ID of stack that always on top (always visible) when it exists. */
-    private static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
-    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 INPUT_MOUSE_SWIPE = "input mouse swipe ";
-    private static final String TASK_ID_PREFIX = "taskId";
-
-    // Regex pattern to match adb shell am stack list output of the form:
-    // taskId=<TASK_ID>: <componentName> bounds=[LEFT,TOP][RIGHT,BOTTOM]
-    private static final String TASK_REGEX_PATTERN_STRING =
-            "taskId=[0-9]+: %s bounds=\\[[0-9]+,[0-9]+\\]\\[[0-9]+,[0-9]+\\]";
-
-    private static final int SWIPE_DURATION_MS = 500;
-
-    private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
-    private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
-    private static final String TARGET_23_PACKAGE_NAME = "android.wm.cts.dndtargetappsdk23";
-
-
-    private static final String SOURCE_ACTIVITY_NAME = "DragSource";
-    private static final String TARGET_ACTIVITY_NAME = "DropTarget";
-
-    private static final String FILE_GLOBAL = "file_global";
-    private static final String FILE_LOCAL = "file_local";
-    private static final String DISALLOW_GLOBAL = "disallow_global";
-    private static final String CANCEL_SOON = "cancel_soon";
-    private static final String GRANT_NONE = "grant_none";
-    private static final String GRANT_READ = "grant_read";
-    private static final String GRANT_WRITE = "grant_write";
-    private static final String GRANT_READ_PREFIX = "grant_read_prefix";
-    private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
-    private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
-
-    private static final String REQUEST_NONE = "request_none";
-    private static final String REQUEST_READ = "request_read";
-    private static final String REQUEST_READ_NESTED = "request_read_nested";
-    private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
-    private static final String REQUEST_WRITE = "request_write";
-
-    private static final String SOURCE_LOG_TAG = "DragSource";
-    private static final String TARGET_LOG_TAG = "DropTarget";
-
-    private static final String RESULT_KEY_START_DRAG = "START_DRAG";
-    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
-    private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
-    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
-    private static final String RESULT_KEY_DROP_RESULT = "DROP";
-    private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
-    private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
-    private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
-    private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
-    private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
-
-    private static final String RESULT_MISSING = "Missing";
-    private static final String RESULT_OK = "OK";
-    private static final String RESULT_EXCEPTION = "Exception";
-    private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
-
-    private static final String AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
-            "am supports-split-screen-multi-window";
-
-    private ITestDevice mDevice;
-
-    private Map<String, String> mSourceResults;
-    private Map<String, String> mTargetResults;
-
-    private String mSourcePackageName;
-    private String mTargetPackageName;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mDevice = getDevice();
-
-        if (!supportsDragAndDrop()) {
-            return;
-        }
-
-        mSourcePackageName = SOURCE_PACKAGE_NAME;
-        mTargetPackageName = TARGET_PACKAGE_NAME;
-        cleanupState();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
-        if (!supportsDragAndDrop()) {
-            return;
-        }
-
-        mDevice.executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
-        mDevice.executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
-    }
-
-    private String executeShellCommand(String command) throws DeviceNotAvailableException {
-        return mDevice.executeShellCommand(command);
-    }
-
-    private void clearLogs() throws DeviceNotAvailableException {
-        executeShellCommand("logcat -c");
-    }
-
-    private String getStartCommand(String componentName, String modeExtra) {
-        return AM_START_N + componentName + " -e mode " + modeExtra;
-    }
-
-    private String getMoveTaskCommand(int taskId, int stackId) throws Exception {
-        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;
-    }
-
-    /**
-     * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
-     * 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();
-
-        // Reinitialize the docked stack to force the window manager to reset its default bounds.
-        // See b/29068935.
-        clearLogs();
-        final String componentName = getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME);
-        executeShellCommand(getStartCommand(componentName, null) + " --stack " +
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        final int taskId = getActivityTaskId(componentName);
-        // Moving a task from the full screen stack to the docked stack resets
-        // WindowManagerService#mDockedStackCreateBounds.
-        executeShellCommand(getMoveTaskCommand(taskId, DOCKED_STACK_ID));
-        waitForResume(mSourcePackageName, SOURCE_ACTIVITY_NAME);
-        executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
-
-        // Remove special stacks.
-        executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
-        executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
-        executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
-    }
-
-    private void launchDockedActivity(String packageName, String activityName, String mode)
-            throws Exception {
-        clearLogs();
-        final String componentName = getComponentName(packageName, activityName);
-        executeShellCommand(getStartCommand(componentName, mode) + " --stack " + DOCKED_STACK_ID);
-        waitForResume(packageName, activityName);
-    }
-
-    private void launchFullscreenActivity(String packageName, String activityName, String mode)
-            throws Exception {
-        clearLogs();
-        final String componentName = getComponentName(packageName, activityName);
-        executeShellCommand(getStartCommand(componentName, mode) + " --stack "
-                + FULLSCREEN_WORKSPACE_STACK_ID);
-        waitForResume(packageName, activityName);
-    }
-
-    /**
-     * @param displaySize size of the display
-     * @param leftSide {@code true} to launch the app taking up the left half of the display,
-     *         {@code false} to launch the app taking up the right half of the display.
-     */
-    private void launchFreeformActivity(String packageName, String activityName, String mode,
-            Point displaySize, boolean leftSide) throws Exception{
-        clearLogs();
-        final String componentName = getComponentName(packageName, activityName);
-        executeShellCommand(getStartCommand(componentName, mode) + " --stack "
-                + FREEFORM_WORKSPACE_STACK_ID);
-        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));
-    }
-
-    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");
-    }
-
-    private void injectInput(Point from, Point to, int durationMs) throws Exception {
-        executeShellCommand(
-                INPUT_MOUSE_SWIPE + from.x + " " + from.y + " " + to.x + " " + to.y + " " +
-                durationMs);
-    }
-
-    static class Point {
-        public int x, y;
-
-        public Point(int _x, int _y) {
-            x=_x;
-            y=_y;
-        }
-
-        public Point() {}
-    }
-
-    private String findTaskInfo(String name) throws Exception {
-        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-        mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
-        final String output = outputReceiver.getOutput();
-        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);
-        CLog.i(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;
-            }
-        }
-        return "";
-    }
-
-    private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
-        final String taskInfo = findTaskInfo(name);
-        final String[] sections = taskInfo.split("\\[");
-        if (sections.length > 2) {
-            try {
-                parsePoint(sections[1], from);
-                parsePoint(sections[2], to);
-                return true;
-            } catch (Exception e) {
-                return false;
-            }
-        }
-        return false;
-    }
-
-    private int getActivityTaskId(String name) throws Exception {
-        final String taskInfo = findTaskInfo(name);
-        for (String word : taskInfo.split("\\s+")) {
-            if (word.startsWith(TASK_ID_PREFIX)) {
-                final String withColon = word.split("=")[1];
-                return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
-            }
-        }
-        return -1;
-    }
-
-    private Point getDisplaySize() throws Exception {
-        final String output = executeShellCommand("wm size");
-        final String[] sizes = output.split(" ")[2].split("x");
-        return new Point(Integer.valueOf(sizes[0].trim()), Integer.valueOf(sizes[1].trim()));
-    }
-
-    private Point getWindowCenter(String name) throws Exception {
-        Point p1 = new Point();
-        Point p2 = new Point();
-        if (getWindowBounds(name, p1, p2)) {
-            return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
-        }
-        return null;
-    }
-
-    private void parsePoint(String string, Point point) {
-        final String[] parts = string.split("[,|\\]]");
-        point.x = Integer.parseInt(parts[0]);
-        point.y = Integer.parseInt(parts[1]);
-    }
-
-    private void unlockDevice() throws DeviceNotAvailableException {
-        // Wake up the device, if necessary.
-        executeShellCommand("input keyevent 224");
-        // Unlock the screen.
-        executeShellCommand("input keyevent 82");
-    }
-
-    private Map<String, String> getLogResults(String className, String lastResultKey)
-            throws Exception {
-        int retryCount = 10;
-        Map<String, String> output = new HashMap<String, String>();
-        do {
-
-            String logs = executeShellCommand("logcat -v brief -d " + className + ":I" + " *:S");
-            for (String line : logs.split("\\n")) {
-                if (line.startsWith("I/" + className)) {
-                    String payload = line.split(":")[1].trim();
-                    final String[] split = payload.split("=");
-                    if (split.length > 1) {
-                        output.put(split[0], split[1]);
-                    }
-                }
-            }
-            if (output.containsKey(lastResultKey)) {
-                return output;
-            }
-        } while (retryCount-- > 0);
-        return output;
-    }
-
-    private void assertDropResult(String sourceMode, String targetMode, String expectedDropResult)
-            throws Exception {
-        assertDragAndDropResults(sourceMode, targetMode, RESULT_OK, expectedDropResult, RESULT_OK);
-    }
-
-    private void assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)
-            throws Exception {
-        assertDragAndDropResults(
-                sourceMode, REQUEST_NONE, expectedStartDragResult, RESULT_MISSING, RESULT_MISSING);
-    }
-
-    private void assertDragAndDropResults(String sourceMode, String targetMode,
-                                          String expectedStartDragResult, String expectedDropResult,
-                                          String expectedListenerResults) throws Exception {
-        if (!supportsDragAndDrop()) {
-            return;
-        }
-
-        if (supportsSplitScreenMultiWindow()) {
-            launchDockedActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode);
-            launchFullscreenActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode);
-        } else if (supportsFreeformMultiWindow()) {
-            // Fallback to try to launch two freeform windows side by side.
-            Point displaySize = getDisplaySize();
-            launchFreeformActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode,
-                    displaySize, true /* leftSide */);
-            launchFreeformActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode,
-                    displaySize, false /* leftSide */);
-        } else {
-            return;
-        }
-
-        clearLogs();
-
-        injectInput(
-                getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME)),
-                getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME)),
-                SWIPE_DURATION_MS);
-
-        mSourceResults = getLogResults(SOURCE_LOG_TAG, RESULT_KEY_START_DRAG);
-        assertSourceResult(RESULT_KEY_START_DRAG, expectedStartDragResult);
-
-        mTargetResults = getLogResults(TARGET_LOG_TAG, RESULT_KEY_DRAG_ENDED);
-        assertTargetResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
-        if (!RESULT_MISSING.equals(expectedDropResult)) {
-            assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
-            assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
-        }
-        assertListenerResults(expectedListenerResults);
-    }
-
-    private void assertListenerResults(String expectedResult) throws Exception {
-        assertTargetResult(RESULT_KEY_DRAG_STARTED, expectedResult);
-        assertTargetResult(RESULT_KEY_DRAG_ENDED, expectedResult);
-        assertTargetResult(RESULT_KEY_EXTRAS, expectedResult);
-
-        assertTargetResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
-        assertTargetResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING);
-        assertTargetResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_MISSING);
-    }
-
-    private void assertSourceResult(String resultKey, String expectedResult) throws Exception {
-        assertResult(mSourceResults, resultKey, expectedResult);
-    }
-
-    private void assertTargetResult(String resultKey, String expectedResult) throws Exception {
-        assertResult(mTargetResults, resultKey, expectedResult);
-    }
-
-    private void assertResult(Map<String, String> results, String resultKey, String expectedResult)
-            throws Exception {
-        if (!supportsDragAndDrop()) {
-            return;
-        }
-
-        if (RESULT_MISSING.equals(expectedResult)) {
-            if (results.containsKey(resultKey)) {
-                fail("Unexpected " + resultKey + "=" + results.get(resultKey));
-            }
-        } else {
-            assertTrue("Missing " + resultKey, results.containsKey(resultKey));
-            assertEquals(resultKey + " result mismatch,", expectedResult,
-                    results.get(resultKey));
-        }
-    }
-
-    private boolean supportsDragAndDrop() throws Exception {
-        String supportsMultiwindow = mDevice.executeShellCommand("am supports-multiwindow").trim();
-        if ("true".equals(supportsMultiwindow)) {
-            return true;
-        } else if ("false".equals(supportsMultiwindow)) {
-            return false;
-        } else {
-            throw new Exception(
-                    "device does not support \"am supports-multiwindow\" shell command.");
-        }
-    }
-
-    private boolean supportsSplitScreenMultiWindow() throws DeviceNotAvailableException {
-        return !executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW).startsWith("false");
-    }
-
-    private boolean supportsFreeformMultiWindow() throws DeviceNotAvailableException {
-        return mDevice.hasFeature("feature:android.software.freeform_window_management");
-    }
-
-    public void testCancelSoon() throws Exception {
-        assertDropResult(CANCEL_SOON, REQUEST_NONE, RESULT_MISSING);
-    }
-
-    public void testDisallowGlobal() throws Exception {
-        assertNoGlobalDragEvents(DISALLOW_GLOBAL, RESULT_OK);
-    }
-
-    public void testDisallowGlobalBelowSdk24() throws Exception {
-        mTargetPackageName = TARGET_23_PACKAGE_NAME;
-        assertNoGlobalDragEvents(GRANT_NONE, RESULT_OK);
-    }
-
-    public void testFileUriLocal() throws Exception {
-        assertNoGlobalDragEvents(FILE_LOCAL, RESULT_OK);
-    }
-
-    public void testFileUriGlobal() throws Exception {
-        assertNoGlobalDragEvents(FILE_GLOBAL, RESULT_EXCEPTION);
-    }
-
-    public void testGrantNoneRequestNone() throws Exception {
-        assertDropResult(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
-    }
-
-    public void testGrantNoneRequestRead() throws Exception {
-        assertDropResult(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
-    }
-
-    public void testGrantNoneRequestWrite() throws Exception {
-        assertDropResult(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
-    }
-
-    public void testGrantReadRequestNone() throws Exception {
-        assertDropResult(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
-    }
-
-    public void testGrantReadRequestRead() throws Exception {
-        assertDropResult(GRANT_READ, REQUEST_READ, RESULT_OK);
-    }
-
-    public void testGrantReadRequestWrite() throws Exception {
-        assertDropResult(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
-    }
-
-    public void testGrantReadNoPrefixRequestReadNested() throws Exception {
-        assertDropResult(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
-    }
-
-    public void testGrantReadPrefixRequestReadNested() throws Exception {
-        assertDropResult(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
-    }
-
-    public void testGrantPersistableRequestTakePersistable() throws Exception {
-        assertDropResult(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
-    }
-
-    public void testGrantReadRequestTakePersistable() throws Exception {
-        assertDropResult(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
-    }
-
-    public void testGrantWriteRequestNone() throws Exception {
-        assertDropResult(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
-    }
-
-    public void testGrantWriteRequestRead() throws Exception {
-        assertDropResult(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
-    }
-
-    public void testGrantWriteRequestWrite() throws Exception {
-        assertDropResult(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java
deleted file mode 100644
index c99f001..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.awt.Rectangle;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import android.server.cts.WindowManagerState.WindowState;
-
-public class DialogFrameTests extends ParentChildTestBase {
-    private List<WindowState> mWindowList = new ArrayList();
-
-    @Override
-    String intentKey() {
-        return "android.server.FrameTestApp.DialogTestCase";
-    }
-
-    @Override
-    String activityName() {
-        return "DialogTestActivity";
-    }
-
-    WindowState getSingleWindow(String windowName) {
-        try {
-            mAmWmState.getWmState().getMatchingVisibleWindowState(
-                    getBaseWindowName() + windowName, mWindowList);
-            return mWindowList.get(0);
-        } catch (Exception e) {
-            CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + windowName);
-            return null;
-        }
-    }
-
-    void doSingleTest(ParentChildTest t) throws Exception {
-        final String[] waitForVisible = new String[] { "TestDialog" };
-
-        mAmWmState.computeState(mDevice, waitForVisible);
-        WindowState dialog = getSingleWindow("TestDialog");
-        WindowState parent = getSingleWindow("DialogTestActivity");
-
-        t.doTest(parent, dialog);
-    }
-
-    // With Width and Height as MATCH_PARENT we should fill
-    // the same content frame as the main activity window
-    public void testMatchParentDialog() throws Exception {
-        doParentChildTest("MatchParent",
-            (WindowState parent, WindowState dialog) -> {
-                assertEquals(parent.getContentFrame(), dialog.getFrame());
-            });
-    }
-
-    // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
-    // we will not be constrained to the insets and so we will be the same size
-    // as the main window main frame.
-    public void testMatchParentDialogLayoutInOverscan() throws Exception {
-        doParentChildTest("MatchParentLayoutInOverscan",
-            (WindowState parent, WindowState dialog) -> {
-                assertEquals(parent.getFrame(), dialog.getFrame());
-            });
-    }
-
-    static final int explicitDimension = 200;
-
-    // The default gravity for dialogs should center them.
-    public void testExplicitSizeDefaultGravity() throws Exception {
-        doParentChildTest("ExplicitSize",
-            (WindowState parent, WindowState dialog) -> {
-                Rectangle contentFrame = parent.getContentFrame();
-                Rectangle expectedFrame = new Rectangle(
-                        contentFrame.x + (contentFrame.width - explicitDimension)/2,
-                        contentFrame.y + (contentFrame.height - explicitDimension)/2,
-                        explicitDimension, explicitDimension);
-                assertEquals(expectedFrame, dialog.getFrame());
-            });
-    }
-
-    public void testExplicitSizeTopLeftGravity() throws Exception {
-        doParentChildTest("ExplicitSizeTopLeftGravity",
-            (WindowState parent, WindowState dialog) -> {
-                Rectangle contentFrame = parent.getContentFrame();
-                Rectangle expectedFrame = new Rectangle(
-                        contentFrame.x,
-                        contentFrame.y,
-                        explicitDimension,
-                        explicitDimension);
-                assertEquals(expectedFrame, dialog.getFrame());
-            });
-    }
-
-    public void testExplicitSizeBottomRightGravity() throws Exception {
-        doParentChildTest("ExplicitSizeBottomRightGravity",
-            (WindowState parent, WindowState dialog) -> {
-                Rectangle contentFrame = parent.getContentFrame();
-                Rectangle expectedFrame = new Rectangle(
-                        contentFrame.x + contentFrame.width - explicitDimension,
-                        contentFrame.y + contentFrame.height - explicitDimension,
-                        explicitDimension, explicitDimension);
-                assertEquals(expectedFrame, dialog.getFrame());
-            });
-    }
-
-    // TODO: Commented out for now because it doesn't work. We end up
-    // insetting the decor on the bottom. I think this is a bug
-    // probably in the default dialog flags:
-    // b/30127373
-    //    public void testOversizedDimensions() throws Exception {
-    //        doParentChildTest("OversizedDimensions",
-    //            (WindowState parent, WindowState dialog) -> {
-    // With the default flags oversize should result in clipping to
-    // parent frame.
-    //                assertEquals(parent.getContentFrame(), dialog.getFrame());
-    //         });
-    //    }
-
-    // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size.
-    // static final int oversizedDimension = 5000;
-    // With FLAG_LAYOUT_NO_LIMITS  we should get the size we request, even if its much
-    // larger than the screen.
-    // public void testOversizedDimensionsNoLimits() throws Exception {
-    // TODO(b/36890978): We only run this in fullscreen because of the
-    // unclear status of NO_LIMITS for non-child surfaces in MW modes
-    //     doFullscreenTest("OversizedDimensionsNoLimits",
-    //        (WindowState parent, WindowState dialog) -> {
-    //            Rectangle contentFrame = parent.getContentFrame();
-    //            Rectangle expectedFrame = new Rectangle(contentFrame.x, contentFrame.y,
-    //                    oversizedDimension, oversizedDimension);
-    //            assertEquals(expectedFrame, dialog.getFrame());
-    //        });
-    // }
-
-    // If we request the MATCH_PARENT and a non-zero position, we wouldn't be
-    // able to fit all of our content, so we should be adjusted to just fit the
-    // content frame.
-    public void testExplicitPositionMatchParent() throws Exception {
-        doParentChildTest("ExplicitPositionMatchParent",
-             (WindowState parent, WindowState dialog) -> {
-                    assertEquals(parent.getContentFrame(),
-                            dialog.getFrame());
-             });
-    }
-
-    // Unless we pass NO_LIMITS in which case our requested position should
-    // be honored.
-    public void testExplicitPositionMatchParentNoLimits() throws Exception {
-        final int explicitPosition = 100;
-        doParentChildTest("ExplicitPositionMatchParentNoLimits",
-            (WindowState parent, WindowState dialog) -> {
-                Rectangle contentFrame = parent.getContentFrame();
-                Rectangle expectedFrame = new Rectangle(contentFrame.x + explicitPosition,
-                        contentFrame.y + explicitPosition,
-                        contentFrame.width,
-                        contentFrame.height);
-            });
-    }
-
-    // We run the two focus tests fullscreen only because switching to the
-    // docked stack will strip away focus from the task anyway.
-    public void testDialogReceivesFocus() throws Exception {
-        doFullscreenTest("MatchParent",
-            (WindowState parent, WindowState dialog) -> {
-                assertEquals(dialog.getName(), mAmWmState.getWmState().getFocusedWindow());
-        });
-    }
-
-    public void testNoFocusDialog() throws Exception {
-        doFullscreenTest("NoFocus",
-            (WindowState parent, WindowState dialog) -> {
-                assertEquals(parent.getName(), mAmWmState.getWmState().getFocusedWindow());
-        });
-    }
-
-    public void testMarginsArePercentagesOfContentFrame() throws Exception {
-        float horizontalMargin = .25f;
-        float verticalMargin = .35f;
-        doParentChildTest("WithMargins",
-            (WindowState parent, WindowState dialog) -> {
-                Rectangle frame = parent.getContentFrame();
-                Rectangle expectedFrame = new Rectangle(
-                        (int)(horizontalMargin*frame.width + frame.x),
-                        (int)(verticalMargin*frame.height + frame.y),
-                        explicitDimension,
-                        explicitDimension);
-                assertEquals(expectedFrame, dialog.getFrame());
-                });
-    }
-
-    public void testDialogPlacedAboveParent() throws Exception {
-        doParentChildTest("MatchParent",
-            (WindowState parent, WindowState dialog) -> {
-                // Not only should the dialog be higher, but it should be
-                // leave multiple layers of space inbetween for DimLayers,
-                // etc...
-                assertTrue(dialog.getLayer() - parent.getLayer() >= 5);
-        });
-    }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java
deleted file mode 100644
index c8ca9f4..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.ActivityManagerTestBase;
-
-public abstract class ParentChildTestBase extends ActivityManagerTestBase {
-    private static final String COMPONENT_NAME = "android.server.FrameTestApp";
-
-    interface ParentChildTest {
-        void doTest(WindowState parent, WindowState child);
-    }
-
-    public void startTestCase(String testCase) throws Exception {
-        setComponentName(COMPONENT_NAME);
-        String cmd = getAmStartCmd(activityName(), intentKey(), testCase);
-        CLog.logAndDisplay(LogLevel.INFO, cmd);
-        executeShellCommand(cmd);
-    }
-
-    public void startTestCaseDocked(String testCase) throws Exception {
-        setComponentName(COMPONENT_NAME);
-        String cmd = getAmStartCmd(activityName(), intentKey(), testCase);
-        CLog.logAndDisplay(LogLevel.INFO, cmd);
-        executeShellCommand(cmd);
-        moveActivityToDockStack(activityName());
-    }
-
-    abstract String intentKey();
-    abstract String activityName();
-
-    abstract void doSingleTest(ParentChildTest t) throws Exception;
-
-    void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
-        CLog.logAndDisplay(LogLevel.INFO, "Running test fullscreen");
-        startTestCase(testCase);
-        doSingleTest(t);
-        stopTestCase();
-    }
-
-    void doDockedTest(String testCase, ParentChildTest t) throws Exception {
-        CLog.logAndDisplay(LogLevel.INFO, "Running test docked");
-        if (!supportsSplitScreenMultiWindow()) {
-            CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
-            return;
-        }
-        startTestCaseDocked(testCase);
-        doSingleTest(t);
-        stopTestCase();
-    }
-
-    void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
-        doFullscreenTest(testCase, t);
-        doDockedTest(testCase, t);
-    }
-}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
index d6077a4..d7517cd 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
index 75b3ca0..d84c7b5 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.content.pm.cts.shortcut.backup.launcher1">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
index b82bb1d..e76cf28 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
 
+import android.content.pm.ShortcutInfo;
 import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
 
 public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
@@ -57,9 +58,22 @@
                 .selectByIds("ms1")
                 .areAllNotPinned();
 
+        // Package3 doesn't support backup&restore.
+        // However, the manifest-shortcuts will be republished anyway, so they're still pinned.
+        // The dynamic shortcuts can't be restored, but we'll still restore them as disabled
+        // shortcuts that are not visible to the publisher.
         assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
-                .haveIds("ms1", "ms2")
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
                 .areAllEnabled()
-                .areAllNotPinned(); // P3 doesn't get backed up, so no longer pinned.
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+
+                .revertToOriginalList()
+                .selectByIds("s1", "s2")
+                .areAllDisabled()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED)
+                ;
     }
 }
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
index 8f124a0..8a6440d 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
index 71ffc61..9297220 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.content.pm.cts.shortcut.backup.launcher2">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
index 75c79c5..115c476 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
 
+import android.content.pm.ShortcutInfo;
 import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
 
 public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
@@ -51,8 +52,22 @@
                 .selectByIds("ms2")
                 .areAllNotPinned();
 
+        // Package3 doesn't support backup&restore.
+        // However, the manifest-shortcuts will be republished anyway, so they're still pinned.
+        // The dynamic shortcuts can't be restored, but we'll still restore them as disabled
+        // shortcuts that are not visible to the publisher.
         assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
-                .haveIds("ms1", "ms2")
-                .areAllEnabled();
+                .haveIds("ms1", "ms2", "s2", "s3")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+
+                .revertToOriginalList()
+                .selectByIds("s2", "s3")
+                .areAllDisabled()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED)
+        ;
     }
 }
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
index 9883e5c..be8edb3 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
index 82dc28f..8f1c1a7 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.content.pm.cts.shortcut.backup.launcher3">
 
     <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk
new file mode 100644
index 0000000..97287fb
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk
@@ -0,0 +1,44 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher4new
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../launcher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml
new file mode 100644
index 0000000..1032971
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.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.content.pm.cts.shortcut.backup.launcher4"
+    android:versionCode="11">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.launcher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk
new file mode 100644
index 0000000..6389d22
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk
@@ -0,0 +1,44 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher4old
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml
new file mode 100644
index 0000000..e7c81b3
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.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.content.pm.cts.shortcut.backup.launcher4"
+    android:versionCode="10">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.launcher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java
new file mode 100644
index 0000000..33215d6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java
@@ -0,0 +1,39 @@
+/*
+ * 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.content.pm.cts.shortcut.backup.launcher4;
+
+import android.app.Activity;
+import android.content.pm.LauncherApps;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+public class MainActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Just accept all requests.
+        if (LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT.equals(getIntent().getAction())) {
+            LauncherApps.PinItemRequest request = getSystemService(LauncherApps.class)
+                    .getPinItemRequest(getIntent());
+            final PersistableBundle extras = request.getShortcutInfo().getExtras();
+            if (extras != null && extras.getBoolean("acceptit")) {
+                request.accept();
+            }
+        }
+        finish();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..f9ecfef
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.content.pm.cts.shortcut.backup.launcher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testRestoredOnOldVersion() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllManifest()
+
+                // s1 is re-published, so it's enabled and dynamic.
+                .revertToOriginalList()
+                .selectByIds("s1")
+                .areAllEnabled()
+                .areAllDynamic()
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+                    // The app re-published the shortcut, not updated, so the fields that existed
+                    // in the original shortcut is gone now.
+                    assertNull(si.getExtras());
+                })
+
+                .revertToOriginalList()
+                .selectByIds("s2")
+                .areAllDisabled()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+                .forAllShortcuts(si -> {
+                    // Note the label shouldn't be updated with the updateShortcuts() call.
+                    assertEquals("shortlabel2", si.getShortLabel());
+                })
+                .areAllNotDynamic();
+    }
+
+    public void testRestoredOnNewVersion() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllManifest()
+
+                // s1 is re-published, so it's enabled and dynamic.
+                .revertToOriginalList()
+                .selectByIds("s1", "s2")
+                .areAllEnabled()
+
+                .revertToOriginalList()
+                .selectByIds("s1")
+                .areAllDynamic()
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+                    // The app re-published the shortcut, not updated, so the fields that existed
+                    // in the original shortcut is gone now.
+                    assertNull(si.getExtras());
+                })
+
+                .revertToOriginalList()
+                .selectByIds("s2")
+                .areAllNotDynamic()
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel2_updated", si.getShortLabel());
+                })
+                ;
+    }
+
+
+    public void testRestoreWrongKey() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllManifest()
+
+                // s1 is re-published, so it's enabled and dynamic.
+                .revertToOriginalList()
+                .selectByIds("s1")
+                .areAllEnabled()
+                .areAllDynamic()
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+                    // The app re-published the shortcut, not updated, so the fields that existed
+                    // in the original shortcut is gone now.
+                    assertNull(si.getExtras());
+                })
+
+
+                // updateShortcuts() shouldn't work on it, so it keeps the original label.
+                .revertToOriginalList()
+                .selectByIds("s2")
+                .areAllNotDynamic()
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel2", si.getShortLabel());
+                })
+        ;
+    }
+
+    public void testRestoreNoManifestOnOldVersion() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("s1", "ms1")
+                .areAllEnabled()
+                .areAllDynamic()
+                .areAllMutable()
+
+                .revertToOriginalList()
+                .selectByIds("s2", "ms2")
+                .areAllDisabled();
+    }
+
+    public void testRestoreNoManifestOnNewVersion() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllPinned()
+
+                .selectByIds("ms1", "ms2")
+                .areAllDisabled()
+                .areAllImmutable()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_APP_CHANGED)
+
+                .revertToOriginalList()
+                .selectByIds("s1", "s2")
+                .areAllEnabled()
+                .areAllMutable();
+    }
+
+    public void testInvisibleIgnored() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+
+                .selectByIds("ms1", "s1", "s2")
+                .areAllPinned()
+                .areAllDisabled()
+                .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+                .areAllNotDynamic()
+                .areAllNotManifest()
+
+                .revertToOriginalList()
+                .selectByIds("ms2")
+                .areAllEnabled()
+                .areAllPinned()
+                .areAllNotDynamic()
+                .areAllNotManifest();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..8d99255
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    static final String PUBLISHER4_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher4";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testPreBackup() {
+        // Pin all the shortcuts.
+        getLauncherApps().pinShortcuts(PUBLISHER4_PKG, list("s1", "s2", "ms1", "ms2"),
+                getUserHandle());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
index 72da903..ac91c82 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
index 3d23c72..f35fcef 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="android.content.pm.cts.shortcut.backup.publisher1">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
index 286dded..2bb7f06 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
index c3271b4..e4c5720 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="android.content.pm.cts.shortcut.backup.publisher2">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
index f1421d1..9a5f530 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
@@ -37,6 +37,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
index 5e03ff8..9f47f2a 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="android.content.pm.cts.shortcut.backup.publisher3">
 
     <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="MainActivity" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
index 870dab9..33c8f82 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
@@ -21,16 +21,17 @@
 
 public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
     public void testWithUninstall() {
+        // backup = false, so dynamic shouldn't be restored.
         assertWith(getManager().getDynamicShortcuts())
                 .isEmpty();
 
-        // backup = false, so no pinned shortcuts should be restored.
+        // But manifest shortcuts will be restored anyway, so they'll restored as pinned.
         assertWith(getManager().getPinnedShortcuts())
-                .isEmpty();
+                .haveIds("ms1", "ms2");
 
         assertWith(getManager().getManifestShortcuts())
                 .haveIds("ms1", "ms2")
-                .areAllNotPinned();
+                .areAllPinned();
     }
 
     public void testWithNoUninstall() {
@@ -39,8 +40,8 @@
                 .haveIds("s1", "s2", "s3")
                 .areAllNotPinned();
 
+        // Manifest shortcuts should still be published.
         assertWith(getManager().getManifestShortcuts())
-                .haveIds("ms1", "ms2")
-                .areAllNotPinned();
+                .haveIds("ms1", "ms2");
     }
 }
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk
new file mode 100644
index 0000000..71a545c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk
@@ -0,0 +1,46 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml
new file mode 100644
index 0000000..c671d95
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="11">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk
new file mode 100644
index 0000000..1e35a01
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk
@@ -0,0 +1,46 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_nobackup
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml
new file mode 100644
index 0000000..e176c81
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="11">
+
+    <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk
new file mode 100644
index 0000000..321377e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk
@@ -0,0 +1,46 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_nomanifest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml
new file mode 100644
index 0000000..a08611a
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="11">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.mk
new file mode 100644
index 0000000..76410e8
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_wrongkey
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml
new file mode 100644
index 0000000..e176c81
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="11">
+
+    <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk
new file mode 100644
index 0000000..7341d66
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk
@@ -0,0 +1,44 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4old
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml
new file mode 100644
index 0000000..0c9f4ab
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="10">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml
new file mode 100644
index 0000000..5872ed1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="label1">Shortcut 1</string>
+    <string name="long_label1">Long shortcut label1</string>
+    <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+    <string name="label2">Shortcut 2</string>
+    <string name="long_label2">Long shortcut label2</string>
+    <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+    <string name="label3">Shortcut 3</string>
+    <string name="long_label3">Long shortcut label3</string>
+    <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+    <string name="label4">Shortcut 4</string>
+    <string name="long_label4">Long shortcut label4</string>
+    <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+    <string name="label5">Shortcut 5</string>
+    <string name="long_label5">Long shortcut label5</string>
+    <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.xml
new file mode 100644
index 0000000..11cede6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:icon="@drawable/black_16x16"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms2"
+        android:shortcutShortLabel="@string/label2">
+        android:shortcutLongLabel="@string/long_label2">
+        <intent android:action="action" />
+        <intent android:action="action2"
+            android:data="data"
+            android:mimeType="a/b"
+            android:targetPackage="pkg"
+            android:targetClass="pkg.class"
+            >
+            <categories android:name="icat1"/>
+            <categories android:name="icat2"/>
+            <extra android:name="key1" android:value="value1" />
+            <extra android:name="key2" android:value="123" />
+            <extra android:name="key3" android:value="true" />
+        </intent>
+    </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java
new file mode 100644
index 0000000..62acf9d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.content.pm.cts.shortcut.backup.publisher4;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..224c8ba
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,339 @@
+/*
+ * 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.content.pm.cts.shortcut.backup.publisher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+
+import java.security.SecureRandom;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testRestoredOnOldVersion() {
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllPinned()
+                .areAllEnabled();
+
+        // At this point, s1 and s2 don't look to exist to the publisher, so it can publish a
+        // dynamic shortcut and that should work.
+        // But updateShortcuts() don't.
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2_updated")
+                .build();
+        assertTrue(getManager().updateShortcuts(list(s2)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1") // s2 not in the list.
+                .areAllEnabled();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2", "s1") // s2 not in the list.
+                .areAllEnabled();
+
+    }
+
+    public void testRestoredOnNewVersion() {
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1")
+                .areAllEnabled()
+                .areAllPinned();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllPinned()
+                .areAllEnabled();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllEnabled();
+
+        // This time, update should work.
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2_updated")
+                .build();
+        assertTrue(getManager().updateShortcuts(list(s2)));
+
+        assertWith(getManager().getPinnedShortcuts())
+                .selectByIds("s2")
+                .forAllShortcuts(si -> {
+                    assertEquals("shortlabel2_updated", si.getShortLabel());
+                });
+    }
+
+    public void testBackupDisabled() {
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllPinned()
+                .areAllEnabled();
+
+        // Backup was disabled, so either s1 nor s2 look to be restored to the app.
+        // So when the app publishes s1, it'll be published as new.
+        // But it'll still be pinned.
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1") // s2 not in the list.
+                .areAllPinned()
+                .areAllEnabled()
+                .forAllShortcuts(si -> {
+                    // The app re-published the shortcut, not updated, so the fields that existed
+                    // in the original shortcut is gone now.
+                    assertNull(si.getExtras());
+                });
+    }
+
+    public void testRestoreWrongKey() {
+        // Restored pinned shortcuts are from a package with a different signature, so the dynamic
+        // pinned shortcuts should be disabled-invisible.
+
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2");
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllPinned()
+                .areAllEnabled();
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2_updated")
+                .build();
+        assertTrue(getManager().updateShortcuts(list(s2)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1") // s2 not in the list.
+                .areAllEnabled();
+    }
+
+    /**
+     * Restored on an older version that have no manifest shortcuts.
+     *
+     * In this case, the publisher wouldn't see the manifest shortcuts, and they're overwritable.
+     */
+    public void testRestoreNoManifestOnOldVersion() {
+        assertWith(getManager().getManifestShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .isEmpty();
+
+        // ms1 was manifest/immutable, but can be overwritten.
+        final ShortcutInfo ms1 = new ShortcutInfo.Builder(getContext(), "ms1")
+                .setShortLabel("ms1_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(ms1)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("ms1");
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1");
+
+        // Adding s1 should also work.
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("s1_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "ms1");
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("s1", "ms1");
+
+        // Update on ms2 should be no-op.
+        final ShortcutInfo ms2 = new ShortcutInfo.Builder(getContext(), "ms2")
+                .setShortLabel("ms2-updated")
+                .build();
+        assertTrue(getManager().updateShortcuts(list(ms2)));
+
+        assertWith(getManager().getManifestShortcuts())
+                .isEmpty();
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "ms1")
+                .areAllEnabled()
+                .areAllPinned()
+                .areAllMutable()
+
+                .selectByIds("s1")
+                .forAllShortcuts(si -> {
+                    assertEquals("s1_new_one", si.getShortLabel());
+                })
+
+                .revertToOriginalList()
+                .selectByIds("ms1")
+                .forAllShortcuts(si -> {
+                    assertEquals("ms1_new_one", si.getShortLabel());
+                });
+    }
+
+    /**
+     * Restored on the same (or newer) version that have no manifest shortcuts.
+     *
+     * In this case, the publisher will see the manifest shortcuts (as immutable pinned),
+     * and they *cannot* be overwritten.
+     */
+    public void testRestoreNoManifestOnNewVersion() {
+        assertWith(getManager().getManifestShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .selectByIds("ms1", "ms2")
+                .areAllDisabled()
+
+                .revertToOriginalList()
+                .selectByIds("s1", "s2")
+                .areAllEnabled();
+    }
+
+    private void assertNoShortcuts() {
+        assertWith(getManager().getDynamicShortcuts()).isEmpty();
+        assertWith(getManager().getPinnedShortcuts()).isEmpty();
+        assertWith(getManager().getManifestShortcuts()).isEmpty();
+    }
+
+    public void testInvisibleIgnored() throws Exception {
+        assertNoShortcuts();
+
+        // Make sure "disable" won't change the disabled reason. Also make sure "enable" won't
+        // enable them.
+        getManager().disableShortcuts(list("s1", "s2", "ms1"));
+        assertNoShortcuts();
+
+        getManager().enableShortcuts(list("ms1", "s2"));
+        assertNoShortcuts();
+
+        getManager().enableShortcuts(list("ms1"));
+        assertNoShortcuts();
+
+        getManager().removeDynamicShortcuts(list("s1", "ms1"));
+        assertNoShortcuts();
+
+        getManager().removeAllDynamicShortcuts();
+        assertNoShortcuts();
+
+
+        // Force launcher 4 to be the default launcher so it'll receive the pin request.
+        setDefaultLauncher(getInstrumentation(),
+                "android.content.pm.cts.shortcut.backup.launcher4/.MainActivity");
+
+        // Update, set and add have been tested already, so let's test "pin".
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putBoolean("acceptit", true);
+
+        final ShortcutInfo ms2 = new ShortcutInfo.Builder(getContext(), "ms2")
+                .setShortLabel("ms2_new_one")
+                .setActivity(getActivity("MainActivity"))
+                .setIntents(new Intent[]{new Intent("main2")})
+                .setExtras(pb)
+                .build();
+
+        final String myIntentAction = "cts-shortcut-intent_" + new SecureRandom().nextInt();
+        final IntentFilter myFilter = new IntentFilter(myIntentAction);
+
+        final BroadcastReceiver onResult = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                latch.countDown();
+            }
+        };
+        getContext().registerReceiver(onResult, myFilter);
+        assertTrue(getManager().requestPinShortcut(ms2,
+                PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
+                        PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender()));
+
+        assertTrue("Didn't receive requestPinShortcut() callback.",
+                latch.await(30, TimeUnit.SECONDS));
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("ms2")
+                .areAllNotDynamic()
+                .areAllNotManifest()
+                .areAllMutable()
+                .areAllPinned()
+                .forAllShortcuts(si -> {
+                    // requestPinShortcut() acts as an update in this case, so even though
+                    // the original shortcut hada  long label, this one does not.
+                    assertTrue(TextUtils.isEmpty(si.getLongLabel()));
+                });
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..15b3853
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.content.pm.cts.shortcut.backup.publisher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testPreBackup() {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getContext().getResources(), R.drawable.black_16x64));
+        final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1")
+                .setLongLabel("longlabel1")
+                .setIcon(icon1)
+                .setActivity(getActivity("MainActivity"))
+                .setDisabledMessage("disabledmessage1")
+                .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                .setExtras(makePersistableBundle("ek1", "ev1"))
+                .setCategories(set("cat1"))
+                .build();
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2")
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+                .setShortLabel("shortlabel3")
+                .setIcon(icon3)
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "s2", "s3")
+                .areAllNotPinned();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllNotPinned();
+   }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk
new file mode 100644
index 0000000..35e2152
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk
@@ -0,0 +1,46 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4old_nomanifest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+    $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml
new file mode 100644
index 0000000..af47b93
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.content.pm.cts.shortcut.backup.publisher4"
+    android:versionCode="10">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="MainActivity" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
index 619bdfe..dc1d018 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
@@ -39,6 +39,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
index 3fbed5f..886aded 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="android.content.pm.cts.shortcut.multiuser">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
index ae0bf75..d7e13b6 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
@@ -43,6 +43,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
@@ -74,6 +76,8 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
index 1b88d5e..72d7dfc 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="android.content.pm.cts.shortcut.upgrade">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher" android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
index 8b53b2e..b060940 100644
--- a/hostsidetests/shortcuts/hostside/AndroidTest.xml
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="android.cts.backup.BackupPreparer">
         <option name="enable-backup-if-needed" value="true" />
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
index c703e7f..bc33abf 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
@@ -17,14 +17,14 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.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;
 
@@ -41,6 +41,8 @@
 abstract public class BaseShortcutManagerHostTest extends DeviceTestCase implements IBuildReceiver {
     protected static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
 
+    protected static final boolean NO_UNINSTALL_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
+
     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
 
     private IBuildInfo mCtsBuild;
@@ -163,7 +165,7 @@
     }
 
     private void printTestResult(TestRunResult runResult) {
-        for (Map.Entry<TestIdentifier, TestResult> testEntry :
+        for (Map.Entry<TestDescription, TestResult> testEntry :
                 runResult.getTestResults().entrySet()) {
             TestResult testResult = testEntry.getValue();
 
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 960b41f..3caacad 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
@@ -26,9 +26,22 @@
     private static final String LAUNCHER1_APK = "CtsShortcutBackupLauncher1.apk";
     private static final String LAUNCHER2_APK = "CtsShortcutBackupLauncher2.apk";
     private static final String LAUNCHER3_APK = "CtsShortcutBackupLauncher3.apk";
+    private static final String LAUNCHER4_OLD_APK = "CtsShortcutBackupLauncher4old.apk";
+    private static final String LAUNCHER4_NEW_APK = "CtsShortcutBackupLauncher4new.apk";
+
     private static final String PUBLISHER1_APK = "CtsShortcutBackupPublisher1.apk";
     private static final String PUBLISHER2_APK = "CtsShortcutBackupPublisher2.apk";
     private static final String PUBLISHER3_APK = "CtsShortcutBackupPublisher3.apk";
+    private static final String PUBLISHER4_OLD_APK = "CtsShortcutBackupPublisher4old.apk";
+    private static final String PUBLISHER4_NEW_APK = "CtsShortcutBackupPublisher4new.apk";
+    private static final String PUBLISHER4_NEW_NOBACKUP_APK
+            = "CtsShortcutBackupPublisher4new_nobackup.apk";
+    private static final String PUBLISHER4_NEW_WRONGKEY_APK
+            = "CtsShortcutBackupPublisher4new_wrongkey.apk";
+    private static final String PUBLISHER4_OLD_NO_MANIFST_APK
+            = "CtsShortcutBackupPublisher4old_nomanifest.apk";
+    private static final String PUBLISHER4_NEW_NO_MANIFST_APK
+            = "CtsShortcutBackupPublisher4new_nomanifest.apk";
 
     private static final String LAUNCHER1_PKG =
             "android.content.pm.cts.shortcut.backup.launcher1";
@@ -36,12 +49,17 @@
             "android.content.pm.cts.shortcut.backup.launcher2";
     private static final String LAUNCHER3_PKG =
             "android.content.pm.cts.shortcut.backup.launcher3";
+    private static final String LAUNCHER4_PKG =
+            "android.content.pm.cts.shortcut.backup.launcher4";
+
     private static final String PUBLISHER1_PKG =
             "android.content.pm.cts.shortcut.backup.publisher1";
     private static final String PUBLISHER2_PKG =
             "android.content.pm.cts.shortcut.backup.publisher2";
     private static final String PUBLISHER3_PKG =
             "android.content.pm.cts.shortcut.backup.publisher3";
+    private static final String PUBLISHER4_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher4";
 
     private static final int BROADCAST_TIMEOUT_SECONDS = 120;
 
@@ -59,16 +77,22 @@
             clearShortcuts(LAUNCHER1_PKG, getPrimaryUserId());
             clearShortcuts(LAUNCHER2_PKG, getPrimaryUserId());
             clearShortcuts(LAUNCHER3_PKG, getPrimaryUserId());
+            clearShortcuts(LAUNCHER4_PKG, getPrimaryUserId());
+
             clearShortcuts(PUBLISHER1_PKG, getPrimaryUserId());
             clearShortcuts(PUBLISHER2_PKG, getPrimaryUserId());
             clearShortcuts(PUBLISHER3_PKG, getPrimaryUserId());
+            clearShortcuts(PUBLISHER4_PKG, getPrimaryUserId());
 
             uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER1_PKG);
             uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER2_PKG);
             uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER3_PKG);
+            uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+
             uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER1_PKG);
             uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER2_PKG);
             uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER3_PKG);
+            uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
 
             waitUntilPackagesGone();
         }
@@ -80,14 +104,16 @@
             dumpsys("tearDown");
         }
 
-        if (mSupportsBackup) {
+        if (mSupportsBackup && !NO_UNINSTALL_IN_TEARDOWN) {
             getDevice().uninstallPackage(LAUNCHER1_PKG);
             getDevice().uninstallPackage(LAUNCHER2_PKG);
             getDevice().uninstallPackage(LAUNCHER3_PKG);
+            getDevice().uninstallPackage(LAUNCHER4_PKG);
 
             getDevice().uninstallPackage(PUBLISHER1_PKG);
             getDevice().uninstallPackage(PUBLISHER2_PKG);
             getDevice().uninstallPackage(PUBLISHER3_PKG);
+            getDevice().uninstallPackage(PUBLISHER4_PKG);
         }
 
         super.tearDown();
@@ -169,8 +195,8 @@
         CLog.i("Waiting until all packages are removed from shortcut manager...");
 
         final String packages[] = {
-                LAUNCHER1_PKG,  LAUNCHER2_PKG, LAUNCHER3_PKG,
-                PUBLISHER1_PKG, PUBLISHER2_PKG, PUBLISHER3_PKG,
+                LAUNCHER1_PKG,  LAUNCHER2_PKG, LAUNCHER3_PKG, LAUNCHER4_PKG,
+                PUBLISHER1_PKG, PUBLISHER2_PKG, PUBLISHER3_PKG, PUBLISHER4_PKG,
         };
 
         String dumpsys = "";
@@ -184,9 +210,11 @@
             if (dumpsys.contains("Launcher: " + LAUNCHER1_PKG)) continue;
             if (dumpsys.contains("Launcher: " + LAUNCHER2_PKG)) continue;
             if (dumpsys.contains("Launcher: " + LAUNCHER3_PKG)) continue;
+            if (dumpsys.contains("Launcher: " + LAUNCHER4_PKG)) continue;
             if (dumpsys.contains("Package: " + PUBLISHER1_PKG)) continue;
             if (dumpsys.contains("Package: " + PUBLISHER2_PKG)) continue;
             if (dumpsys.contains("Package: " + PUBLISHER3_PKG)) continue;
+            if (dumpsys.contains("Package: " + PUBLISHER4_PKG)) continue;
 
             dumpsys("Shortcut manager handled broadcasts");
 
@@ -298,6 +326,353 @@
                 getPrimaryUserId());
     }
 
+    public void testBackupAndRestore_downgrade() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from the new version and pin them.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Restore the old version of the app, and the launcher.
+        // (But we don't check the launcher's version, so using old is fine.)
+        installAppAsUser(LAUNCHER4_OLD_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_OLD_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoredOnOldVersion",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoredOnOldVersion",
+                getPrimaryUserId());
+
+        // New install the original version. All blocked shortcuts should re-appear.
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoredOnNewVersion",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoredOnNewVersion",
+                getPrimaryUserId());
+
+    }
+
+    public void testBackupAndRestore_backupWasDisabled() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from "nobackup" version.
+
+        installAppAsUser(PUBLISHER4_NEW_NOBACKUP_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the "backup-ok" version. But restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testBackupDisabled",
+                getPrimaryUserId());
+    }
+
+    public void testBackupAndRestore_backupIsDisabled() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from backup-ok version.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the nobackup version. Restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_NEW_NOBACKUP_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testBackupDisabled",
+                getPrimaryUserId());
+    }
+
+    public void testBackupAndRestore_wrongKey() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from backup-ok version.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the nobackup version. Restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_NEW_WRONGKEY_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreWrongKey",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreWrongKey",
+                getPrimaryUserId());
+    }
+
+    public void testBackupAndRestore_noManifestOnOldVersion() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from backup-ok version.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the nobackup version. Restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_OLD_NO_MANIFST_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreNoManifestOnOldVersion",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreNoManifestOnOldVersion",
+                getPrimaryUserId());
+    }
+
+    public void testBackupAndRestore_noManifestOnNewVersion() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from backup-ok version.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the nobackup version. Restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_NEW_NO_MANIFST_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreNoManifestOnNewVersion",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testRestoreNoManifestOnNewVersion",
+                getPrimaryUserId());
+    }
+
+    /**
+     * Make sure invisible shortcuts are ignored by all API calls.
+     *
+     * (Restore from new to old-nomanifest)
+     */
+    public void testBackupAndRestore_invisibleIgnored() throws Exception {
+        if (!mSupportsBackup) {
+            return;
+        }
+        dumpsys("Test start");
+
+        // First, publish shortcuts from backup-ok version.
+
+        installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        dumpsys("Before backup");
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+        uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+        // Make sure the shortcut service handled all the uninstall broadcasts.
+        waitUntilPackagesGone();
+
+        // Do it one more time just in case...
+        waitUntilBroadcastsDrain();
+
+        // Then restore
+        doRestore();
+
+        dumpsys("After restore");
+
+        // Install the nobackup version. Restoration is limited.
+        installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER4_OLD_NO_MANIFST_APK, getPrimaryUserId());
+        waitUntilBroadcastsDrain();
+
+        runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testInvisibleIgnored",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+                "testInvisibleIgnored",
+                getPrimaryUserId());
+    }
+
     public void testBackupAndRestore_withNoUninstall() throws Exception {
         if (!mSupportsBackup) {
             return;
diff --git a/hostsidetests/statsd/Android.mk b/hostsidetests/statsd/Android.mk
new file mode 100644
index 0000000..b614cb1
--- /dev/null
+++ b/hostsidetests/statsd/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# tag this module as a cts test artifact
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := CtsStatsdHostTestCases
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_STATIC_JAVA_LIBRARIES := platformprotos
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util host-libprotobuf-java-full
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
new file mode 100644
index 0000000..4a0f150
--- /dev/null
+++ b/hostsidetests/statsd/AndroidTest.xml
@@ -0,0 +1,22 @@
+<?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 Statsd host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="statsd" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsStatsdHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/statsd/apps/Android.mk b/hostsidetests/statsd/apps/Android.mk
new file mode 100644
index 0000000..4a74e80
--- /dev/null
+++ b/hostsidetests/statsd/apps/Android.mk
@@ -0,0 +1,20 @@
+# 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)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.mk b/hostsidetests/statsd/apps/statsdapp/Android.mk
new file mode 100644
index 0000000..ec4b86a
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/Android.mk
@@ -0,0 +1,45 @@
+# 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_PACKAGE_NAME := CtsStatsdApp
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit org.apache.http.legacy
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    android-support-v4 \
+    legacy-android-test \
+    android-support-test \
+    statsdprotolite
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
new file mode 100644
index 0000000..04c8c08
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -0,0 +1,96 @@
+<?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.server.cts.device.statsd" >
+    <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.DUMP" /> <!-- must be granted via pm grant -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+
+        <service android:name=".StatsdCtsBackgroundService" android:exported="true" />
+        <activity android:name=".StatsdCtsForegroundActivity" android:exported="true" />
+        <service android:name=".StatsdCtsForegroundService" android:exported="true" />
+
+        <activity
+            android:name=".VideoPlayerActivity"
+            android:label="@string/app_name"
+            android:resizeableActivity="true"
+            android:supportsPictureInPicture="true"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+            android:launchMode="singleTop" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".DaveyActivity" />
+
+        <service android:name=".StatsdAuthenticator"
+            android:exported="false">
+            <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="StatsdSyncService"
+            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=".StatsdProvider"
+            android:authorities="com.android.server.cts.device.statsd.provider" />
+
+        <service android:name=".StatsdJobService"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
+
+        <receiver android:name=".SubscriberTests$BroadcastSubscriberReceiver1"
+                  android:exported="false" >
+        </receiver>
+        <receiver android:name=".SubscriberTests$BroadcastSubscriberReceiver2"
+                  android:exported="false" >
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.cts.device.statsd"
+                     android:label="CTS tests of android.os.statsd stats collection">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>/>
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
new file mode 100644
index 0000000..bf43931
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:custom="http://schemas.android.com/apk/res/com.android.server.cts.device.statsd"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+  <com.android.server.cts.device.statsd.DaveyView
+      android:id="@+id/davey_view"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      custom:causeDavey="false" />
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..a029c80
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:id="@+id/video_frame"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+        <VideoView
+            android:id="@+id/video_player_view"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent" />
+    </FrameLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4 b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3 b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
new file mode 100644
index 0000000..d20f772
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml b/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml
new file mode 100644
index 0000000..e769146
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/values/attrs.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.
+-->
+<resources>
+  <declare-styleable name="DaveyView">
+    <attr name="causeDavey" format="boolean" />
+  </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
new file mode 100644
index 0000000..e40d2ac
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">CTS Statsd Atoms App</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
new file mode 100644
index 0000000..71c73d7
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="com.android.cts.statsd"
+    android:label="@string/app_name" />
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml
new file mode 100644
index 0000000..d8cb52e
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/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= "com.android.server.cts.device.statsd.provider"
+    android:accountType="com.android.cts.statsd"
+/>
\ No newline at end of file
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
new file mode 100644
index 0000000..e4dee54
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -0,0 +1,446 @@
+/*
+ * 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.server.cts.device.statsd;
+
+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;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+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.CameraManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.media.MediaPlayer;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class AtomTests {
+    private static final String TAG = AtomTests.class.getSimpleName();
+
+    @Test
+    public void testAppStart() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent(context, StatsdCtsForegroundActivity.class);
+        intent.putExtra(StatsdCtsForegroundActivity.KEY_ACTION,
+                StatsdCtsForegroundActivity.ACTION_SLEEP_WHILE_TOP);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    @Test
+    public void testAudioState() {
+        // TODO: This should surely be getTargetContext(), here and everywhere, but test first.
+        Context context = InstrumentationRegistry.getContext();
+        MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.good);
+        mediaPlayer.start();
+        sleep(2_000);
+        mediaPlayer.stop();
+    }
+
+    @Test
+    public void testBleScanOptimized() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build();
+        performBleScan(scanSettings, false);
+    }
+
+    @Test
+    public void testBleScanUnoptimized() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        performBleScan(scanSettings, false);
+    }
+
+    @Test
+    public void testBleScanResult() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        // TODO: Add an extremely weak ScanFilter to allow background ble scan results.
+        performBleScan(scanSettings, true);
+    }
+
+    private static void performBleScan(ScanSettings scanSettings, boolean waitForResult) {
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            Log.e(TAG, "Device does not support Bluetooth");
+            return;
+        }
+        boolean bluetoothEnabledByTest = false;
+        if (!bluetoothAdapter.isEnabled()) {
+            if (!bluetoothAdapter.enable()) {
+                Log.e(TAG, "Bluetooth is not enabled");
+                return;
+            }
+            sleep(8_000);
+            bluetoothEnabledByTest = true;
+        }
+        BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+        if (bleScanner == null) {
+            Log.e(TAG, "Cannot access BLE scanner");
+            return;
+        }
+
+        CountDownLatch resultsLatch = new CountDownLatch(1);
+        ScanCallback scanCallback = new ScanCallback() {
+            @Override
+            public void onScanResult(int callbackType, ScanResult result) {
+                Log.v(TAG, "called onScanResult");
+                resultsLatch.countDown();
+            }
+            @Override
+            public void onScanFailed(int errorCode) {
+                Log.v(TAG, "called onScanFailed");
+            }
+            @Override
+            public void onBatchScanResults(List<ScanResult> results) {
+                Log.v(TAG, "called onBatchScanResults");
+                resultsLatch.countDown();
+            }
+        };
+
+        bleScanner.startScan(null, scanSettings, scanCallback);
+        if (waitForResult) {
+            waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
+        } else {
+            sleep(2_000);
+        }
+        bleScanner.stopScan(scanCallback);
+
+        // Restore adapter state at end of test
+        if (bluetoothEnabledByTest) {
+            bluetoothAdapter.disable();
+        }
+    }
+
+    @Test
+    public void testCameraState() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        CameraManager cam = context.getSystemService(CameraManager.class);
+        String[] cameraIds = cam.getCameraIdList();
+        if (cameraIds.length == 0) {
+            Log.e(TAG, "No camera found on device");
+            return;
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final CameraDevice.StateCallback cb = new CameraDevice.StateCallback() {
+            @Override
+            public void onOpened(CameraDevice cd) {
+                Log.i(TAG, "CameraDevice " + cd.getId() + " opened");
+                sleep(2_000);
+                cd.close();
+            }
+            @Override
+            public void onClosed(CameraDevice cd) {
+                latch.countDown();
+                Log.i(TAG, "CameraDevice " + cd.getId() + " closed");
+            }
+            @Override
+            public void onDisconnected(CameraDevice cd) {
+                Log.w(TAG, "CameraDevice  " + cd.getId() + " disconnected");
+            }
+            @Override
+            public void onError(CameraDevice cd, int error) {
+                Log.e(TAG, "CameraDevice " + cd.getId() + "had error " + error);
+            }
+        };
+
+        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+        handlerThread.start();
+        Looper looper = handlerThread.getLooper();
+        Handler handler = new Handler(looper);
+
+        cam.openCamera(cameraIds[0], cb, handler);
+        waitForReceiver(context, 10_000, latch, null);
+    }
+
+    @Test
+    public void testFlashlight() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        CameraManager cam = context.getSystemService(CameraManager.class);
+        String[] cameraIds = cam.getCameraIdList();
+        boolean foundFlash = false;
+        for (int i = 0; i < cameraIds.length; i++) {
+            String id = cameraIds[i];
+            if(cam.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) {
+                cam.setTorchMode(id, true);
+                sleep(500);
+                cam.setTorchMode(id, false);
+                foundFlash = true;
+                break;
+            }
+        }
+        if(!foundFlash) {
+            Log.e(TAG, "No flashlight found on device");
+        }
+    }
+
+    @Test
+    public void testForegroundService() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        // The service goes into foreground and exits shortly
+        Intent intent = new Intent(context, StatsdCtsForegroundService.class);
+        context.startService(intent);
+        sleep(500);
+        context.stopService(intent);
+    }
+
+    @Test
+    public void testGpsScan() {
+        Context context = InstrumentationRegistry.getContext();
+        final LocationManager locManager = context.getSystemService(LocationManager.class);
+        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+            Log.e(TAG, "GPS provider is not enabled");
+            return;
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+
+        final LocationListener locListener = new LocationListener() {
+            public void onLocationChanged(Location location) {
+                Log.v(TAG, "onLocationChanged: location has been obtained");
+            }
+            public void onProviderDisabled(String provider) {
+                Log.w(TAG, "onProviderDisabled " + provider);
+            }
+            public void onProviderEnabled(String provider) {
+                Log.w(TAG, "onProviderEnabled " + provider);
+            }
+            public void onStatusChanged(String provider, int status, Bundle extras) {
+                Log.w(TAG, "onStatusChanged " + provider + " " + status);
+            }
+        };
+
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                Looper.prepare();
+                locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 990, 0,
+                        locListener);
+                sleep(1_000);
+                locManager.removeUpdates(locListener);
+                latch.countDown();
+                return null;
+            }
+        }.execute();
+
+        waitForReceiver(context, 59_000, latch, null);
+    }
+
+    @Test
+    public void testSyncState() throws Exception {
+
+        Context context = InstrumentationRegistry.getContext();
+        StatsdAuthenticator.removeAllAccounts(context);
+        AccountManager am = context.getSystemService(AccountManager.class);
+        CountDownLatch latch = StatsdSyncAdapter.resetCountDownLatch();
+
+        Account account = StatsdAuthenticator.getTestAccount();
+        StatsdAuthenticator.ensureTestAccount(context);
+        sleep(500);
+
+        // Just force set is syncable.
+        ContentResolver.setMasterSyncAutomatically(true);
+        sleep(500);
+        ContentResolver.setIsSyncable(account, StatsdProvider.AUTHORITY, 1);
+        // Wait for the first (automatic) sync to finish
+        waitForReceiver(context, 120_000, latch, null);
+
+        // Request and wait for the second sync to finish
+        latch = StatsdSyncAdapter.resetCountDownLatch();
+        StatsdSyncAdapter.requestSync(account);
+        waitForReceiver(context, 120_000, latch, null);
+        StatsdAuthenticator.removeAllAccounts(context);
+    }
+
+    @Test
+    public void testScheduledJob() throws Exception {
+        final ComponentName name = new ComponentName("com.android.server.cts.device.statsd",
+                StatsdJobService.class.getName());
+
+        Context context = InstrumentationRegistry.getContext();
+        JobScheduler js = context.getSystemService(JobScheduler.class);
+        assertTrue("JobScheduler service not available", js != null);
+
+        JobInfo.Builder builder = new JobInfo.Builder(1, name);
+        builder.setOverrideDeadline(0);
+        JobInfo job = builder.build();
+
+        long startTime = System.currentTimeMillis();
+        CountDownLatch latch = StatsdJobService.resetCountDownLatch();
+        js.schedule(job);
+        waitForReceiver(context, 2_500, latch, null);
+    }
+
+    @Test
+    public void testWakelockState() {
+        Context context = InstrumentationRegistry.getContext();
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "StatsdPartialWakelock");
+        wl.acquire();
+        sleep(500);
+        wl.release();
+    }
+
+    @Test
+    public void testWakeupAlarm() {
+        Context context = InstrumentationRegistry.getContext();
+        String name = "android.cts.statsd.testWakeupAlarm";
+        CountDownLatch onReceiveLatch = new CountDownLatch(1);
+        BroadcastReceiver receiver =
+                registerReceiver(context, onReceiveLatch, new IntentFilter(name));
+        AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
+        PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
+        manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+            SystemClock.elapsedRealtime() + 2_000, pintent);
+        waitForReceiver(context, 10_000, onReceiveLatch, receiver);
+    }
+
+    @Test
+    public void testWifiLock() {
+        Context context = InstrumentationRegistry.getContext();
+        WifiManager wm = context.getSystemService(WifiManager.class);
+        WifiManager.WifiLock lock = wm.createWifiLock("StatsdCTSWifiLock");
+        lock.acquire();
+        sleep(500);
+        lock.release();
+    }
+
+    @Test
+    public void testWifiMulticastLock() {
+        Context context = InstrumentationRegistry.getContext();
+        WifiManager wm = context.getSystemService(WifiManager.class);
+        WifiManager.MulticastLock lock = wm.createMulticastLock("StatsdCTSMulticastLock");
+        lock.acquire();
+        sleep(500);
+        lock.release();
+    }
+
+    @Test
+    /** Does two wifi scans. */
+    // TODO: Copied this from BatterystatsValidation but we probably don't need to wait for results.
+    public void testWifiScan() {
+        Context context = InstrumentationRegistry.getContext();
+        IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        // Sometimes a scan was already running (from a different uid), so the first scan doesn't
+        // start when requested. Therefore, additionally wait for whatever scan is currently running
+        // to finish, then request a scan again - at least one of these two scans should be
+        // attributed to this app.
+        for (int i = 0; i < 2; i++) {
+            CountDownLatch onReceiveLatch = new CountDownLatch(1);
+            BroadcastReceiver receiver = registerReceiver(context, onReceiveLatch, intentFilter);
+            context.getSystemService(WifiManager.class).startScan();
+            waitForReceiver(context, 60_000, onReceiveLatch, receiver);
+        }
+    }
+
+    @Test
+    public void testSimpleCpu() {
+        long timestamp = System.currentTimeMillis();
+        for (int i = 0; i < 10000; i ++) {
+            timestamp += i;
+        }
+        Log.i(TAG, "The answer is " + timestamp);
+    }
+
+    // ------- Helper methods
+
+    /** Puts the current thread to sleep. */
+    static void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception while sleeping", e);
+        }
+    }
+
+    /** Register receiver to determine when given action is complete. */
+    private static BroadcastReceiver registerReceiver(
+            Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) {
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.d(TAG, "Received broadcast.");
+                onReceiveLatch.countDown();
+            }
+        };
+        // Run Broadcast receiver in a different thread since the main thread will wait.
+        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+        handlerThread.start();
+        Looper looper = handlerThread.getLooper();
+        Handler handler = new Handler(looper);
+        ctx.registerReceiver(receiver, intentFilter, null, handler);
+        return receiver;
+    }
+
+    /**
+     * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no
+     * receiver is needed to be unregistered.
+     */
+    private static void waitForReceiver(Context ctx,
+            int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) {
+        try {
+            boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS);
+            if (didFinish) {
+                Log.v(TAG, "Finished performing action");
+            } else {
+                // This is not necessarily a problem. If we just want to make sure a count was
+                // recorded for the request, it doesn't matter if the action actually finished.
+                Log.w(TAG, "Did not finish in specified time.");
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception while awaiting action to finish", e);
+        }
+        if (ctx != null && receiver != null) {
+            ctx.unregisterReceiver(receiver);
+        }
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
new file mode 100644
index 0000000..e2ec7f7
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.widget.VideoView;
+import android.util.Log;
+
+
+public class DaveyActivity extends Activity {
+    private static final String TAG = "statsdDaveyActivity";
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_davey);
+        DaveyView view = (DaveyView)findViewById(R.id.davey_view);
+        view.causeDavey(true);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
new file mode 100644
index 0000000..bf1cd35
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.view.View;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+
+
+public class DaveyView extends View {
+
+    private static final String TAG = "statsdDaveyView";
+
+    private static final long DAVEY_TIME_MS = 750; // A bit more than 700ms to be safe.
+    private boolean mCauseDavey;
+    private Paint mPaint;
+    private int mTexty;
+
+    public DaveyView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.getTheme().obtainStyledAttributes(
+                attrs,
+                R.styleable.DaveyView,
+                0, 0);
+
+        try {
+            mCauseDavey = a.getBoolean(R.styleable.DaveyView_causeDavey, false);
+        } finally {
+            a.recycle();
+        }
+
+        mPaint = new Paint();
+        mPaint.setColor(Color.BLACK);
+        mPaint.setTextSize(20);
+        FontMetrics metric = mPaint.getFontMetrics();
+        int textHeight = (int) Math.ceil(metric.descent - metric.ascent);
+        mTexty = textHeight - (int) metric.descent;
+    }
+
+    public void causeDavey(boolean cause) {
+        mCauseDavey = cause;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mCauseDavey) {
+            canvas.drawText("Davey!", 0, mTexty, mPaint);
+            SystemClock.sleep(DAVEY_TIME_MS);
+            mCauseDavey = false;
+        } else {
+            canvas.drawText("No Davey", 0, mTexty, mPaint);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/MetricsTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/MetricsTests.java
new file mode 100644
index 0000000..869d47b0a
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/MetricsTests.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.server.cts.device.statsd;
+
+import android.util.StatsLog;
+
+import com.android.os.AtomsProto;
+
+import org.junit.Test;
+
+public class MetricsTests {
+    @Test
+    public void testSimpleEventCountMetric() {
+        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
+                StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF);
+        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
+                StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON);
+    }
+
+    @Test
+    public void testEventCountWithCondition() {
+        StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1);
+        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
+                StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF);
+        StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 2);
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
new file mode 100644
index 0000000..85acd07
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsd;
+
+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;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Authenticator for the sync test.
+ */
+public class StatsdAuthenticator extends Service {
+    private static final String TAG = "AtomTestsAuthenticator";
+
+    private static final String ACCOUNT_NAME = "StatsdCts";
+    private static final String ACCOUNT_TYPE = "com.android.cts.statsd";
+    private static Authenticator sInstance;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (sInstance == null) {
+            sInstance = new Authenticator(getApplicationContext());
+
+        }
+        return sInstance.getIBinder();
+    }
+
+    public static Account getTestAccount() {
+        return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    }
+
+    /**
+     * Adds the test account, if it doesn't exist yet.
+     */
+    public static void ensureTestAccount(Context context) {
+        final Account account = getTestAccount();
+
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+
+        final AccountManager am = context.getSystemService(AccountManager.class);
+
+        if (!Arrays.asList(am.getAccountsByType(account.type)).contains(account) ){
+            am.addAccountExplicitly(account, "password", new Bundle());
+        }
+    }
+
+    /**
+     * Remove the test account.
+     */
+    public static void removeAllAccounts(Context context) {
+        final AccountManager am = context.getSystemService(AccountManager.class);
+
+        for (Account account : am.getAccountsByType(ACCOUNT_TYPE)) {
+            Log.i(TAG, "Removing " + account + "...");
+            am.removeAccountExplicitly(account);
+            Log.i(TAG, "Removed");
+        }
+    }
+
+    public static class Authenticator extends AbstractAccountAuthenticator {
+
+        private final Context mContxet;
+
+        public Authenticator(Context context) {
+            super(context);
+            mContxet = context;
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] requiredFeatures, Bundle options)
+                throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+                Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public String getAuthTokenLabel(String authTokenType) {
+            return "token_label";
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+                String[] features) throws NetworkErrorException {
+            return new Bundle();
+        }
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
new file mode 100644
index 0000000..fc1c472
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
@@ -0,0 +1,54 @@
+/*
+ * 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.server.cts.device.statsd;
+
+import com.android.server.cts.device.statsd.AtomTests;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.util.Log;
+
+/** An service (to be run as a background process) which performs one of a number of actions. */
+public class StatsdCtsBackgroundService extends IntentService {
+    private static final String TAG = StatsdCtsBackgroundService.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+
+    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+
+    public StatsdCtsBackgroundService() {
+        super(StatsdCtsBackgroundService.class.getName());
+    }
+
+    @Override
+    public void onHandleIntent(Intent intent) {
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from background service.");
+
+        switch (action) {
+            case ACTION_BACKGROUND_SLEEP:
+                AtomTests.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP);
+                break;
+            case ACTION_END_IMMEDIATELY:
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action");
+        }
+    }
+}
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
new file mode 100644
index 0000000..14f3dba
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import com.android.server.cts.device.statsd.AtomTests;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+/** An activity (to be run as a foreground process) which performs one of a number of actions. */
+public class StatsdCtsForegroundActivity extends Activity {
+    private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
+
+    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_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+
+    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+    public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_END_IMMEDIATELY:
+                finish();
+                break;
+            case ACTION_SLEEP_WHILE_TOP:
+                doSleepWhileTop();
+                break;
+            case ACTION_SHOW_APPLICATION_OVERLAY:
+                doShowApplicationOverlay();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+    }
+
+    /** Does nothing, but asynchronously. */
+    private void doSleepWhileTop() {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                AtomTests.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void nothing) {
+                finish();
+            }
+        }.execute();
+    }
+
+    private void doShowApplicationOverlay() {
+        // Adapted from BatteryStatsBgVsFgActions.java.
+        final WindowManager wm = getSystemService(WindowManager.class);
+        Point size = new Point();
+        wm.getDefaultDisplay().getSize(size);
+
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+        wmlp.width = size.x / 4;
+        wmlp.height = size.y / 4;
+        wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+        wmlp.setTitle(getPackageName());
+
+        ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+
+        View v = new View(this);
+        v.setBackgroundColor(Color.GREEN);
+        v.setLayoutParams(vglp);
+        wm.addView(v, wmlp);
+
+        // The overlay continues long after the finish. The following is just to end the activity.
+        AtomTests.sleep(SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY);
+        finish();
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
new file mode 100644
index 0000000..ab46f5f
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.cts.device.statsd;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+public class StatsdCtsForegroundService extends Service {
+    private static final String TAG = "SimpleForegroundService";
+    private static final String NOTIFICATION_CHANNEL_ID = "Foreground Service";
+
+    // TODO: pass this in from host side.
+    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+    private boolean mChannelCreated;
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.i(TAG, "Handling message.");
+            // Sleep.
+            try {
+                Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE);
+            } catch (InterruptedException e) {
+                // Restore interrupt status.
+                Thread.currentThread().interrupt();
+            }
+            Log.i(TAG, "Stopping service.");
+            // Stop the service using the startId, so that we don't stop
+            // the service in the middle of handling another job
+            stopSelf(msg.arg1);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        // Start up the thread running the service.  Note that we create a
+        // separate thread because the service normally runs in the process's
+        // main thread, which we don't want to block.  We also make it
+        // background priority so CPU-intensive work will not disrupt our UI.
+        HandlerThread thread = new HandlerThread("ServiceStartArguments",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        // Get the HandlerThread's Looper and use it for our Handler
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+
+        if (ApiLevelUtil.isBefore(Build.VERSION_CODES.O_MR1)) {
+            return;
+        }
+        // OMR1 requires notification channel to be set
+        NotificationManager notificationManager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_HIGH);
+        notificationManager.createNotificationChannel(channel);
+        mChannelCreated = true;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("CTS Foreground")
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .build();
+        Log.i(TAG, "Starting Foreground.");
+        startForeground(1, notification);
+
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        mServiceHandler.sendMessage(msg);
+
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onDestroy () {
+        if (mChannelCreated) {
+            NotificationManager notificationManager =
+                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        }
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
new file mode 100644
index 0000000..329bdfa
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handles callback from the framework {@link android.app.job.JobScheduler}.
+ * Runs a job for 0.5 seconds. Provides a countdown latch to wait on, by the test that schedules it.
+ */
+@TargetApi(21)
+public class StatsdJobService extends JobService {
+  private static final String TAG = "AtomTestsJobService";
+
+  JobInfo mRunningJobInfo;
+  JobParameters mRunningParams;
+  private static CountDownLatch sLatch;
+
+  final Handler mHandler = new Handler();
+  final Runnable mWorker = new Runnable() {
+    @Override public void run() {
+      try {
+        Thread.sleep(500);
+      } catch (InterruptedException e) {
+      }
+      jobFinished(mRunningParams, false);
+      if (sLatch != null) {
+        sLatch.countDown();
+      }
+    }
+  };
+
+  public static synchronized CountDownLatch resetCountDownLatch() {
+    sLatch = new CountDownLatch(1);
+    return sLatch;
+  }
+
+  @Override
+  public void onCreate() {
+    super.onCreate();
+  }
+
+  @Override
+  public boolean onStartJob(JobParameters params) {
+    mRunningParams = params;
+    mHandler.post(mWorker);
+    return true;
+  }
+
+  @Override
+  public boolean onStopJob(JobParameters params) {
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
new file mode 100644
index 0000000..bd652b2
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsd;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Provider for the sync test.
+ */
+public class StatsdProvider extends ContentProvider {
+    public static final String AUTHORITY = "com.android.server.cts.device.statsd.provider";
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @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;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
new file mode 100644
index 0000000..12f110b
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsd;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Sync adapter for the sync test.
+ */
+public class StatsdSyncAdapter extends AbstractThreadedSyncAdapter {
+    private static final String TAG = "AtomTestsSyncAdapter";
+
+    private static final int TIMEOUT_SECONDS = 60 * 2;
+
+    private static CountDownLatch sLatch;
+
+    private static final Object sLock = new Object();
+
+
+    public StatsdSyncAdapter(Context context) {
+        // No need for auto-initialization because we set isSyncable in the test anyway.
+        super(context, /* autoInitialize= */ false);
+    }
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+        }
+        synchronized (sLock) {
+            Log.i(TAG, "onPerformSync");
+            sLock.notifyAll();
+            if (sLatch != null) {
+                sLatch.countDown();
+            } else {
+                Log.w(TAG, "sLatch is null, resetCountDownLatch probably should have been called");
+            }
+        }
+    }
+
+    /**
+     * Request a sync on the given account, and wait for it.
+     */
+    public static void requestSync(Account account) throws Exception {
+        final Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+
+        ContentResolver.requestSync(account, StatsdProvider.AUTHORITY, extras);
+    }
+
+    public static synchronized CountDownLatch resetCountDownLatch() {
+        sLatch = new CountDownLatch(1);
+        return sLatch;
+    }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java
new file mode 100644
index 0000000..ab06c6f
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.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.server.cts.device.statsd;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service for the sync test.
+ */
+public class StatsdSyncService extends Service {
+
+    private static StatsdSyncAdapter sAdapter;
+
+    @Override
+    public synchronized IBinder onBind(Intent intent) {
+        if (sAdapter == null) {
+            sAdapter = new StatsdSyncAdapter(getApplicationContext());
+        }
+        return sAdapter.getSyncAdapterBinder();
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/SubscriberTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/SubscriberTests.java
new file mode 100644
index 0000000..3d2a0ed
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/SubscriberTests.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsd;
+
+import android.app.PendingIntent;
+import android.app.StatsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.StatsDimensionsValue;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+import android.util.StatsLog;
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.BroadcastSubscriberDetails;
+import com.android.internal.os.StatsdConfigProto.CountMetric;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SubscriberTests {
+    public static final String TAG = "statsd.SubscriberTests";
+
+    private Context mContext;
+    private StatsManager mStatsManager;
+
+    private PendingIntent mPendingIntent1;
+    private PendingIntent mPendingIntent2;
+    private static CountDownLatch sLatch1; // static since used by receiver
+    private static CountDownLatch sLatch2; // static since used by receiver
+    private static final int UID = android.os.Process.myUid();  // static since used by receiver
+
+    public static final int SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS = 2_000;
+
+    public static final int CONFIG_ID = 12;
+    public static final int ATOM_MATCHER_ID = 1;
+    public static final int METRIC_ID = 1;
+    public static final int ALERT_ID = 1;
+    public static final int SUBSCRIPTION_ID_1 = 1;
+    public static final int SUBSCRIPTION_ID_2 = 2;
+    public static final int SUBSCRIBER_ID_1 = 1;
+    public static final int SUBSCRIBER_ID_2 = 2;
+
+    private static final StatsdConfig CONFIG = StatsdConfig.newBuilder().setId(CONFIG_ID)
+            .addAtomMatcher(AtomMatcher.newBuilder()
+                    .setId(ATOM_MATCHER_ID)
+                    .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                            .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                            // Event only when the uid is this app's uid.
+                            .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                    .setField(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                    .setEqInt(UID)
+                            )
+                    )
+            )
+            .addCountMetric(CountMetric.newBuilder()
+                    .setId(METRIC_ID)
+                    .setWhat(ATOM_MATCHER_ID)
+                    .setBucket(TimeUnit.CTS)
+                    // Slice by uid (since that's the typical case)
+                    .setDimensionsInWhat(FieldMatcher.newBuilder()
+                            .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                            .addChild(FieldMatcher.newBuilder().setField(
+                                    AppBreadcrumbReported.UID_FIELD_NUMBER))
+                    )
+            )
+            .addAlert(Alert.newBuilder()
+                    .setId(ALERT_ID)
+                    .setMetricId(METRIC_ID)
+                    .setNumBuckets(4)
+                    .setRefractoryPeriodSecs(0)
+                    .setTriggerIfSumGt(0) // even a single event triggers it
+            )
+            .addSubscription(Subscription.newBuilder()
+                    .setId(SUBSCRIPTION_ID_1)
+                    .setRuleType(Subscription.RuleType.ALERT)
+                    .setRuleId(ALERT_ID)
+                    .setBroadcastSubscriberDetails(BroadcastSubscriberDetails.newBuilder()
+                            .setSubscriberId(SUBSCRIBER_ID_1))
+            )
+            .addSubscription(Subscription.newBuilder()
+                    .setId(SUBSCRIPTION_ID_2)
+                    .setRuleType(Subscription.RuleType.ALERT)
+                    .setRuleId(ALERT_ID)
+                    .setBroadcastSubscriberDetails(BroadcastSubscriberDetails.newBuilder()
+                            .setSubscriberId(SUBSCRIBER_ID_2))
+            )
+            .build();
+
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mStatsManager = (StatsManager) mContext.getSystemService("stats");
+        mPendingIntent1 = PendingIntent.getBroadcast(mContext, 1,
+                new Intent(mContext, BroadcastSubscriberReceiver1.class), 0);
+        mPendingIntent2 = PendingIntent.getBroadcast(mContext, 2,
+                new Intent(mContext, BroadcastSubscriberReceiver2.class), 0);
+    }
+
+    /** Tests that setBroadcastSubscriber() works as intended. */
+    @Test
+    public void testBroadcastSubscriber() {
+        Log.d(TAG, "Running testBroadcastSubscriber under UID=" + UID);
+        try {
+            logPendingIntent(mPendingIntent1);
+            logPendingIntent(mPendingIntent2);
+            sLatch1 = new CountDownLatch(1);
+            sLatch2 = new CountDownLatch(1);
+            mStatsManager.removeConfiguration(CONFIG_ID);
+            mStatsManager.addConfiguration(CONFIG_ID, CONFIG.toByteArray());
+
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_1, mPendingIntent1);
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_2, mPendingIntent2);
+            AtomTests.sleep(SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS);
+            StatsLog.write(StatsLog.APP_BREADCRUMB_REPORTED, UID, /* label */ 1,
+                    StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+
+            Assert.assertTrue(waitForLatch(sLatch1, 10_000));
+            Assert.assertTrue(waitForLatch(sLatch2, 10_000));
+        } finally {
+            mStatsManager.removeConfiguration(CONFIG_ID);
+        }
+    }
+
+    /** Tests that setBroadcastSubscriber(,, null) works as intended. */
+    @Test
+    public void testUnsetBroadcastSubscriber() {
+        Log.d(TAG, "Running testUnsetBroadcastSubscriber under UID=" + UID);
+        try {
+            logPendingIntent(mPendingIntent1);
+            logPendingIntent(mPendingIntent2);
+            sLatch1 = new CountDownLatch(2);
+            sLatch2 = new CountDownLatch(2);
+            mStatsManager.removeConfiguration(CONFIG_ID);
+            mStatsManager.addConfiguration(CONFIG_ID, CONFIG.toByteArray());
+
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_1, mPendingIntent1);
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_2, mPendingIntent2);
+            AtomTests.sleep(SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS);
+            StatsLog.write(StatsLog.APP_BREADCRUMB_REPORTED, UID, /* label */ 1,
+                    StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+            AtomTests.sleep(SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS);
+            // During sleep, both latches count down by 1, as tested in testBroadcastSubscriber.
+
+            // Now remove subscriber2 and make sure only subscriber1 receives broadcast.
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_2, null);
+            AtomTests.sleep(SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS);
+            StatsLog.write(StatsLog.APP_BREADCRUMB_REPORTED, UID, /* label */ 1,
+                    StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+            Assert.assertTrue(waitForLatch(sLatch1, 10_000));
+            Log.d(TAG, "About to wait for a latch, expecting it to not finish");
+            Assert.assertFalse(waitForLatch(sLatch2, 2_000)); // should fail
+
+            // Now add subscriber2 back again and make sure it receives this time.
+            mStatsManager.setBroadcastSubscriber(CONFIG_ID, SUBSCRIBER_ID_2, mPendingIntent2);
+            AtomTests.sleep(SLEEP_TIME_AFTER_SET_SUBSCRIBER_MS);
+            StatsLog.write(StatsLog.APP_BREADCRUMB_REPORTED, UID, /* label */ 1,
+                    StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+            Assert.assertTrue(waitForLatch(sLatch2, 10_000));
+        } finally {
+            mStatsManager.removeConfiguration(CONFIG_ID);
+        }
+    }
+
+    /** Prints some information about the PendingIntent to logcat. */
+    private static void logPendingIntent(PendingIntent pendingIntent) {
+        Log.d(TAG, String.format("Created PendingIntent with " +
+                        "CreatorPackage=%s, CreatorUid=%d, toString=%s",
+                pendingIntent.getCreatorPackage(),
+                pendingIntent.getCreatorUid(),
+                pendingIntent.toString()));
+    }
+
+    /** Checks that the intent (presumably received from a broadcast) is as expected. */
+    private static void checkIntent(Intent intent, long subscriptionId) {
+        Log.d(TAG, "Received " + (intent != null ? intent.toString() : "null intent")
+        + "\nwith extras " + (intent != null && intent.getExtras() != null ?
+                intent.getExtras().toString() : "(null intent)"));
+
+        Assert.assertNotNull(intent);
+
+        long intentConfigUid = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_UID, -1);
+        long intentConfigKey = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, -1);
+        long intentSubscrId = intent.getLongExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, -1);
+        long intentRuleId = intent.getLongExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, -1);
+        StatsDimensionsValue intentDimsValue =
+                intent.getParcelableExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE);
+        String intentDimsValueStr =
+                intentDimsValue != null ? intentDimsValue.toString() : "null";
+
+        Log.d(TAG, "Relevant extras are"
+                + " {ConfigUid=" + intentConfigUid
+                + ", ConfigKey=" + intentConfigKey
+                + ", SubscriptionId=" + intentSubscrId
+                + ", RuleId=" + intentRuleId
+                + ", DimensionsValue=" + intentDimsValueStr
+                + "}");
+
+        Assert.assertEquals(UID, intentConfigUid);
+        Assert.assertEquals(CONFIG_ID, intentConfigKey);
+        Assert.assertEquals(subscriptionId, intentSubscrId);
+        Assert.assertEquals(METRIC_ID, intentRuleId);
+
+        String expectedDimValue = String.format("%d:{%d:%d|}",
+                Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, AppBreadcrumbReported.UID_FIELD_NUMBER,
+                UID);
+        Assert.assertEquals(expectedDimValue, intentDimsValueStr);
+
+        Assert.assertEquals(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, intentDimsValue.getField());
+        List<StatsDimensionsValue> intentTuple = intentDimsValue.getTupleValueList();
+        Assert.assertEquals(1, intentTuple.size());
+        StatsDimensionsValue intentTupleValue = intentTuple.get(0);
+        Assert.assertEquals(AppBreadcrumbReported.UID_FIELD_NUMBER, intentTupleValue.getField());
+        Assert.assertTrue(intentTupleValue.isValueType(StatsDimensionsValue.INT_VALUE_TYPE));
+        Assert.assertEquals(UID, intentTupleValue.getIntValue());
+    }
+
+    public final static class BroadcastSubscriberReceiver1 extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context mContext, Intent intent) {
+            Log.d(TAG, "Broadcast received by BroadcastSubscriberReceiver1");
+            checkIntent(intent, SUBSCRIPTION_ID_1);
+            Assert.assertNotNull(sLatch1);
+            sLatch1.countDown();
+        }
+    }
+    public final static class BroadcastSubscriberReceiver2 extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context mContext, Intent intent) {
+            Log.d(TAG, "Broadcast received by BroadcastSubscriberReceiver2");
+            checkIntent(intent, SUBSCRIPTION_ID_2);
+            Assert.assertNotNull(sLatch2);
+            sLatch2.countDown();
+        }
+    }
+
+    /** Waits for up to maxWaitTimeMs for the latch to count down, and returns whether it did so. */
+    private static boolean waitForLatch(CountDownLatch latch, int maxWaitTimeMs) {
+        try {
+            boolean didFinish
+                    = latch.await(maxWaitTimeMs, java.util.concurrent.TimeUnit.MILLISECONDS);
+            if (didFinish) {
+                Log.v(TAG, "Latch finished");
+                return true;
+            } else {
+                Log.w(TAG, "Latch did not finish in specified time.");
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception while awaiting latch to finish", e);
+        }
+        return false;
+    }
+}
+
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
new file mode 100644
index 0000000..ea1fcec
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.server.cts.device.statsd;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.widget.VideoView;
+
+public class VideoPlayerActivity extends Activity {
+    private static final String TAG = VideoPlayerActivity.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_PLAY_VIDEO = "action.play_video";
+    public static final String ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE =
+            "action.play_video_picture_in_picture_mode";
+
+    public static final int DELAY_MILLIS = 2000;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_PLAY_VIDEO:
+                playVideo();
+                break;
+            case ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE:
+                playVideo();
+                this.enterPictureInPictureMode();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+        delay();
+    }
+
+    private void playVideo() {
+        setContentView(R.layout.activity_main);
+        VideoView videoView = (VideoView)findViewById(R.id.video_player_view);
+        videoView.setVideoPath("android.resource://" + getPackageName() + "/" + R.raw.colors_video);
+        videoView.start();
+    }
+
+    private void delay() {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                SystemClock.sleep(DELAY_MILLIS);
+                return null;
+            }
+            @Override
+            protected void onPostExecute(Void nothing) {
+                finish();
+            }
+        }.execute();
+    }
+}
+
+
+
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
new file mode 100644
index 0000000..403ab06
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.alert;
+
+import android.cts.statsd.atom.AtomTestCase;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.CountMetric;
+import com.android.internal.os.StatsdConfigProto.DurationMetric;
+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.IncidentdDetails;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto.AnomalyDetected;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.List;
+
+/**
+ * Statsd Anomaly Detection tests.
+ */
+public class AnomalyDetectionTests extends AtomTestCase {
+
+    private static final String TAG = "Statsd.AnomalyDetectionTests";
+
+    private static final boolean INCIDENTD_TESTS_ENABLED = true;
+
+    private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
+
+    // Config constants
+    private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
+    private static final int METRIC_ID = 8;
+    private static final int ALERT_ID = 11;
+    private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
+    private static final int ANOMALY_DETECT_MATCH_ID = 10;
+    private static final int ANOMALY_EVENT_ID = 101;
+    private static final int INCIDENTD_SECTION = -1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!INCIDENTD_TESTS_ENABLED) {
+            CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
+        }
+    }
+
+    // Tests that anomaly detection for count works.
+    // Also tests that anomaly detection works when spanning multiple buckets.
+    public void testCountAnomalyDetection() throws Exception {
+        StatsdConfig.Builder config = getBaseConfig(10, 20, 2 /* threshold: > 2 counts */)
+                .addCountMetric(CountMetric.newBuilder()
+                        .setId(METRIC_ID)
+                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setBucket(TimeUnit.CTS) // 1 second
+                        // Slice by label
+                        .setDimensionsInWhat(FieldMatcher.newBuilder()
+                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                )
+                        )
+                );
+        uploadConfig(config);
+
+        String markTime = getCurrentLogcatDate();
+        // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
+        doAppBreadcrumbReportedStart(6);
+        Thread.sleep(500);
+        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
+        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+
+        // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
+        doAppBreadcrumbReportedStart(6);
+        Thread.sleep(500);
+        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
+        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+
+        // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
+        doAppBreadcrumbReportedStart(12);
+        Thread.sleep(1000);
+        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
+        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+
+        doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
+        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertEquals("Expected 1 anomaly", 1, data.size());
+        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
+        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
+        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+    }
+
+    // Tests that anomaly detection for duration works.
+    // Also tests that refractory periods in anomaly detection work.
+    public void testDurationAnomalyDetection() throws Exception {
+        final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
+        StatsdConfig.Builder config =
+                getBaseConfig(17, 17, 10_000_000_000L  /*threshold: > 10 seconds in nanoseconds*/)
+                        .addDurationMetric(DurationMetric.newBuilder()
+                                .setId(METRIC_ID)
+                                .setWhat(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // predicate below
+                                .setAggregationType(DurationMetric.AggregationType.SUM)
+                                .setBucket(TimeUnit.CTS) // 1 second
+                        )
+                        .addPredicate(StatsdConfigProto.Predicate.newBuilder()
+                                .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
+                                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
+                                        .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                                        .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
+                                )
+                        );
+        uploadConfig(config);
+
+        // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
+
+        // We prevent the device from sleeping through the anomaly alarm to avoid extra waiting.
+        // This assumes (as per CTS requirements) that screen Stay Awake is on.
+        turnScreenOn();
+
+        // Test that alarm doesn't fire early.
+        String markTime = getCurrentLogcatDate();
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(6_000);
+        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+
+        doAppBreadcrumbReportedStop(1);
+        Thread.sleep(4_000);
+        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+
+        // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(9_000);
+        List<EventMetricData> data = getEventMetricDataList();
+        assertEquals("Expected an anomaly,", 1, data.size());
+        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+
+        // Now test that the refractory period is obeyed.
+        markTime = getCurrentLogcatDate();
+        doAppBreadcrumbReportedStop(1);
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(3_000);
+        // NB: the previous getEventMetricDataList also removes the report, so it is back to 0.
+        assertEquals("Expected only 1 anomaly,", 0, getEventMetricDataList().size());
+
+        // Test that detection works again after refractory period finishes.
+        doAppBreadcrumbReportedStop(1);
+        Thread.sleep(8_000);
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(10_000);
+        // We can do an incidentd test now that all the timing issues are done.
+        data = getEventMetricDataList();
+        assertEquals("Expected another anomaly,", 1, data.size());
+        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+
+        doAppBreadcrumbReportedStop(1);
+        turnScreenOff();
+    }
+
+    // Tests that anomaly detection for duration works even when the alarm fires too late.
+    public void testDurationAnomalyDetectionForLateAlarms() throws Exception {
+        final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
+        StatsdConfig.Builder config =
+                getBaseConfig(50, 0, 6_000_000_000L /* threshold: > 6 seconds in nanoseconds */)
+                        .addDurationMetric(DurationMetric.newBuilder()
+                                .setId(METRIC_ID)
+                                .setWhat(
+                                        APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // Predicate below.
+                                .setAggregationType(DurationMetric.AggregationType.SUM)
+                                .setBucket(TimeUnit.CTS) // 1 second
+                        )
+                        .addPredicate(StatsdConfigProto.Predicate.newBuilder()
+                                .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
+                                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
+                                        .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                                        .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
+                                )
+                        );
+        uploadConfig(config);
+
+        // We prevent the device from sleeping through the anomaly alarm to avoid extra waiting.
+        // This assumes (as per CTS requirements) that screen Stay Awake is on.
+        turnScreenOn();
+
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(5_000);
+        doAppBreadcrumbReportedStop(1);
+        Thread.sleep(2_000);
+        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+
+        // Test that alarm does fire when it is supposed to.
+        // The anomaly occurs in 1s, but alarms won't fire that quickly.
+        // It is likely that the alarm will only fire after this period is already over, but the
+        // anomaly should nonetheless be detected when the event stops.
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(2_000);
+        // Anomaly should be detected here if the alarm didn't fire yet.
+        doAppBreadcrumbReportedStop(1);
+        Thread.sleep(200);
+        List<EventMetricData> data = getEventMetricDataList();
+        assertEquals("Expected an anomaly,", 1, data.size());
+        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+
+        turnScreenOff();
+    }
+
+    // Tests that anomaly detection for value works.
+    public void testValueAnomalyDetection() throws Exception {
+        StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
+                .addValueMetric(ValueMetric.newBuilder()
+                        .setId(METRIC_ID)
+                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setBucket(TimeUnit.ONE_MINUTE)
+                        // Get the label field's value:
+                        .setValueField(FieldMatcher.newBuilder()
+                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
+                        )
+
+                );
+        uploadConfig(config);
+
+        String markTime = getCurrentLogcatDate();
+        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
+        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
+        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+
+        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
+        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertEquals("Expected 1 anomaly", 1, data.size());
+        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
+        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
+        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+    }
+
+    // Tests that anomaly detection for gauge works.
+    public void testGaugeAnomalyDetection() throws Exception {
+        StatsdConfig.Builder config = getBaseConfig(1, 20, 6 /* threshold: value > 6 */)
+                .addGaugeMetric(GaugeMetric.newBuilder()
+                        .setId(METRIC_ID)
+                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setBucket(TimeUnit.CTS)
+                        // Get the label field's value into the gauge:
+                        .setGaugeFieldsFilter(
+                                FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
+                                        .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
+                                )
+                        )
+                );
+        uploadConfig(config);
+
+        String markTime = getCurrentLogcatDate();
+        doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
+        Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
+        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
+        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+
+        // We waited for >1s above, so we are now in the next bucket (which is essential).
+        doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
+        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertEquals("Expected 1 anomaly", 1, data.size());
+        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
+        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
+        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+    }
+
+    private final StatsdConfig.Builder getBaseConfig(int numBuckets,
+                                                     int refractorySecs,
+                                                     long triggerIfSumGt) throws Exception {
+        return StatsdConfig.newBuilder().setId(CONFIG_ID)
+                // Items of relevance for detecting the anomaly:
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                // Event only when the uid is this app's uid.
+                                .addFieldValueMatcher(
+                                        createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                                .setEqInt(getHostUid())
+                                )
+                                .addFieldValueMatcher(
+                                        createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                                .setEqInt(
+                                                        AppBreadcrumbReported.State.START.ordinal())
+                                )
+                        )
+                )
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                // Event only when the uid is this app's uid.
+                                .addFieldValueMatcher(
+                                        createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                                .setEqInt(getHostUid())
+                                )
+                                .addFieldValueMatcher(
+                                        createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                                .setEqInt(
+                                                        AppBreadcrumbReported.State.STOP.ordinal())
+                                )
+                        )
+                )
+                .addAlert(Alert.newBuilder()
+                        .setId(ALERT_ID)
+                        .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
+                        .setNumBuckets(numBuckets)
+                        .setRefractoryPeriodSecs(refractorySecs)
+                        .setTriggerIfSumGt(triggerIfSumGt)
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId(SUBSCRIPTION_ID_INCIDENTD)
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId(ALERT_ID)
+                        .setIncidentdDetails(IncidentdDetails.newBuilder()
+                                .addSection(INCIDENTD_SECTION))
+                )
+                // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
+                .addNoReportMetric(METRIC_ID)
+                .addAllowedLogSource("AID_ROOT") // needed for AppBreadcrumb (if rooted)
+                .addAllowedLogSource("AID_SHELL") // needed for AppBreadcrumb (if unrooted)
+                .addAllowedLogSource("AID_STATSD") // needed for AnomalyDetected
+                // No need in this test for .addAllowedLogSource("AID_SYSTEM")
+
+                // Items of relevance to reporting the anomaly (we do want this data):
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(ANOMALY_DETECT_MATCH_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(
+                                        createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
+                                                .setEqInt(getHostUid())
+                                )
+                                .addFieldValueMatcher(
+                                        createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
+                                                .setEqInt(CONFIG_ID)
+                                )
+                        )
+                )
+                .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
+                        .setId(ANOMALY_EVENT_ID)
+                        .setWhat(ANOMALY_DETECT_MATCH_ID)
+                );
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/BroadcastSubscriberTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/BroadcastSubscriberTests.java
new file mode 100644
index 0000000..8c184b9
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/BroadcastSubscriberTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.alert;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Statsd broadcast subscriber tests. Some may be done device-side.
+ */
+public class BroadcastSubscriberTests extends DeviceAtomTestCase {
+
+    private static final String TAG = "Statsd.BroadcastSubscriberTests";
+
+    // These tests currently require selinux policy to be disabled, so keep false!
+    // TODO: Fix this.
+    private static final boolean TESTS_ENABLED = false;
+
+    public static final int SUBSCRIBER_TESTS_CONFIG_ID = 12;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!TESTS_ENABLED) {
+            CLog.w(TAG, TAG + " tests are disabled by a flag. Change flag to true to run.");
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().executeShellCommand(
+                String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(getUid()),
+                        String.valueOf(SUBSCRIBER_TESTS_CONFIG_ID)));
+        super.tearDown();
+    }
+
+    /** Tests that anomaly detection generic subscribers work. */
+    public void testBroadcastSubscriber() throws Exception {
+        runAlertTest("testBroadcastSubscriber");
+    }
+
+    /** Tests that unsetting generic subscribers work. */
+    public void testUnsetBroadcastSubscriber() throws Exception {
+        runAlertTest("testUnsetBroadcastSubscriber");
+    }
+
+    public void runAlertTest(String methodName) throws Exception {
+        if (!TESTS_ENABLED) return;
+        CLog.d("\nPerforming device-side test of " + methodName + " for uid " + getUid());
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".SubscriberTests", methodName);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
new file mode 100644
index 0000000..a067f9d
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -0,0 +1,557 @@
+/*
+ * 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.cts.statsd.atom;
+
+import android.view.DisplayStateEnum;
+
+import com.android.annotations.Nullable;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Base class for testing Statsd atoms.
+ * Validates reporting of statsd logging based on different events
+ */
+public class AtomTestCase extends BaseTestCase {
+
+    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 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();
+
+    protected static final int WAIT_TIME_SHORT = 500;
+    protected static final int WAIT_TIME_LONG = 2_000;
+
+    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
+    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // TODO: need to do these before running real test:
+        // 1. compile statsd and push to device
+        // 2. make sure StatsCompanionService and incidentd is running
+        // 3. start statsd
+        // These should go away once we have statsd properly set up.
+
+        // Uninstall to clear the history in case it's still on the device.
+        removeConfig(CONFIG_ID);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        removeConfig(CONFIG_ID);
+        super.tearDown();
+    }
+
+    /**
+     * Determines whether logcat indicates that incidentd fired since the given device date.
+     */
+    protected boolean didIncidentdFireSince(String date) throws Exception {
+        final String INCIDENTD_TAG = "incidentd";
+        final String INCIDENTD_STARTED_STRING = "reportIncident";
+        // 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", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+        return log.contains(INCIDENTD_STARTED_STRING);
+    }
+
+    protected static StatsdConfig.Builder createConfigBuilder() {
+        return StatsdConfig.newBuilder().setId(CONFIG_ID);
+    }
+
+    protected void createAndUploadConfig(int atomTag) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag);
+        uploadConfig(conf);
+    }
+
+    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
+        uploadConfig(config.build());
+    }
+
+    protected void uploadConfig(StatsdConfig config) throws Exception {
+        LogUtil.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();
+        getDevice().pushFile(configFile, remotePath);
+        getDevice().executeShellCommand(
+                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+                        String.valueOf(CONFIG_ID)));
+        getDevice().executeShellCommand("rm " + remotePath);
+    }
+
+    protected void removeConfig(long configId) throws Exception {
+        getDevice().executeShellCommand(
+                String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
+    }
+
+    /** 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();
+        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<EventMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getEventMetrics().getDataList());
+        }
+        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
+
+        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
+        for (EventMetricData d : data) {
+            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
+        }
+        return data;
+    }
+
+    protected List<Atom> getGaugeMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue(reportList.getReportsCount() == 1);
+        // only config
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<Atom> data = new ArrayList<>();
+        for (GaugeMetricData gaugeMetricData :
+                report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) {
+                data.add(atom);
+            }
+        }
+
+        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
+        for (Atom d : data) {
+            LogUtil.CLog.d("Atom:\n" + d.toString());
+        }
+        return data;
+    }
+
+    protected StatsLogReport getStatsLogReport() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue(reportList.getReportsCount() == 1);
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertTrue(report.hasUidMap());
+        assertEquals(1, report.getMetricsCount());
+        return report.getMetrics(0);
+    }
+
+    /** Gets the statsd report. Note that this also deletes that report from statsd. */
+    protected ConfigMetricsReportList getReportList() throws Exception {
+        try {
+            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
+                            "--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 + ".");
+            throw (e);
+        }
+    }
+
+    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
+    protected static FieldValueMatcher.Builder createFvm(int field) {
+        return FieldValueMatcher.newBuilder().setField(field);
+    }
+
+    protected 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 key.
+     *
+     * @param conf    configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm     FieldValueMatcher.Builder for the relevant key
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            FieldValueMatcher.Builder fvm)
+            throws Exception {
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given keys.
+     *
+     * @param conf   configuration
+     * @param atomId atom tag (from atoms.proto)
+     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
+            List<FieldValueMatcher.Builder> fvms) throws Exception {
+
+        final String atomName = "Atom" + System.nanoTime();
+        final String eventName = "Event" + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        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()));
+    }
+
+    /**
+     * 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 addGaugeAtom(StatsdConfig.Builder conf, int atomId,
+            @Nullable FieldMatcher.Builder dimension) throws Exception {
+        final String atomName = "Atom" + System.nanoTime();
+        final String gaugeName = "Gauge" + System.nanoTime();
+        final String predicateName = "SCREEN_IS_ON";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        // TODO: change this predicate to something simpler and easier
+        final String predicateTrueName = "SCREEN_TURNED_ON";
+        final String predicateFalseName = "SCREEN_TURNED_OFF";
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(predicateTrueName.hashCode())
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE)
+                        )
+                )
+        )
+                // Used to trigger predicate
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(predicateFalseName.hashCode())
+                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                        .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                        .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE)
+                                )
+                        )
+                );
+        conf.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setId(gaugeName.hashCode())
+                .setWhat(atomName.hashCode())
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setBucket(TimeUnit.CTS)
+                .setCondition(predicateName.hashCode());
+        if (dimension != null) {
+            gaugeMetric.setDimensionsInWhat(dimension.build());
+        }
+        conf.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Asserts that each set of states in stateSets occurs at least once in data.
+     * Asserts that the states in data occur in the same order as the sets in stateSets.
+     *
+     * @param stateSets        A list of set of states, where each set represents an equivalent
+     *                         state of the device for the purpose of CTS.
+     * @param data             list of EventMetricData from statsd, produced by
+     *                         getReportMetricListData()
+     * @param wait             expected duration (in ms) between state changes; asserts that the
+     *                         actual wait
+     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
+     *                         assertion.
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
+            int wait, Function<Atom, Integer> getStateFromAtom) {
+        // Sometimes, there are more events than there are states.
+        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+        assertTrue("Too few states found (" + data.size() + ")", data.size() >= stateSets.size());
+        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
+        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
+            Atom atom = data.get(dataIndex).getAtom();
+            int state = getStateFromAtom.apply(atom);
+            // If state is in the current state set, we do not assert anything.
+            // If it is not, we expect to have transitioned to the next state set.
+            if (stateSets.get(stateSetIndex).contains(state)) {
+                // No need to assert anything. Just log it.
+                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
+                        + "in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+            } else {
+                stateSetIndex += 1;
+                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+                        + " in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+                assertTrue("Missed first state", dataIndex != 0); // should not be on first data
+                assertTrue("Too many states (" + (stateSetIndex + 1) + ")",
+                        stateSetIndex < stateSets.size());
+                assertTrue("Is in wrong state (" + state + ")",
+                        stateSets.get(stateSetIndex).contains(state));
+                if (wait > 0) {
+                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
+                            wait / 2, wait * 5);
+                }
+            }
+        }
+        assertTrue("Too few states (" + (stateSetIndex + 1) + ")",
+                stateSetIndex == stateSets.size() - 1);
+    }
+
+    /**
+     * Removes all elements from data prior to the first occurrence of an element of state. After
+     * this method is called, the first element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
+            Function<Atom, Integer> getStateFromAtom) {
+        int firstStateIdx;
+        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
+            Atom atom = data.get(firstStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (firstStateIdx == 0) {
+            // First first element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(0, firstStateIdx).clear();
+    }
+
+    /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
+    protected int getHostUid() throws DeviceNotAvailableException {
+        String strUid = "";
+        try {
+            strUid = getDevice().executeShellCommand("id -u");
+            return Integer.parseInt(strUid.trim());
+        } catch (NumberFormatException e) {
+            LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
+            // Fall back to alternative method...
+            if (getDevice().isAdbRoot()) {
+                return 0; // ROOT
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    protected void turnScreenOn() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().executeShellCommand("wm dismiss-keyguard");
+    }
+
+    protected void turnScreenOff() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+    }
+
+    protected void setChargingState(int state) throws Exception {
+        getDevice().executeShellCommand("cmd battery set status " + state);
+    }
+
+    protected void unplugDevice() throws Exception {
+        getDevice().executeShellCommand("cmd battery unplug");
+    }
+
+    protected void plugInAc() throws Exception {
+        getDevice().executeShellCommand("cmd battery set ac 1");
+    }
+
+    protected void plugInUsb() throws Exception {
+        getDevice().executeShellCommand("cmd battery set usb 1");
+    }
+
+    protected void plugInWireless() throws Exception {
+        getDevice().executeShellCommand("cmd battery set wireless 1");
+    }
+
+    public void doAppBreadcrumbReportedStart(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
+    }
+
+    public void doAppBreadcrumbReportedStop(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
+    }
+
+    public void doAppBreadcrumbReported(int label, int state) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd stats log-app-breadcrumb %d %d", label, state));
+    }
+
+    protected void setBatteryLevel(int level) throws Exception {
+        getDevice().executeShellCommand("cmd battery set level " + level);
+    }
+
+    protected void resetBatteryStatus() throws Exception {
+        getDevice().executeShellCommand("cmd battery reset");
+    }
+
+    protected int getScreenBrightness() throws Exception {
+        return Integer.parseInt(
+                getDevice().executeShellCommand("settings get system screen_brightness").trim());
+    }
+
+    protected void setScreenBrightness(int brightness) throws Exception {
+        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
+    }
+
+    protected boolean isScreenBrightnessModeManual() throws Exception {
+        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
+        return Integer.parseInt(mode.trim()) == 0;
+    }
+
+    protected void setScreenBrightnessMode(boolean manual) throws Exception {
+        getDevice().executeShellCommand(
+                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
+    }
+
+    protected int getScreenTimeoutMillis() throws Exception {
+        return Integer.parseInt(
+                getDevice().executeShellCommand("settings get system screen_off_timeout").trim());
+    }
+
+    protected void setScreenTimeoutMillis(int timeout) throws Exception {
+        getDevice().executeShellCommand("settings put system screen_off_timeout " + timeout);
+    }
+
+    protected void enterDozeModeLight() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
+    }
+
+    protected void enterDozeModeDeep() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
+    }
+
+    protected void leaveDozeMode() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle unforce");
+        getDevice().executeShellCommand("dumpsys deviceidle disable");
+        getDevice().executeShellCommand("dumpsys deviceidle enable");
+    }
+
+    protected void turnBatterySaverOn() throws Exception {
+        getDevice().executeShellCommand("cmd battery unplug");
+        getDevice().executeShellCommand("settings put global low_power 1");
+    }
+
+    protected void turnBatterySaverOff() throws Exception {
+        getDevice().executeShellCommand("settings put global low_power 0");
+        getDevice().executeShellCommand("cmd battery reset");
+    }
+
+    protected void rebootDevice() throws Exception {
+        getDevice().rebootUntilOnline();
+    }
+
+    /**
+     * Asserts that the two events are within the specified range of each other.
+     *
+     * @param d0        the event that should occur first
+     * @param d1        the event that should occur second
+     * @param minDiffMs d0 should precede d1 by at least this amount
+     * @param maxDiffMs d0 should precede d1 by at most this amount
+     */
+    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
+            int minDiffMs, int maxDiffMs) {
+        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
+        assertTrue("Illegal time difference (" + diffMs + "ms)", minDiffMs <= diffMs);
+        assertTrue("Illegal time difference (" + diffMs + "ms)", diffMs <= maxDiffMs);
+    }
+
+    protected String getCurrentLogcatDate() throws Exception {
+        // TODO: Do something more robust than this for getting logcat markers.
+        long timestampMs = getDevice().getDeviceDate();
+        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+                .format(new Date(timestampMs));
+    }
+
+    protected String getLogcatSince(String date, String logcatParams) throws Exception {
+        return getDevice().executeShellCommand(String.format(
+                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+    }
+
+    /**
+     * TODO: Anomaly detection will be moved to general statsd device-side tests.
+     * Pulled atoms also should have a better way of constructing the config.
+     * Remove this config when that happens.
+     */
+    protected StatsdConfig.Builder getPulledAndAnomalyConfig() {
+        return StatsdConfig.newBuilder().setId(CONFIG_ID);
+    }
+
+    /**
+     * Determines if the device has the given feature.
+     * Prints a warning if its value differs from requiredAnswer.
+     */
+    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
+        final String features = getDevice().executeShellCommand("pm list features");
+        boolean hasIt = features.contains(featureName);
+        if (hasIt != requiredAnswer) {
+            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
+                    + featureName);
+        }
+        return hasIt;
+    }
+
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
new file mode 100644
index 0000000..654861f
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+// Largely copied from incident's ProtoDumpTestCase
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+
+    protected IBuildInfo mCtsBuild;
+
+    private static final String TEST_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertNotNull(mCtsBuild);
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Call onto the device with an adb shell command 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.
+     */
+    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        return parser.parseFrom(receiver.getOutput());
+    }
+
+    /**
+     * Install a device side test package.
+     *
+     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+     * @param grantPermissions whether to give runtime permissions.
+     */
+    protected void installPackage(String appFileName, boolean grantPermissions)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        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);
+    }
+
+    /**
+     * 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
+     */
+    protected void runDeviceTests(@Nonnull String pkgName,
+            @Nullable String testClassName, @Nullable 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(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
new file mode 100644
index 0000000..b54a070
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -0,0 +1,211 @@
+/*
+ * 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.cts.statsd.atom;
+
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.MessageMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
+ */
+public class DeviceAtomTestCase extends AtomTestCase {
+
+    protected static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
+    protected static final String DEVICE_SIDE_TEST_PACKAGE
+            = "com.android.server.cts.device.statsd";
+
+    protected static final String CONFIG_NAME = "cts_config";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        installTestApp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Performs a device-side test by calling a method on the app and returns its stats events.
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param atom atom tag (from atoms.proto)
+     * @param key atom's field corresponding to state
+     * @param stateOn 'on' value
+     * @param stateOff 'off' value
+     * @param minTimeDiffMs max allowed time between start and stop
+     * @param maxTimeDiffMs min allowed time between start and stop
+     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
+     * @return list of events with the app's uid matching the configuration defined by the params.
+     */
+    protected List<EventMetricData> doDeviceMethodOnOff(
+            String methodName, int atom, int key, int stateOn, int stateOff,
+            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
+        List<EventMetricData> data = doDeviceMethod(methodName, conf);
+
+        if (demandExactlyTwo) {
+            assertTrue(data.size() == 2);
+        } else {
+            assertTrue(data.size() >= 2);
+        }
+        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
+        return data;
+    }
+
+    /**
+     *
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param cfg statsd configuration
+     * @return list of events with the app's uid matching the configuration.
+     */
+    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
+            throws Exception {
+        removeConfig(CONFIG_ID);
+        uploadConfig(cfg);
+        int appUid = getUid();
+        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
+
+        return getEventMetricDataList();
+    }
+
+    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag, useAttribution);
+        uploadConfig(conf);
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm FieldValueMatcher.Builder for the relevant key
+     */
+    @Override
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
+            throws Exception {
+
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param useAttribution if the atom has a uid or AttributionNode
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            boolean useAttribution) throws Exception {
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid;
+        if (useAttribution) {
+            fvmUid = createAttributionFvm(UID_KEY);
+        } else {
+            fvmUid = createFvm(UID_KEY).setEqInt(getUid());
+        }
+        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
+    }
+
+    /**
+     * Creates a FieldValueMatcher for atoms that use AttributionNode
+     */
+    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
+        final int ATTRIBUTION_NODE_UID_KEY = 1;
+        return createFvm(field).setPosition(Position.ANY)
+                .setMatchesTuple(MessageMatcher.newBuilder()
+                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
+                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
+    }
+
+    /**
+     * Gets the uid of the test app.
+     */
+    protected int getUid() throws Exception {
+        String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
+                + DEVICE_SIDE_TEST_PACKAGE);
+        String[] uidLineParts = uidLine.split(":");
+        // 3rd entry is package uid
+        assertTrue(uidLineParts.length > 2);
+        int uid = Integer.parseInt(uidLineParts[2].trim());
+        assertTrue(uid > 10000);
+        return uid;
+    }
+
+    /**
+     * Installs the test apk.
+     */
+    protected void installTestApp() throws Exception {
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
+        allowBackgroundServices();
+    }
+
+    /**
+     * Required to successfully start a background service from adb in O.
+     */
+    protected void allowBackgroundServices() throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
+        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue,
+            long waitTime) throws Exception {
+        String intentString = null;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        }
+        if (intentString == null) {
+            getDevice().executeShellCommand(
+                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
+        } else {
+            getDevice().executeShellCommand(
+                    "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);
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
new file mode 100644
index 0000000..67c9d78
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -0,0 +1,609 @@
+/*
+ * 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.cts.statsd.atom;
+
+import android.os.BatteryPluggedStateEnum; // From os/enums.proto
+import android.os.BatteryStatusEnum; // From os/enums.proto
+import android.os.TemperatureTypeEnum; // From os/enums.proto
+import android.platform.test.annotations.RestrictedBuildTest;
+import android.server.DeviceIdleModeEnum; // From server/enums.proto
+import android.view.DisplayStateEnum; // From view/enums.proto
+
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.CountMetric;
+import com.android.internal.os.StatsdConfigProto.DurationMetric;
+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.IncidentdDetails;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BatterySaverModeStateChanged;
+import com.android.os.AtomsProto.ChargingStateChanged;
+import com.android.os.AtomsProto.CpuTimePerFreq;
+import com.android.os.AtomsProto.DeviceIdleModeStateChanged;
+import com.android.os.AtomsProto.FullBatteryCapacity;
+import com.android.os.AtomsProto.KernelWakelock;
+import com.android.os.AtomsProto.PluggedStateChanged;
+import com.android.os.AtomsProto.RemainingBatteryCapacity;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.AtomsProto.SubsystemSleepState;
+import com.android.os.AtomsProto.Temperature;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Statsd atom tests that are done via adb (hostside).
+ */
+public class HostAtomTests extends AtomTestCase {
+
+    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";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testScreenStateChangedAtom() throws Exception {
+        // Setup, make sure the screen is off.
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenOnStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_ON_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_VR_VALUE));
+        Set<Integer> screenOffStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenOnStates, screenOffStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        // 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.getScreenStateChanged().getState().getNumber());
+    }
+
+    public void testChargingStateChangedAtom() throws Exception {
+        // Setup, set charging state to full.
+        setChargingState(5);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.CHARGING_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryUnknownStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_UNKNOWN_VALUE));
+        Set<Integer> batteryChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_CHARGING_VALUE));
+        Set<Integer> batteryDischargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_DISCHARGING_VALUE));
+        Set<Integer> batteryNotChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_NOT_CHARGING_VALUE));
+        Set<Integer> batteryFullStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_FULL_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryUnknownStates, batteryChargingStates,
+                batteryDischargingStates, batteryNotChargingStates, batteryFullStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        setChargingState(1);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(2);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(3);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(4);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setChargingState(5);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getChargingStateChanged().getState().getNumber());
+    }
+
+    public void testPluggedStateChangedAtom() throws Exception {
+        // Setup, unplug device.
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> unpluggedStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE));
+        Set<Integer> acStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE));
+        Set<Integer> usbStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE));
+        Set<Integer> wirelessStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(acStates, unpluggedStates, usbStates,
+                unpluggedStates, wirelessStates, unpluggedStates);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        plugInAc();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+        plugInUsb();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+        plugInWireless();
+        Thread.sleep(WAIT_TIME_SHORT);
+        unplugDevice();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getPluggedStateChanged().getState().getNumber());
+    }
+
+    public void testBatteryLevelChangedAtom() throws Exception {
+        // Setup, set battery level to full.
+        setBatteryLevel(100);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryDead = new HashSet<>(Arrays.asList(0));
+        Set<Integer> battery25p = new HashSet<>(Arrays.asList(25));
+        Set<Integer> battery50p = new HashSet<>(Arrays.asList(50));
+        Set<Integer> battery75p = new HashSet<>(Arrays.asList(75));
+        Set<Integer> batteryFull = new HashSet<>(Arrays.asList(100));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryDead, battery25p, battery50p,
+                battery75p, batteryFull);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        setBatteryLevel(0);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(25);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(50);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(75);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setBatteryLevel(100);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Unfreeze battery state after test
+        resetBatteryStatus();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getBatteryLevelChanged().getBatteryLevel());
+    }
+
+    public void testScreenBrightnessChangedAtom() throws Exception {
+        // Setup: record initial brightness state, set mode to manual and brightness to full.
+        int initialBrightness = getScreenBrightness();
+        boolean isInitialManual = isScreenBrightnessModeManual();
+        int initialTimeout = getScreenTimeoutMillis();
+        setScreenTimeoutMillis(600000);
+        turnScreenOn();
+        setScreenBrightnessMode(true);
+        setScreenBrightness(255);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.SCREEN_BRIGHTNESS_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenMin = new HashSet<>(Arrays.asList(25));
+        Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
+        Set<Integer> screen200 = new HashSet<>(Arrays.asList(200));
+        Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen200, screenMax);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        setScreenBrightness(25);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setScreenBrightness(100);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setScreenBrightness(200);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setScreenBrightness(255);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Restore initial screen brightness
+        setScreenBrightness(initialBrightness);
+        setScreenBrightnessMode(isInitialManual);
+        setScreenTimeoutMillis(initialTimeout);
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+                atom -> atom.getScreenBrightnessChanged().getLevel());
+    }
+
+    public void testDeviceIdleModeStateChangedAtom() throws Exception {
+        // Setup, leave doze mode.
+        leaveDozeMode();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.DEVICE_IDLE_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> dozeOff = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_OFF_VALUE));
+        Set<Integer> dozeLight = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_LIGHT_VALUE));
+        Set<Integer> dozeDeep = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_DEEP_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(dozeLight, dozeDeep, dozeOff);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        enterDozeModeLight();
+        Thread.sleep(WAIT_TIME_SHORT);
+        enterDozeModeDeep();
+        Thread.sleep(WAIT_TIME_SHORT);
+        leaveDozeMode();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // 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_SHORT,
+                atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
+    }
+
+    public void testBatterySaverModeStateChangedAtom() throws Exception {
+        // Setup, turn off battery saver.
+        turnBatterySaverOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batterySaverOn = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.ON_VALUE));
+        Set<Integer> batterySaverOff = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batterySaverOn, batterySaverOff);
+
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Trigger events in same order.
+        turnBatterySaverOn();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnBatterySaverOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // 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_SHORT,
+                atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
+    }
+
+    @RestrictedBuildTest
+    public void testRemainingBatteryCapacity() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        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);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        turnScreenOff();
+        assertTrue(data.size() > 0);
+        Atom atom = data.get(0);
+        assertTrue(atom.getRemainingBatteryCapacity().hasChargeUAh());
+        assertTrue(atom.getRemainingBatteryCapacity().getChargeUAh() > 0);
+    }
+
+    @RestrictedBuildTest
+    public void testFullBatteryCapacity() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        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);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        turnScreenOff();
+
+        assertTrue(data.size() > 0);
+        Atom atom = data.get(0);
+        assertTrue(atom.getFullBatteryCapacity().hasCapacityUAh());
+        assertTrue(atom.getFullBatteryCapacity().getCapacityUAh() > 0);
+    }
+
+    @RestrictedBuildTest
+    public void testTemperature() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+                .setField(Atom.TEMPERATURE_FIELD_NUMBER)
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(Temperature.SENSOR_LOCATION_FIELD_NUMBER))
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(Temperature.SENSOR_NAME_FIELD_NUMBER));
+        addGaugeAtom(config, Atom.TEMPERATURE_FIELD_NUMBER, dimension);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        turnScreenOff();
+
+        assertTrue(data.size() >= TemperatureTypeEnum.values().length - 1);
+        for (int i = 0; i < data.size(); i++) {
+            Temperature temp = data.get(i).getTemperature();
+            assertTrue("Temperature atom " + i + " has no type",
+                    temp.hasSensorLocation());
+            assertTrue("Temperature reported atom " + i + " has no temperature",
+                    temp.hasTemperatureC());
+            assertTrue("Temperature reported atom " + i + " has an unreasonably low temperature:" +
+                    + temp.getTemperatureC(), temp.getTemperatureC() > 0.0);
+            assertTrue("Temperature reported atom " + i + " has an unreasonably high temperature:" +
+                    + temp.getTemperatureC(), temp.getTemperatureC() < 80.0);
+
+        }
+    }
+
+    public void testKernelWakelock() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        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);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        Atom atom = data.get(0);
+        assertTrue(!atom.getKernelWakelock().getName().equals(""));
+        assertTrue(atom.getKernelWakelock().hasCount());
+        assertTrue(atom.getKernelWakelock().hasVersion());
+        assertTrue(atom.getKernelWakelock().getVersion() > 0);
+        assertTrue(atom.getKernelWakelock().hasTime());
+    }
+
+    public void testCpuTimePerFreq() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+                .setField(Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER)
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(CpuTimePerFreq.CLUSTER_FIELD_NUMBER));
+        addGaugeAtom(config, Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER, dimension);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(2000);
+        turnScreenOn();
+        Thread.sleep(2000);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        Atom atom = data.get(0);
+        assertTrue(atom.getCpuTimePerFreq().getCluster() >= 0);
+        assertTrue(atom.getCpuTimePerFreq().getFreqIndex() >= 0);
+        assertTrue(atom.getCpuTimePerFreq().getTimeMillis() > 0);
+    }
+
+    public void testSubsystemSleepState() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+                .setField(Atom.SUBSYSTEM_SLEEP_STATE_FIELD_NUMBER)
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(SubsystemSleepState.SUBSYSTEM_NAME_FIELD_NUMBER));
+        addGaugeAtom(config, Atom.SUBSYSTEM_SLEEP_STATE_FIELD_NUMBER, dimension);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertTrue(!atom.getSubsystemSleepState().getSubsystemName().equals(""));
+            assertTrue(atom.getSubsystemSleepState().getCount() >= 0);
+            assertTrue(atom.getSubsystemSleepState().getTimeMillis() >= 0);
+        }
+    }
+
+    public void testModemActivityInfo() throws Exception {
+        if (!hasFeature(FEATURE_TELEPHONY, true)) return;
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        addGaugeAtom(config, Atom.MODEM_ACTIVITY_INFO_FIELD_NUMBER, null);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertTrue(atom.getModemActivityInfo().getTimestampMillis() > 0);
+            assertTrue(atom.getModemActivityInfo().getSleepTimeMillis() > 0);
+            assertTrue(atom.getModemActivityInfo().getControllerIdleTimeMillis() > 0);
+            assertTrue(atom.getModemActivityInfo().getControllerTxTimePl0Millis() >= 0);
+            assertTrue(atom.getModemActivityInfo().getControllerRxTimeMillis() >= 0);
+            assertTrue(atom.getModemActivityInfo().getEnergyUsed() >= 0);
+        }
+    }
+
+    public void testWifiActivityInfo() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        addGaugeAtom(config, Atom.WIFI_ACTIVITY_ENERGY_INFO_FIELD_NUMBER, null);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertTrue(atom.getWifiActivityEnergyInfo().getTimestampMillis() > 0);
+            assertTrue(atom.getWifiActivityEnergyInfo().getStackState() >= 0);
+            assertTrue(atom.getWifiActivityEnergyInfo().getControllerIdleTimeMillis() > 0);
+            assertTrue(atom.getWifiActivityEnergyInfo().getControllerTxTimeMillis() >= 0);
+            assertTrue(atom.getWifiActivityEnergyInfo().getControllerRxTimeMillis() >= 0);
+            assertTrue(atom.getWifiActivityEnergyInfo().getControllerEnergyUsed() >= 0);
+        }
+    }
+
+    public void testBluetoothActivityInfo() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH, true)) return;
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        addGaugeAtom(config, Atom.BLUETOOTH_ACTIVITY_INFO_FIELD_NUMBER, null);
+
+        turnScreenOff();
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertTrue(atom.getBluetoothActivityInfo().getTimestampMillis() > 0);
+            assertTrue(atom.getBluetoothActivityInfo().getBluetoothStackState() >= 0);
+            assertTrue(atom.getBluetoothActivityInfo().getControllerIdleTimeMillis() > 0);
+            assertTrue(atom.getBluetoothActivityInfo().getControllerTxTimeMillis() >= 0);
+            assertTrue(atom.getBluetoothActivityInfo().getControllerRxTimeMillis() >= 0);
+            assertTrue(atom.getBluetoothActivityInfo().getEnergyUsed() >= 0);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
new file mode 100644
index 0000000..3645381
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.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 com.android.tradefed.log.LogUtil.CLog;
+
+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;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class ProcStateAtomTests extends DeviceAtomTestCase {
+
+    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;
+    private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
+    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
+
+    // The tests here are using the BatteryStats definition of 'background'.
+    private static final Set<Integer> BG_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TRANSIENT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BACKUP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_RECEIVER_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_HEAVY_WEIGHT_VALUE
+            ));
+
+    // Using the BatteryStats definition of 'cached', which is why HOME (etc) are considered cached.
+    private static final Set<Integer> CACHED_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_HOME_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_LAST_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_CLIENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_RECENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_EMPTY_VALUE
+            ));
+
+    private static final Set<Integer> MISC_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_UI_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_TOP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BOUND_FOREGROUND_SERVICE_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE,
+
+                    ProcessStateEnum.PROCESS_STATE_UNKNOWN_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_NONEXISTENT_VALUE
+            ));
+
+    private static final Set<Integer> ALL_STATES = Stream.of(MISC_STATES, CACHED_STATES, BG_STATES)
+            .flatMap(s -> s.stream()).collect(Collectors.toSet());
+
+    private static final Function<Atom, Integer> PROC_STATE_FUNCTION =
+            atom -> atom.getUidProcessStateChanged().getState().getNumber();
+
+    private static final int PROC_STATE_ATOM_TAG = Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testForegroundService() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        executeForegroundService();
+        final int waitTime = SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testForeground() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE));
+        // There are no offStates, since the app remains in foreground until killed.
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        turnScreenOn();
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+        turnScreenOff();
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testBackground() throws Exception {
+        Set<Integer> onStates = BG_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        final int waitTime = SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTop() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        turnScreenOn();
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+        turnScreenOff();
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTopSleeping() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  //False: does not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        turnScreenOn();
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        // ASAP, turn off the screen to make proc state -> top_sleeping.
+        turnScreenOff();
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out initial proc states.
+        // Don't check the wait time, since it's up to the system how long top sleeping persists.
+        assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testCached() throws Exception {
+        Set<Integer> onStates = CACHED_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(PROC_STATE_ATOM_TAG, false);  // False: des not use attribution.
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        // The schedule is as follows
+        // #1. The system may do anything it wants, such as moving the app into a cache state.
+        // #2. We move the app into the background.
+        // #3. The background process ends, so the app definitely moves to a cache state
+        //          (this is the ultimate goal of the test).
+        // #4. We start a foreground activity, moving the app out of cache.
+
+        // 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);
+        // Now forcibly bring the app out of cache (#4 above).
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        // Now check the data *before* the app enters cache again (to avoid another cache event).
+
+        List<EventMetricData> data = getEventMetricDataList();
+        // First, clear out any incidental cached states of step #1, prior to step #2.
+        popUntilFind(data, BG_STATES, PROC_STATE_FUNCTION);
+        // Now clear out the bg state from step #2 (since we are interested in the cache after it).
+        popUntilFind(data, onStates, PROC_STATE_FUNCTION);
+        // The result is that data should start at step #3, definitively in a cached state.
+        assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
+    }
+
+    public void testValidityOfStates() throws Exception {
+        assertFalse("UNKNOWN_TO_PROTO should not be a valid state",
+                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);
+        result.removeAll(b);
+        return result;
+    }
+
+    /** Returns the set of all states that are not in set. */
+    private Set<Integer> complement(Set<Integer> set) {
+        return difference(ALL_STATES, set);
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
new file mode 100644
index 0000000..a9d5163
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -0,0 +1,604 @@
+/*
+ * 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.cts.statsd.atom;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.WakeLockLevelEnum;
+
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.AppStartChanged;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.AudioStateChanged;
+import com.android.os.AtomsProto.BleScanResultReceived;
+import com.android.os.AtomsProto.BleScanStateChanged;
+import com.android.os.AtomsProto.BleUnoptimizedScanStateChanged;
+import com.android.os.AtomsProto.CameraStateChanged;
+import com.android.os.AtomsProto.CpuTimePerUid;
+import com.android.os.AtomsProto.CpuTimePerUidFreq;
+import com.android.os.AtomsProto.FlashlightStateChanged;
+import com.android.os.AtomsProto.ForegroundServiceStateChanged;
+import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.MediaCodecActivityChanged;
+import com.android.os.AtomsProto.OverlayStateChanged;
+import com.android.os.AtomsProto.PictureInPictureStateChanged;
+import com.android.os.AtomsProto.ScheduledJobStateChanged;
+import com.android.os.AtomsProto.SyncStateChanged;
+import com.android.os.AtomsProto.WakelockStateChanged;
+import com.android.os.AtomsProto.WifiLockStateChanged;
+import com.android.os.AtomsProto.WifiMulticastLockStateChanged;
+import com.android.os.AtomsProto.WifiScanStateChanged;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class UidAtomTests extends DeviceAtomTestCase {
+
+    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";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testAppStartChanged() throws Exception {
+        final int atomTag = Atom.APP_START_CHANGED_FIELD_NUMBER;
+        final String name = "testAppStart";
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        AppStartChanged atom = data.get(0).getAtom().getAppStartChanged();
+        assertEquals("com.android.server.cts.device.statsd", atom.getPkgName());
+        assertEquals(AppStartChanged.TransitionType.WARM, atom.getType());
+        assertEquals("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity",
+                atom.getActivityName());
+        assertFalse(atom.getIsInstantApp());
+        assertTrue(atom.getActivityStartMillis() > 0);
+        assertTrue(atom.getTransitionDelayMillis() > 0);
+    }
+
+    public void testBleScan() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int field = BleScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = BleScanStateChanged.State.ON_VALUE;
+        final int stateOff = BleScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+        BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+        BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertTrue(a0.getState().getNumber() == stateOn);
+        assertTrue(a1.getState().getNumber() == stateOff);
+    }
+
+    public void testBleUnoptimizedScan() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        final int atom = Atom.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int field = BleUnoptimizedScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = BleUnoptimizedScanStateChanged.State.ON_VALUE;
+        final int stateOff = BleUnoptimizedScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+
+        BleUnoptimizedScanStateChanged a0 = data.get(0).getAtom().getBleUnoptimizedScanStateChanged();
+        BleUnoptimizedScanStateChanged a1 = data.get(1).getAtom().getBleUnoptimizedScanStateChanged();
+        assertTrue(a0.getState().getNumber() == stateOn);
+        assertTrue(a1.getState().getNumber() == stateOff);
+
+        // Now repeat the test for optimized scanning and make sure no atoms are reported.
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(field).setEqInt(stateOn));
+        addAtomEvent(conf, atom, createFvm(field).setEqInt(stateOff));
+        data = doDeviceMethod("testBleScanOptimized", conf);
+        assertTrue(data.isEmpty());
+    }
+
+    public void testBleScanResult() throws Exception {
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+        turnScreenOn(); // BLE results are not given unless screen is on. TODO: make more robust.
+
+        final int atom = Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
+        final int field = BleScanResultReceived.NUM_OF_RESULTS_FIELD_NUMBER;
+
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(field).setGteInt(0));
+        List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
+
+        assertTrue(data.size() >= 1);
+        BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
+        assertTrue(a0.getNumOfResults() >= 1);
+
+        turnScreenOff();
+    }
+
+    public void testCameraState() throws Exception {
+        if (!hasFeature(FEATURE_CAMERA, true) && !hasFeature(FEATURE_CAMERA_FRONT, true)) return;
+
+        final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> cameraOn = new HashSet<>(Arrays.asList(CameraStateChanged.State.ON_VALUE));
+        Set<Integer> cameraOff = new HashSet<>(Arrays.asList(CameraStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(cameraOn, cameraOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testCameraState");
+
+        // 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.getCameraStateChanged().getState().getNumber());
+    }
+
+    public void testCpuTimePerUid() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        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);
+
+        uploadConfig(config);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<Atom> atomList = getGaugeMetricDataList();
+
+        // TODO: We don't have atom matching on gauge yet. Let's refactor this after that feature is
+        // implemented.
+        boolean found = false;
+        int uid = getUid();
+        for (Atom atom : atomList) {
+            if (atom.getCpuTimePerUid().getUid() == uid) {
+                found = true;
+                assertTrue(atom.getCpuTimePerUid().getUserTimeMillis() > 0);
+                assertTrue(atom.getCpuTimePerUid().getSysTimeMillis() > 0);
+            }
+        }
+        assertTrue("found uid " + uid, found);
+    }
+
+    public void testCpuTimePerUidFreq() throws Exception {
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+                .setField(Atom.CPU_TIME_PER_UID_FREQ_FIELD_NUMBER)
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(CpuTimePerUidFreq.UID_FIELD_NUMBER));
+        addGaugeAtom(config, Atom.CPU_TIME_PER_UID_FREQ_FIELD_NUMBER, dimension);
+
+        uploadConfig(config);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnScreenOn();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+        turnScreenOn();
+
+        List<Atom> atomList = getGaugeMetricDataList();
+
+        // TODO: We don't have atom matching on gauge yet. Let's refactor this after that feature is
+        // implemented.
+        boolean found = false;
+        int uid = getUid();
+        for (Atom atom : atomList) {
+            if (atom.getCpuTimePerUidFreq().getUid() == uid) {
+                found = true;
+                assertTrue(atom.getCpuTimePerUidFreq().getFreqIdx() >= 0);
+                assertTrue(atom.getCpuTimePerUidFreq().getTimeMillis() > 0);
+            }
+        }
+        assertTrue("found uid " + uid, found);
+    }
+
+    public void testFlashlightState() throws Exception {
+        if (!hasFeature(FEATURE_CAMERA_FLASH, true)) return;
+
+        final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testFlashlight";
+
+        Set<Integer> flashlightOn = new HashSet<>(
+            Arrays.asList(FlashlightStateChanged.State.ON_VALUE));
+        Set<Integer> flashlightOff = new HashSet<>(
+            Arrays.asList(FlashlightStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(flashlightOn, flashlightOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // 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_SHORT,
+                atom -> atom.getFlashlightStateChanged().getState().getNumber());
+    }
+
+    public void testForegroundServiceState() throws Exception {
+        final int atomTag = Atom.FOREGROUND_SERVICE_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testForegroundService";
+
+        Set<Integer> enterForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.ENTER_VALUE));
+        Set<Integer> exitForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.EXIT_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(enterForeground, exitForeground);
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // 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_SHORT,
+                atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
+    }
+
+    public void testGpsScan() throws Exception {
+        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+        // Whitelist this app against background location request throttling
+        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;
+
+        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);
+    }
+
+    public void testDavey() throws Exception {
+        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 testScheduledJobState() throws Exception {
+        String expectedName = "com.android.server.cts.device.statsd/.StatsdJobService";
+        final int atomTag = Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> jobSchedule = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.SCHEDULED_VALUE));
+        Set<Integer> jobOn = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.STARTED_VALUE));
+        Set<Integer> jobOff = new HashSet<>(
+                Arrays.asList(ScheduledJobStateChanged.State.FINISHED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testScheduledJob");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getScheduledJobStateChanged().getState().getNumber());
+
+        for (EventMetricData e : data) {
+            assertTrue(e.getAtom().getScheduledJobStateChanged().getName().equals(expectedName));
+        }
+    }
+
+    public void testSyncState() throws Exception {
+        final int atomTag = Atom.SYNC_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> syncOn = new HashSet<>(Arrays.asList(SyncStateChanged.State.ON_VALUE));
+        Set<Integer> syncOff = new HashSet<>(Arrays.asList(SyncStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(syncOn, syncOff, syncOn, syncOff);
+
+        createAndUploadConfig(atomTag, true);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSyncState");
+
+        // 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_SHORT,
+                atom -> atom.getSyncStateChanged().getState().getNumber());
+    }
+
+    public void testWakelockState() throws Exception {
+        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.ACQUIRE_VALUE,
+                WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
+        Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.RELEASE_VALUE,
+                WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
+
+        final String EXPECTED_TAG = "StatsdPartialWakelock";
+        final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
+
+        // 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_SHORT,
+            atom -> atom.getWakelockStateChanged().getState().getNumber());
+
+        for (EventMetricData event: data) {
+            String tag = event.getAtom().getWakelockStateChanged().getTag();
+            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getLevel();
+            assertTrue("Expected tag: " + EXPECTED_TAG + ", but got tag: " + tag,
+                    tag.equals(EXPECTED_TAG));
+            assertTrue("Expected wakelock level: " + EXPECTED_LEVEL  + ", but got level: " + type,
+                    type == EXPECTED_LEVEL);
+        }
+    }
+
+    public void testWakeupAlarm() throws Exception {
+        final int atomTag = Atom.WAKEUP_ALARM_OCCURRED_FIELD_NUMBER;
+
+        StatsdConfig.Builder config = createConfigBuilder();
+        addAtomEvent(config, atomTag, true);  // True: uses attribution.
+
+        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"));
+        }
+    }
+
+    public void testWifiLock() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+
+        final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLock");
+
+        // 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_SHORT,
+                atom -> atom.getWifiLockStateChanged().getState().getNumber());
+    }
+
+    public void testWifiMulticastLock() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+
+        final int atomTag = Atom.WIFI_MULTICAST_LOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> lockOn = new HashSet<>(
+                Arrays.asList(WifiMulticastLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(
+                Arrays.asList(WifiMulticastLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        createAndUploadConfig(atomTag, true);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiMulticastLock");
+
+        // 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_SHORT,
+                atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
+    }
+
+    public void testWifiScan() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+
+        final int atom = Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        final int key = WifiScanStateChanged.STATE_FIELD_NUMBER;
+        final int stateOn = WifiScanStateChanged.State.ON_VALUE;
+        final int stateOff = WifiScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 500;
+        final int maxTimeDiffMillis = 60_000;
+        final boolean demandExactlyTwo = false; // Two scans are performed, so up to 4 atoms logged.
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
+                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, demandExactlyTwo);
+
+        assertTrue(data.size() >= 2);
+        assertTrue(data.size() <= 4);
+        WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
+        WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
+        assertTrue(a0.getState().getNumber() == stateOn);
+        assertTrue(a1.getState().getNumber() == stateOff);
+    }
+
+    public void testAudioState() throws Exception {
+        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 testMediaCodecActivity() throws Exception {
+        final int atomTag = Atom.MEDIA_CODEC_ACTIVITY_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(MediaCodecActivityChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(MediaCodecActivityChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        turnScreenOn();
+        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();
+
+        turnScreenOff();
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getMediaCodecActivityChanged().getState().getNumber());
+    }
+
+    public void testPictureInPictureState() throws Exception {
+        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);
+
+        turnScreenOn();
+        createAndUploadConfig(atomTag, false);
+
+        runActivity("VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        turnScreenOff();
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getPictureInPictureStateChanged().getState().getNumber());
+    }
+
+    public void testOverlayState() throws Exception {
+        final int atomTag = Atom.OVERLAY_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> entered = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.ENTERED_VALUE));
+        Set<Integer> exited = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.EXITED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(entered, exited);
+
+        createAndUploadConfig(atomTag, false);
+
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_application_overlay",
+                3_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        // The overlay box should appear about 2sec after the app start, we wait 3sec in total
+        // before force-stop it. So the duration is about 1 sec
+        assertStatesOccurred(stateSet, data, 1_000,
+                atom -> atom.getOverlayStateChanged().getState().getNumber());
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
new file mode 100644
index 0000000..1969e5b
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.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.cts.statsd.metric;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.DurationBucketInfo;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.List;
+
+public class DurationMetricsTests extends DeviceAtomTestCase {
+
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
+
+    public void testDurationMetric() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcher =
+            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcher =
+            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+
+        StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig();
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+
+        // Add Predicate's.
+        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
+                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
+                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
+                .build();
+        Predicate predicate = Predicate.newBuilder()
+                                  .setId(MetricsUtils.StringToId("Predicate"))
+                                  .setSimplePredicate(simplePredicate)
+                                  .build();
+        builder.addPredicate(predicate);
+
+        // Add DurationMetric.
+        builder.addDurationMetric(
+            StatsdConfigProto.DurationMetric.newBuilder()
+                .setId(MetricsUtils.DURATION_METRIC_ID)
+                .setWhat(predicate.getId())
+                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
+                .setBucket(StatsdConfigProto.TimeUnit.CTS));
+
+        // Upload config.
+        uploadConfig(builder);
+
+        // Create AppBreadcrumbReported Start/Stop events.
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(2000);
+        doAppBreadcrumbReportedStop(1);
+
+        // Wait for the metrics to propagate to statsd.
+        Thread.sleep(2000);
+
+        StatsLogReport metricReport = getStatsLogReport();
+        assertEquals(MetricsUtils.DURATION_METRIC_ID, metricReport.getMetricId());
+        assertTrue(metricReport.hasDurationMetrics());
+        StatsLogReport.DurationMetricDataWrapper durationData
+                = metricReport.getDurationMetrics();
+        assertTrue(durationData.getDataCount() == 1);
+        assertTrue(durationData.getData(0).getBucketInfo(0).getDurationNanos() > 0);
+        assertTrue(durationData.getData(0).getBucketInfo(0).getDurationNanos() < 1e10);
+    }
+
+    public void testDurationMetricWithDimension() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcherA =
+            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcherA =
+            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+        AtomMatcher startAtomMatcherB =
+            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+        AtomMatcher stopAtomMatcherB =
+            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
+
+        StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig();
+        builder.addAtomMatcher(startAtomMatcherA);
+        builder.addAtomMatcher(stopAtomMatcherA);
+        builder.addAtomMatcher(startAtomMatcherB);
+        builder.addAtomMatcher(stopAtomMatcherB);
+
+        // Add Predicate's.
+        SimplePredicate simplePredicateA = SimplePredicate.newBuilder()
+                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
+                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
+                .build();
+        Predicate predicateA = Predicate.newBuilder()
+                                   .setId(MetricsUtils.StringToId("Predicate_A"))
+                                   .setSimplePredicate(simplePredicateA)
+                                   .build();
+        builder.addPredicate(predicateA);
+
+        FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
+                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER);
+        dimensionsBuilder.addChild(FieldMatcher.newBuilder()
+                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                .setPosition(Position.FIRST)
+                .addChild(FieldMatcher.newBuilder().setField(
+                        AppBreadcrumbReported.LABEL_FIELD_NUMBER)));
+        Predicate predicateB =
+            Predicate.newBuilder()
+                .setId(MetricsUtils.StringToId("Predicate_B"))
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
+                                        .setDimensions(dimensionsBuilder.build())
+                                        .build())
+                .build();
+        builder.addPredicate(predicateB);
+
+        // Add DurationMetric.
+        builder.addDurationMetric(
+            StatsdConfigProto.DurationMetric.newBuilder()
+                .setId(MetricsUtils.DURATION_METRIC_ID)
+                .setWhat(predicateB.getId())
+                .setCondition(predicateA.getId())
+                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .setDimensionsInWhat(
+                    FieldMatcher.newBuilder()
+                        .setField(Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder()
+                                      .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                      .setPosition(Position.FIRST)
+                                      .addChild(FieldMatcher.newBuilder().setField(
+                                          AppBreadcrumbReported.LABEL_FIELD_NUMBER)))));
+
+        // Upload config.
+        uploadConfig(builder);
+
+        // Trigger events.
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(2000);
+        doAppBreadcrumbReportedStart(2);
+        Thread.sleep(2000);
+        doAppBreadcrumbReportedStop(1);
+        Thread.sleep(2000);
+        doAppBreadcrumbReportedStop(2);
+
+        // Wait for the metrics to propagate to statsd.
+        Thread.sleep(2000);
+
+        StatsLogReport metricReport = getStatsLogReport();
+        assertEquals(MetricsUtils.DURATION_METRIC_ID, metricReport.getMetricId());
+        assertTrue(metricReport.hasDurationMetrics());
+        StatsLogReport.DurationMetricDataWrapper durationData
+                = metricReport.getDurationMetrics();
+        assertTrue(durationData.getDataCount() == 1);
+        assertTrue(durationData.getData(0).getBucketInfoList().size() > 1);
+        for (DurationBucketInfo bucketInfo : durationData.getData(0).getBucketInfoList()) {
+            assertTrue(bucketInfo.getDurationNanos() > 0);
+            assertTrue(bucketInfo.getDurationNanos() < 1e10);
+        }
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
new file mode 100644
index 0000000..92b840e
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.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 android.cts.statsd.metric;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+
+public class GaugeMetricsTests extends DeviceAtomTestCase {
+
+  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+
+  public void testGaugeMetric() throws Exception {
+    // Add AtomMatcher's.
+    AtomMatcher startAtomMatcher =
+        MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+    AtomMatcher stopAtomMatcher =
+        MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+    AtomMatcher atomMatcher =
+        MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+
+    StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig();
+    builder.addAtomMatcher(startAtomMatcher);
+    builder.addAtomMatcher(stopAtomMatcher);
+    builder.addAtomMatcher(atomMatcher);
+
+    // Add Predicate's.
+    SimplePredicate simplePredicate = SimplePredicate.newBuilder()
+                                          .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
+                                          .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
+                                          .build();
+    Predicate predicate = Predicate.newBuilder()
+                              .setId(MetricsUtils.StringToId("Predicate"))
+                              .setSimplePredicate(simplePredicate)
+                              .build();
+    builder.addPredicate(predicate);
+
+    // Add GaugeMetric.
+    FieldMatcher fieldMatcher =
+        FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
+    builder.addGaugeMetric(
+        StatsdConfigProto.GaugeMetric.newBuilder()
+            .setId(MetricsUtils.GAUGE_METRIC_ID)
+            .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+            .setCondition(predicate.getId())
+            .setGaugeFieldsFilter(
+                FieldFilter.newBuilder().setIncludeAll(false).setFields(fieldMatcher).build())
+            .setDimensionsInWhat(
+                FieldMatcher.newBuilder()
+                    .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                    .addChild(FieldMatcher.newBuilder()
+                                  .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                  .build())
+                    .build())
+            .setBucket(StatsdConfigProto.TimeUnit.CTS)
+            .build());
+
+    // Upload config.
+    uploadConfig(builder);
+
+    // Create AppBreadcrumbReported Start/Stop events.
+    doAppBreadcrumbReportedStart(0);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStart(1);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStart(2);
+    Thread.sleep(2000);
+    doAppBreadcrumbReportedStop(2);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStop(0);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStop(1);
+    doAppBreadcrumbReportedStart(2);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStart(1);
+    Thread.sleep(2000);
+    doAppBreadcrumbReportedStop(2);
+    Thread.sleep(10);
+    doAppBreadcrumbReportedStop(1);
+
+    // Wait for the metrics to propagate to statsd.
+    Thread.sleep(2000);
+
+    StatsLogReport metricReport = getStatsLogReport();
+    assertEquals(MetricsUtils.GAUGE_METRIC_ID, metricReport.getMetricId());
+    assertTrue(metricReport.hasGaugeMetrics());
+    StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
+    assertEquals(gaugeData.getDataCount(), 1);
+
+    int bucketCount = gaugeData.getData(0).getBucketInfoCount();
+    GaugeMetricData data = gaugeData.getData(0);
+    assertTrue(bucketCount > 2);
+    assertTrue(data.getBucketInfo(0).hasStartBucketNanos());
+    assertTrue(data.getBucketInfo(0).hasEndBucketNanos());
+    assertEquals(data.getBucketInfo(0).getAtomCount(), 1);
+    assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel(), 0);
+    assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState(),
+        AppBreadcrumbReported.State.START);
+
+    assertTrue(data.getBucketInfo(1).hasStartBucketNanos());
+    assertTrue(data.getBucketInfo(1).hasEndBucketNanos());
+    assertEquals(data.getBucketInfo(1).getAtomCount(), 1);
+
+    assertTrue(data.getBucketInfo(bucketCount - 1).hasStartBucketNanos());
+    assertTrue(data.getBucketInfo(bucketCount - 1).hasEndBucketNanos());
+    assertEquals(data.getBucketInfo(bucketCount - 1).getAtomCount(), 1);
+    assertEquals(
+        data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getLabel(), 2);
+    assertEquals(
+        data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getState(),
+        AppBreadcrumbReported.State.STOP);
+  }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsTestCase.java
new file mode 100644
index 0000000..dcfe713
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsTestCase.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.metric;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.List;
+
+public class MetricsTestCase extends DeviceAtomTestCase {
+
+    private static abstract class TestCase {
+        private final StatsdConfigProto.StatsdConfig mConfig;
+        private final String mMethod;
+        private final long mWaitTimeMs;
+
+        public TestCase(StatsdConfigProto.StatsdConfig config, String methodName, long waitTimeMs) {
+            mConfig = config;
+            mMethod = methodName;
+            mWaitTimeMs = waitTimeMs;
+        }
+
+        public StatsdConfigProto.StatsdConfig getConfig() {
+            return mConfig;
+        }
+
+        public String getMethod() {
+            return mMethod;
+        }
+
+        public long getWaitTimeMs() {
+            return mWaitTimeMs;
+        }
+
+        /**
+         * Results validation.
+         */
+        abstract void verifyReport(StatsLog.ConfigMetricsReport report);
+    }
+
+    private void testMetrics(TestCase test) throws DeviceNotAvailableException {
+        try {
+            uploadConfig(test.getConfig());
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+                    ".MetricsTests", test.getMethod());
+            Thread.sleep(test.getWaitTimeMs());
+            test.verifyReport(getReportCommon());
+        } catch (Exception e) {
+            fail(e.toString());
+        } finally {
+            try {
+                removeConfig(CONFIG_ID);
+            } catch (Exception e) {
+                // ignored.
+            }
+        }
+
+    }
+
+    public void testSimpleEventCountMetric() throws DeviceNotAvailableException {
+        long matcherId = 1;
+        StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig();
+        builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                .setId(MetricsUtils.COUNT_METRIC_ID)
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .setWhat(matcherId))
+                .addAtomMatcher(MetricsUtils.getAtomMatcher(
+                        Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER).setId(matcherId).build());
+
+        TestCase testCase = new TestCase(builder.build(), "testSimpleEventCountMetric", 1000) {
+            @Override
+            void verifyReport(StatsLog.ConfigMetricsReport report) {
+                assertEquals(1, report.getMetricsCount());
+                com.android.os.StatsLog.StatsLogReport metricReport = report.getMetrics(0);
+                assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
+                assertTrue(metricReport.hasCountMetrics());
+
+                com.android.os.StatsLog.StatsLogReport.CountMetricDataWrapper countData
+                        = metricReport.getCountMetrics();
+
+                assertTrue(countData.getDataCount() > 0);
+                assertEquals(2, countData.getData(0).getBucketInfo(0).getCount());
+            }
+        };
+        testMetrics(testCase);
+    }
+
+    public void testEventCountWithCondition() throws DeviceNotAvailableException {
+        long startMatcherId = 1;
+        long endMatcherId = 2;
+        long whatMatcherId = 3;
+        long conditionId = 4;
+
+        StatsdConfigProto.AtomMatcher plugMatcher =
+                MetricsUtils.getAtomMatcher(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
+                        .setId(whatMatcherId).build();
+
+        StatsdConfigProto.AtomMatcher predicateStartMatcher =
+                StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(startMatcherId).setSimpleAtomMatcher(
+                        StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                                .addFieldValueMatcher(
+                                        StatsdConfigProto.FieldValueMatcher.newBuilder()
+                                                .setField(1).setEqInt(1))).build();
+
+        StatsdConfigProto.AtomMatcher predicateEndMatcher =
+                StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(endMatcherId).setSimpleAtomMatcher(
+                        StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                                .addFieldValueMatcher(
+                                        StatsdConfigProto.FieldValueMatcher.newBuilder()
+                                                .setField(1).setEqInt(2))).build();
+
+        StatsdConfigProto.SimplePredicate predicate = StatsdConfigProto.SimplePredicate.newBuilder()
+                .setStart(startMatcherId)
+                .setStop(endMatcherId)
+                .setCountNesting(false)
+                .build();
+
+        StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
+                .setSimplePredicate(predicate)
+                .setId(conditionId)
+                .build();
+
+        StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig()
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                        .setWhat(whatMatcherId)
+                        .setCondition(conditionId))
+                .addAtomMatcher(plugMatcher)
+                .addAtomMatcher(predicateStartMatcher)
+                .addAtomMatcher(predicateEndMatcher)
+                .addPredicate(p);
+
+        TestCase testCase = new TestCase(builder.build(),
+                "testEventCountWithCondition", 1000) {
+            @Override
+            void verifyReport(StatsLog.ConfigMetricsReport report) {
+                assertEquals(1, report.getMetricsCount());
+                com.android.os.StatsLog.StatsLogReport metricReport = report.getMetrics(0);
+                assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
+                assertTrue(metricReport.hasCountMetrics());
+
+                com.android.os.StatsLog.StatsLogReport.CountMetricDataWrapper countData
+                        = metricReport.getCountMetrics();
+
+                assertTrue(countData.getDataCount() > 0);
+                assertEquals(1, countData.getData(0).getBucketInfo(0).getCount());
+            }
+        };
+        testMetrics(testCase);
+    }
+
+    private com.android.os.StatsLog.ConfigMetricsReport getReportCommon() throws Exception {
+        com.android.os.StatsLog.ConfigMetricsReportList report = getReportList();
+        List<com.android.os.StatsLog.ConfigMetricsReport> list = report.getReportsList();
+        assertTrue(list.size() > 0);
+        com.android.os.StatsLog.ConfigMetricsReport report1 = list.get(0);
+        assertTrue(report1.hasUidMap());
+        // One metric per test.
+        return list.get(0);
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
new file mode 100644
index 0000000..c5ef40c
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.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.cts.statsd.metric;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+
+public class MetricsUtils {
+    public static final long COUNT_METRIC_ID = 3333;
+    public static final long DURATION_METRIC_ID = 4444;
+    public static final long GAUGE_METRIC_ID = 5555;
+    public static final long VALUE_METRIC_ID = 6666;
+
+    public static StatsdConfigProto.StatsdConfig.Builder getEmptyConfig() {
+        StatsdConfigProto.StatsdConfig.Builder builder =
+                StatsdConfigProto.StatsdConfig.newBuilder();
+        // Only accept the log events from this cts and android system (1000) to avoid noise.
+        builder.addAllowedLogSource("com.android.server.cts.device.statsd");
+        builder.addAllowedLogSource("android");
+        return builder;
+    }
+
+    public static AtomMatcher.Builder getAtomMatcher(int atomId) {
+        AtomMatcher.Builder builder = AtomMatcher.newBuilder();
+        builder.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomId).build());
+        return builder;
+    }
+
+    public static AtomMatcher startAtomMatcher(int id) {
+      return AtomMatcher.newBuilder()
+          .setId(id)
+          .setSimpleAtomMatcher(
+              SimpleAtomMatcher.newBuilder()
+                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                            .setEqInt(AppBreadcrumbReported.State.START.ordinal())))
+          .build();
+    }
+
+    public static AtomMatcher stopAtomMatcher(int id) {
+      return AtomMatcher.newBuilder()
+          .setId(id)
+          .setSimpleAtomMatcher(
+              SimpleAtomMatcher.newBuilder()
+                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                            .setEqInt(AppBreadcrumbReported.State.STOP.ordinal())))
+          .build();
+    }
+
+    public static AtomMatcher simpleAtomMatcher(int id) {
+      return AtomMatcher.newBuilder()
+          .setId(id)
+          .setSimpleAtomMatcher(
+              SimpleAtomMatcher.newBuilder().setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
+          .build();
+    }
+
+    public static long StringToId(String str) {
+      return str.hashCode();
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
new file mode 100644
index 0000000..44b3a48
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.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.cts.statsd.metric;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ValueBucketInfo;
+import com.android.os.StatsLog.ValueMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+
+public class ValueMetricsTests extends DeviceAtomTestCase {
+  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+
+  public void testValueMetric() throws Exception {
+    // Add AtomMatcher's.
+    AtomMatcher startAtomMatcher =
+        MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+    AtomMatcher stopAtomMatcher =
+        MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+    AtomMatcher atomMatcher =
+        MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+
+    StatsdConfigProto.StatsdConfig.Builder builder = MetricsUtils.getEmptyConfig();
+    builder.addAtomMatcher(startAtomMatcher);
+    builder.addAtomMatcher(stopAtomMatcher);
+    builder.addAtomMatcher(atomMatcher);
+
+    // Add ValueMetric.
+    FieldMatcher fieldMatcher =
+        FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
+    builder.addValueMetric(
+        ValueMetric.newBuilder()
+            .setId(MetricsUtils.VALUE_METRIC_ID)
+            .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+            .setBucket(StatsdConfigProto.TimeUnit.CTS)
+            .setValueField(FieldMatcher.newBuilder()
+                               .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                               .addChild(FieldMatcher.newBuilder().setField(
+                                   AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
+            .setDimensionsInWhat(FieldMatcher.newBuilder()
+                                     .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                                     .build())
+            .build());
+
+    // Upload config.
+    uploadConfig(builder);
+
+    // Create AppBreadcrumbReported Start/Stop events.
+    doAppBreadcrumbReportedStart(1);
+    Thread.sleep(1000);
+    doAppBreadcrumbReportedStop(1);
+    doAppBreadcrumbReportedStart(3);
+    doAppBreadcrumbReportedStop(3);
+
+    // Wait for the metrics to propagate to statsd.
+    Thread.sleep(2000);
+
+    StatsLogReport metricReport = getStatsLogReport();
+    assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
+    assertTrue(metricReport.hasValueMetrics());
+    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+    assertEquals(valueData.getDataCount(), 1);
+
+    int bucketCount = valueData.getData(0).getBucketInfoCount();
+    assertTrue(bucketCount > 1);
+    ValueMetricData data = valueData.getData(0);
+    int totalValue = 0;
+    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+      assertTrue(bucketInfo.hasStartBucketElapsedNanos());
+      assertTrue(bucketInfo.hasEndBucketElapsedNanos());
+      totalValue += (int) bucketInfo.getValue();
+    }
+    assertEquals(totalValue, 8);
+  }
+}
diff --git a/hostsidetests/sustainedperf/AndroidTest.xml b/hostsidetests/sustainedperf/AndroidTest.xml
index e7cbee8..dd49674 100644
--- a/hostsidetests/sustainedperf/AndroidTest.xml
+++ b/hostsidetests/sustainedperf/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Sustained Performance host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index 18e44ee..e1ae6e9 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS System UI host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="sysui" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/theme/AndroidTest.xml b/hostsidetests/theme/AndroidTest.xml
index fb5d6bb..beb9218 100644
--- a/hostsidetests/theme/AndroidTest.xml
+++ b/hostsidetests/theme/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Theme host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
index 4f93e5a..4bf32cd 100644
--- a/hostsidetests/theme/README
+++ b/hostsidetests/theme/README
@@ -67,6 +67,22 @@
 
      ./cts/hostsidetests/theme/generate_images.py
 
+There is an option to build locally an Android system image and use an emulator that is stored in
+Android source tree under "prebuilts/android-emulator/linux-x86_64/emulator". This option does not
+require a SDK and can be used to generate images with locally modified source code: for example
+right before making a test breaking change.
+
+  1. From the console, set up your build environment for sdk_phone_x86_64 and build Android and CTS:
+
+     lunch sdk_phone_x86_64-userdebug && make -j32 && make cts -j32
+
+  2. Use the reference image script to generate the reference images. The script
+     will automatically start the emulator in the required configurations and
+     install the resulting reference images in assets/<platform>/<dpi>.zip,
+     overwriting any existing images.
+
+     ./cts/hostsidetests/theme/generate_images.py local
+
 A complete collection of reference images for a given API revision must include
 a set for each possible DPI bucket (tvdpi, xxhdpi, etc.) that may be tested.
 
diff --git a/hostsidetests/theme/assets/P b/hostsidetests/theme/assets/P
deleted file mode 120000
index a5c750f..0000000
--- a/hostsidetests/theme/assets/P
+++ /dev/null
@@ -1 +0,0 @@
-27
\ No newline at end of file
diff --git a/hostsidetests/theme/assets/P/260dpi.zip b/hostsidetests/theme/assets/P/260dpi.zip
new file mode 100644
index 0000000..5b67841
--- /dev/null
+++ b/hostsidetests/theme/assets/P/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/280dpi.zip b/hostsidetests/theme/assets/P/280dpi.zip
new file mode 100644
index 0000000..30d6436
--- /dev/null
+++ b/hostsidetests/theme/assets/P/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/300dpi.zip b/hostsidetests/theme/assets/P/300dpi.zip
new file mode 100644
index 0000000..9b84c4a
--- /dev/null
+++ b/hostsidetests/theme/assets/P/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/340dpi.zip b/hostsidetests/theme/assets/P/340dpi.zip
new file mode 100644
index 0000000..65cb0b2
--- /dev/null
+++ b/hostsidetests/theme/assets/P/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/360dpi.zip b/hostsidetests/theme/assets/P/360dpi.zip
new file mode 100644
index 0000000..f05b4a6
--- /dev/null
+++ b/hostsidetests/theme/assets/P/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/400dpi.zip b/hostsidetests/theme/assets/P/400dpi.zip
new file mode 100644
index 0000000..7004b79
--- /dev/null
+++ b/hostsidetests/theme/assets/P/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/420dpi.zip b/hostsidetests/theme/assets/P/420dpi.zip
new file mode 100644
index 0000000..6f90816
--- /dev/null
+++ b/hostsidetests/theme/assets/P/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/560dpi.zip b/hostsidetests/theme/assets/P/560dpi.zip
new file mode 100644
index 0000000..3cb9693
--- /dev/null
+++ b/hostsidetests/theme/assets/P/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/hdpi.zip b/hostsidetests/theme/assets/P/hdpi.zip
new file mode 100644
index 0000000..2d468f2
--- /dev/null
+++ b/hostsidetests/theme/assets/P/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/ldpi.zip b/hostsidetests/theme/assets/P/ldpi.zip
new file mode 100644
index 0000000..aa249df
--- /dev/null
+++ b/hostsidetests/theme/assets/P/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/mdpi.zip b/hostsidetests/theme/assets/P/mdpi.zip
new file mode 100644
index 0000000..1b39785
--- /dev/null
+++ b/hostsidetests/theme/assets/P/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/tvdpi.zip b/hostsidetests/theme/assets/P/tvdpi.zip
new file mode 100644
index 0000000..1503250
--- /dev/null
+++ b/hostsidetests/theme/assets/P/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xhdpi.zip b/hostsidetests/theme/assets/P/xhdpi.zip
new file mode 100644
index 0000000..a2820d0
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xxhdpi.zip b/hostsidetests/theme/assets/P/xxhdpi.zip
new file mode 100644
index 0000000..14418ff
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xxxhdpi.zip b/hostsidetests/theme/assets/P/xxxhdpi.zip
new file mode 100644
index 0000000..2ae33a0
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/avd.py b/hostsidetests/theme/avd.py
index 4c444a2..2a43129 100644
--- a/hostsidetests/theme/avd.py
+++ b/hostsidetests/theme/avd.py
@@ -46,8 +46,12 @@
         port_adb = find_free_port()
         port_tty = find_free_port()
         # -no-window might be useful here
-        emu_cmd = "%s -avd %s %s-ports %d,%d" \
-                  % (self._emu_path, self._name, self._opts, port_adb, port_tty)
+        if self._name == "local":
+            emu_cmd = "emulator %s-ports %d,%d -gpu on -wipe-data" \
+                      % (self._opts, port_adb, port_tty)
+        else:
+            emu_cmd = "%s -avd %s %s-ports %d,%d" \
+                      % (self._emu_path, self._name, self._opts, port_adb, port_tty)
         print(emu_cmd)
 
         emu_proc = subprocess.Popen(emu_cmd.split(" "), bufsize=-1, stdout=subprocess.PIPE,
diff --git a/hostsidetests/theme/generate_images.py b/hostsidetests/theme/generate_images.py
index 8b70f00..5dcc76f 100755
--- a/hostsidetests/theme/generate_images.py
+++ b/hostsidetests/theme/generate_images.py
@@ -50,7 +50,7 @@
 
 
 class ParallelExecutor(threading.Thread):
-    def __init__(self, tasks, q):
+    def __init__(self, tasks, setup, q):
         threading.Thread.__init__(self)
         self._q = q
         self._tasks = tasks
@@ -60,7 +60,7 @@
     def run(self):
         try:
             while True:
-                config = q.get(block=True, timeout=2)
+                config = self._q.get(block=True, timeout=2)
                 for t in self._tasks:
                     try:
                         if t(self._setup, config):
@@ -70,7 +70,7 @@
                     except:
                         print("Failed to execute thread:", sys.exc_info()[0])
                         traceback.print_exc()
-                q.task_done()
+                self._q.task_done()
         except KeyboardInterrupt:
             raise
         except Empty:
@@ -86,7 +86,7 @@
     result = 0
     threads = []
     for i in range(num_threads):
-        t = ParallelExecutor(tasks, q)
+        t = ParallelExecutor(tasks, setup, q)
         t.start()
         threads.append(t)
     for t in threads:
@@ -181,7 +181,10 @@
 
 
 def start_emulator(name, density):
-    emu_path = get_emulator_path()
+    if name == "local":
+        emu_path = ""
+    else:
+        emu_path = get_emulator_path()
 
     # Start emulator for 560dpi, normal screen size.
     test_avd = AVD(name, emu_path)
@@ -226,18 +229,21 @@
         tasks = [do_capture]
         setup = (theme_apk, out_path)
 
-        devices = enumerate_android_devices('emulator')
+        devices = enumerate_android_devices()
 
-        device_queue = Queue()
-        for device in devices:
-            device_queue.put(device)
+        if len(devices) > 0:
+            device_queue = Queue()
+            for device in devices:
+                device_queue.put(device)
 
-        result = execute_parallel(tasks, setup, device_queue, len(devices))
+            result = execute_parallel(tasks, setup, device_queue, len(devices))
 
-        if result > 0:
-            print('Generated reference images for %(count)d devices' % {"count": result})
+            if result > 0:
+                print('Generated reference images for %(count)d devices' % {"count": result})
+            else:
+                print('Failed to generate reference images')
         else:
-            print('Failed to generate reference images')
+            print('No devices found')
 
 
 if __name__ == '__main__':
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 5c82e2a..017febc 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -93,7 +93,7 @@
         mDevice = getDevice();
         mHardwareType = mDevice.executeShellCommand(HARDWARE_TYPE_CMD).trim();
 
-        final String density = getDensityBucketForDevice(mDevice, mHardwareType);
+        final String density = getDensityBucketForDevice(mDevice);
         final String referenceZipAssetPath = String.format("/%s.zip", density);
         mReferences = extractReferenceImages(referenceZipAssetPath);
 
@@ -231,11 +231,7 @@
         return receiver.getOutput().contains("OK ");
     }
 
-    private static String getDensityBucketForDevice(ITestDevice device, String hardwareType) {
-        if (hardwareType.contains("android.hardware.type.television")) {
-            // references images for tv are under bucket "tvdpi".
-            return "tvdpi";
-        }
+    private static String getDensityBucketForDevice(ITestDevice device) {
         final int density;
         try {
             density = getDensityForDevice(device);
@@ -250,6 +246,9 @@
             case 160:
                 bucket = "mdpi";
                 break;
+            case 213:
+                bucket = "tvdpi";
+                break;
             case 240:
                 bucket = "hdpi";
                 break;
diff --git a/hostsidetests/trustedvoice/AndroidTest.xml b/hostsidetests/trustedvoice/AndroidTest.xml
index 3e23236..2767992 100644
--- a/hostsidetests/trustedvoice/AndroidTest.xml
+++ b/hostsidetests/trustedvoice/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Trustedvoice 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.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/tv/AndroidTest.xml b/hostsidetests/tv/AndroidTest.xml
index d9edad0..07c38de 100644
--- a/hostsidetests/tv/AndroidTest.xml
+++ b/hostsidetests/tv/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS tv host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="tv" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHostsideTvTests.jar" />
diff --git a/hostsidetests/tv/app2/Android.mk b/hostsidetests/tv/app2/Android.mk
index 76ae765..3120510 100644
--- a/hostsidetests/tv/app2/Android.mk
+++ b/hostsidetests/tv/app2/Android.mk
@@ -29,7 +29,7 @@
 
 LOCAL_PACKAGE_NAME := CtsHostsideTvInputMonitor
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
diff --git a/hostsidetests/tzdata/AndroidTest.xml b/hostsidetests/tzdata/AndroidTest.xml
index 1a2716e..39fc109 100644
--- a/hostsidetests/tzdata/AndroidTest.xml
+++ b/hostsidetests/tzdata/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS tzdatacheck host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHostTzDataTests.jar" />
diff --git a/hostsidetests/ui/AndroidTest.xml b/hostsidetests/ui/AndroidTest.xml
index 9aaf7a4..88c515a 100644
--- a/hostsidetests/ui/AndroidTest.xml
+++ b/hostsidetests/ui/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsUiHostTestCases.jar" />
diff --git a/hostsidetests/ui/appA/Android.mk b/hostsidetests/ui/appA/Android.mk
index 7da5606..3abc7a0 100644
--- a/hostsidetests/ui/appA/Android.mk
+++ b/hostsidetests/ui/appA/Android.mk
@@ -20,7 +20,10 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/ui/appA/AndroidManifest.xml b/hostsidetests/ui/appA/AndroidManifest.xml
index f336abd..dd2a901 100644
--- a/hostsidetests/ui/appA/AndroidManifest.xml
+++ b/hostsidetests/ui/appA/AndroidManifest.xml
@@ -21,6 +21,8 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity
             android:name=".AppAActivity"
             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
@@ -33,4 +35,4 @@
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/ui/appB/Android.mk b/hostsidetests/ui/appB/Android.mk
index 07e2858..4501cf3 100644
--- a/hostsidetests/ui/appB/Android.mk
+++ b/hostsidetests/ui/appB/Android.mk
@@ -20,7 +20,10 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/ui/appB/AndroidManifest.xml b/hostsidetests/ui/appB/AndroidManifest.xml
index aaf7a2c..9d99377 100644
--- a/hostsidetests/ui/appB/AndroidManifest.xml
+++ b/hostsidetests/ui/appB/AndroidManifest.xml
@@ -20,6 +20,8 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity
             android:name=".AppBActivity"
             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
diff --git a/hostsidetests/ui/control/Android.mk b/hostsidetests/ui/control/Android.mk
index 688ace7..3b4f8da 100644
--- a/hostsidetests/ui/control/Android.mk
+++ b/hostsidetests/ui/control/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsDeviceTaskSwitchingControl
diff --git a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
index 1b49eca..03a581d 100644
--- a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
+++ b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
@@ -25,7 +25,6 @@
 import com.android.compatibility.common.util.Stat;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
diff --git a/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java b/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java
index 9cfd957..814d4e1 100644
--- a/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java
+++ b/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java
@@ -20,11 +20,11 @@
 import com.android.compatibility.common.util.MetricsStore;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -111,7 +111,7 @@
 
     public class LocalListener extends CollectingTestListener {
         @Override
-        public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+        public void testEnded(TestDescription test, Map<String, String> testMetrics) {
             // necessary as testMetrics passed from CollectingTestListerner is empty
             if (testMetrics.containsKey(RESULT_KEY)) {
                 try {
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index 6dd8e6f..7078705 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -14,13 +14,15 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAppUsageTestApp.apk" />
+        <option name="test-file-name" value="CtsAppUsageTestAppToo.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsAppUsageHostTestCases.jar" />
-        <option name="runtime-hint" value="8m" />
+        <option name="runtime-hint" value="2m" />
     </test>
 </configuration>
diff --git a/hostsidetests/usage/app/Android.mk b/hostsidetests/usage/app/Android.mk
index 59626d6..3d51230 100644
--- a/hostsidetests/usage/app/Android.mk
+++ b/hostsidetests/usage/app/Android.mk
@@ -29,3 +29,23 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# Build a second one similar to the first
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAppUsageTestAppToo
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.app.usage.apptoo
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
index 7d5ce48..303c26f 100755
--- a/hostsidetests/usage/app/AndroidManifest.xml
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -19,7 +19,7 @@
     package="android.app.usage.app">
 
     <application>
-        <activity android:name=".TestActivity">
+        <activity android:name="android.app.usage.app.TestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
index 3cd7bda..1243cdd 100644
--- a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
+++ b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
@@ -16,18 +16,31 @@
 
 package android.app.usage.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class AppIdleHostTest extends DeviceTestCase {
     private static final String SETTINGS_APP_IDLE_CONSTANTS = "app_idle_constants";
 
     private static final String TEST_APP_PACKAGE = "android.app.usage.app";
     private static final String TEST_APP_CLASS = "TestActivity";
+    private static final String TEST_APP_PACKAGE2 = "android.app.usage.apptoo";
 
     private static final long ACTIVITY_LAUNCH_WAIT_MILLIS = 500;
 
+    private static final int SB_ACTIVE = 10;
+    private static final int SB_WORKING_SET = 20;
+    private static final int SB_FREQUENT = 30;
+    private static final int SB_RARE = 40;
+    private static final int SB_NEVER = 50;
+
     /**
      * A reference to the device under test.
      */
@@ -104,6 +117,55 @@
         }
     }
 
+    private void setAppStandbyBucket(String packageName, int bucket) throws Exception {
+        mDevice.executeShellCommand(
+                String.format("am set-standby-bucket %s %s", packageName, bucket));
+    }
+
+    private int getAppStandbyBucket(String packageName) throws Exception {
+        String bucketString = mDevice.executeShellCommand(
+                String.format("am get-standby-bucket %s", packageName));
+        try {
+            return Integer.parseInt(bucketString.trim());
+        } catch (NumberFormatException nfe) {
+        }
+        return -1;
+    }
+
+    public void testSetAppStandbyBucket() throws Exception {
+        // Set to ACTIVE
+        setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE);
+        assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+        // set to WORKING_SET
+        setAppStandbyBucket(TEST_APP_PACKAGE, 20);
+        assertEquals(20, getAppStandbyBucket(TEST_APP_PACKAGE));
+    }
+
+    public void testSetAppStandbyBuckets() throws Exception {
+        // Set multiple packages states
+        String command = String.format("am set-standby-bucket %s %d %s %d",
+                TEST_APP_PACKAGE, SB_FREQUENT, TEST_APP_PACKAGE2, SB_WORKING_SET);
+        mDevice.executeShellCommand(command);
+        assertEquals(SB_FREQUENT, getAppStandbyBucket(TEST_APP_PACKAGE));
+        assertEquals(SB_WORKING_SET, getAppStandbyBucket(TEST_APP_PACKAGE2));
+    }
+
+    public void testCantSetOwnStandbyBucket() throws Exception {
+        setAppStandbyBucket("com.android.shell", 40);
+        assertNotEquals(40, getAppStandbyBucket("com.android.shell"));
+    }
+
+    public void testOutOfBoundsStandbyBucket() throws Exception {
+        setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE);
+        assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+        // Try lower than min
+        setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE - 1);
+        assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+        // Try higher than max
+        setAppStandbyBucket(TEST_APP_PACKAGE, 50 + 1);
+        assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+    }
+
     private static void sleepUninterrupted(long timeMillis) {
         boolean interrupted;
         do {
diff --git a/hostsidetests/usb/AndroidTest.xml b/hostsidetests/usb/AndroidTest.xml
index cfdcaea..bad4ff9 100644
--- a/hostsidetests/usb/AndroidTest.xml
+++ b/hostsidetests/usb/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsUsbTests.jar" />
diff --git a/hostsidetests/usb/SerialTestApp/Android.mk b/hostsidetests/usb/SerialTestApp/Android.mk
index 2ddf30f..bbd55f6 100644
--- a/hostsidetests/usb/SerialTestApp/Android.mk
+++ b/hostsidetests/usb/SerialTestApp/Android.mk
@@ -20,7 +20,9 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
index a75dd75..7c627ee 100644
--- a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
+++ b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
@@ -17,6 +17,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.usb.serialtest">
 
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
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 a2b0c72..f47c2d2 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
@@ -28,6 +28,6 @@
     private static final String TAG = "CtsUsbSerialTest";
 
     public void testSerial() throws Exception {
-        Log.e(TAG, Build.SERIAL);
+        Log.e(TAG, Build.getSerial());
     }
 }
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 81cc265..8a686cc 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -17,11 +17,11 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -65,7 +65,7 @@
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
         File app = buildHelper.getTestFile(APK_NAME);
         String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-        mDevice.installPackage(app, false, options);
+        mDevice.installPackage(app, false, true, options);
     }
 
     @Override
diff --git a/hostsidetests/webkit/AndroidTest.xml b/hostsidetests/webkit/AndroidTest.xml
index 37d0467..414fad4 100644
--- a/hostsidetests/webkit/AndroidTest.xml
+++ b/hostsidetests/webkit/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/webkit/app/Android.mk b/hostsidetests/webkit/app/Android.mk
index 60e9f3f..fd2ee9a 100644
--- a/hostsidetests/webkit/app/Android.mk
+++ b/hostsidetests/webkit/app/Android.mk
@@ -27,6 +27,8 @@
     ctstestserver \
     ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 # When built, explicitly put it in the data partition.
 #LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
diff --git a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
index a47d653..67408fa 100644
--- a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
+++ b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
@@ -18,6 +18,9 @@
 
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.StrictMode;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
@@ -48,6 +51,7 @@
         extends ActivityInstrumentationTestCase2<WebViewStartupCtsActivity> {
 
     private static final String TAG = WebViewDeviceSideStartupTest.class.getSimpleName();
+    private static final long TEST_TIMEOUT_MS = 3000;
 
     private WebViewStartupCtsActivity mActivity;
 
@@ -199,4 +203,67 @@
         onUiThread.loadUrlAndWaitForCompletion("about:blank");
         onUiThread.loadUrlAndWaitForCompletion("");
     }
+
+    @UiThreadTest
+    public void testGetWebViewLooperOnUiThread() {
+        PackageManager pm = mActivity.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+
+        createAndCheckWebViewLooper();
+    }
+
+    /**
+     * Ensure that a WebView created on the UI thread returns that thread as its creator thread.
+     * This ensures WebView.getLooper() is not implemented as 'return Looper.myLooper();'.
+     */
+    public void testGetWebViewLooperCreatedOnUiThreadFromInstrThread() {
+        PackageManager pm = mActivity.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+
+        WebView[] webviewHolder = new WebView[1];
+        // Create the WebView on the UI thread and then ensure webview.getLooper() returns the UI
+        // thread.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                webviewHolder[0] = createAndCheckWebViewLooper();
+            }
+        });
+        assertEquals(Looper.getMainLooper(), webviewHolder[0].getLooper());
+    }
+
+    /**
+     * Ensure that a WebView created on a background thread returns that thread as its creator
+     * thread.
+     * This ensures WebView.getLooper() is not bound to the UI thread regardless of the thread it is
+     * created on..
+     */
+    public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
+            throws InterruptedException {
+        PackageManager pm = mActivity.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+
+        // Create a WebView on a background thread, check it from the UI thread
+        final WebView webviewHolder[] = new WebView[1];
+
+        // Use a HandlerThread, because such a thread owns a Looper.
+        HandlerThread backgroundThread = new HandlerThread("WebViewLooperCtsHandlerThread");
+        backgroundThread.start();
+        new Handler(backgroundThread.getLooper()).post(new Runnable() {
+            @Override
+            public void run() {
+                webviewHolder[0] = createAndCheckWebViewLooper();
+            }
+        });
+        backgroundThread.join(TEST_TIMEOUT_MS);
+        assertEquals(backgroundThread.getLooper(), webviewHolder[0].getLooper());
+    }
+
+    private WebView createAndCheckWebViewLooper() {
+        // Ensure we are running this on a thread with a Looper - otherwise there's no point.
+        assertNotNull(Looper.myLooper());
+        WebView webview = new WebView(mActivity);
+        assertEquals(Looper.myLooper(), webview.getLooper());
+        return webview;
+    }
 }
diff --git a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
index 53f4a49..d40109e 100644
--- a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
+++ b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
@@ -17,12 +17,13 @@
 
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 
-import java.util.Map;
+import java.util.Collection;
 
 public class WebViewHostSideStartupTest extends DeviceTestCase {
     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
@@ -35,26 +36,48 @@
     }
 
     public void testCookieManager() throws DeviceNotAvailableException {
-        assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
-                    "testCookieManagerBlockingUiThread"));
+        assertDeviceTestPasses("testCookieManagerBlockingUiThread");
     }
 
     public void testWebViewVersionApiOnUiThread() throws DeviceNotAvailableException {
-        assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
-                    "testGetCurrentWebViewPackageOnUiThread"));
+        assertDeviceTestPasses("testGetCurrentWebViewPackageOnUiThread");
     }
 
     public void testWebViewVersionApi() throws DeviceNotAvailableException {
-        assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
-                    "testGetCurrentWebViewPackage"));
+        assertDeviceTestPasses("testGetCurrentWebViewPackage");
     }
 
     public void testStrictMode() throws DeviceNotAvailableException {
-        assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
-                    "testStrictModeNotViolatedOnStartup"));
+        assertDeviceTestPasses("testStrictModeNotViolatedOnStartup");
     }
 
-    private boolean runDeviceTest(String packageName, String testClassName,
+    public void testGetWebViewLooperOnUiThread() throws DeviceNotAvailableException {
+        assertDeviceTestPasses("testGetWebViewLooperOnUiThread");
+    }
+
+    public void testGetWebViewLooperFromUiThread() throws DeviceNotAvailableException {
+        assertDeviceTestPasses("testGetWebViewLooperCreatedOnUiThreadFromInstrThread");
+    }
+
+    public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
+            throws DeviceNotAvailableException {
+        assertDeviceTestPasses("testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread");
+    }
+
+    private void assertDeviceTestPasses(String testMethodName) throws DeviceNotAvailableException {
+        TestRunResult testRunResult = runSingleDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG,
+                                                 DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
+                                                 testMethodName);
+        assertTrue(testRunResult.isRunComplete());
+
+        Collection<TestResult> testResults = testRunResult.getTestResults().values();
+        // We're only running a single test.
+        assertEquals(1, testResults.size());
+        TestResult testResult = testResults.toArray(new TestResult[1])[0];
+        assertTrue(testResult.getStackTrace(), TestStatus.PASSED.equals(testResult.getStatus()));
+    }
+
+    private TestRunResult runSingleDeviceTest(String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
         testClassName = packageName + "." + testClassName;
 
@@ -65,7 +88,6 @@
         CollectingTestListener listener = new CollectingTestListener();
         assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
 
-        TestRunResult runResult = listener.getCurrentRunResults();
-        return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
+        return listener.getCurrentRunResults();
     }
 }
diff --git a/libs/deviceutillegacy/Android.mk b/libs/deviceutillegacy/Android.mk
index f169ca8..8cfa6d1 100644
--- a/libs/deviceutillegacy/Android.mk
+++ b/libs/deviceutillegacy/Android.mk
@@ -18,8 +18,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
-    junit \
-    legacy-android-test
+    junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src)
@@ -30,4 +31,6 @@
 
 LOCAL_MODULE := ctsdeviceutillegacy
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
index b528417..7db9a76 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -661,15 +661,6 @@
         });
     }
 
-    public boolean savePicture(final Bundle b, final File dest) {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.savePicture(b, dest);
-            }
-        });
-    }
-
     public boolean pageUp(final boolean top) {
         return getValue(new ValueGetter<Boolean>() {
             @Override
diff --git a/libs/json/Android.mk b/libs/json/Android.mk
index 231f635..d1b2051 100644
--- a/libs/json/Android.mk
+++ b/libs/json/Android.mk
@@ -33,6 +33,7 @@
 
 LOCAL_MODULE := json
 LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/libs/vogar-expect/Android.mk b/libs/vogar-expect/Android.mk
index 2b5e074..f888d26 100644
--- a/libs/vogar-expect/Android.mk
+++ b/libs/vogar-expect/Android.mk
@@ -32,6 +32,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_MODULE := vogarexpect
 LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
 
 LOCAL_STATIC_JAVA_LIBRARIES := guava json
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/AlarmManager/Android.mk b/tests/AlarmManager/Android.mk
new file mode 100755
index 0000000..a1676ab
--- /dev/null
+++ b/tests/AlarmManager/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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 := ub-uiautomator android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, app/src)
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsAlarmManagerTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
new file mode 100644
index 0000000..f557b51
--- /dev/null
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.alarmmanager.cts" >
+
+    <application android:label="Cts Alarm Manager Test">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.alarmmanager.cts"
+                     android:label="Alarm Manager Tests"/>
+</manifest>
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
new file mode 100644
index 0000000..a8c8955
--- /dev/null
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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 Alarm Manager 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="CtsAlarmManagerTestCases.apk" />
+        <option name="test-file-name" value="AlarmTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.alarmmanager.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+
+</configuration>
diff --git a/tests/AlarmManager/app/Android.mk b/tests/AlarmManager/app/Android.mk
new file mode 100644
index 0000000..b2023e9
--- /dev/null
+++ b/tests/AlarmManager/app/Android.mk
@@ -0,0 +1,33 @@
+# 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 := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AlarmTestApp
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
new file mode 100644
index 0000000..f5b04b6
--- /dev/null
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.alarmmanager.alarmtestapp.cts">
+
+    <application>
+        <receiver android:name=".TestAlarmScheduler"
+                  android:exported="true" />
+        <receiver android:name=".TestAlarmReceiver" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
new file mode 100644
index 0000000..a083e08
--- /dev/null
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
@@ -0,0 +1,39 @@
+/*
+ * 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.alarmmanager.alarmtestapp.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestAlarmReceiver extends BroadcastReceiver{
+    private static final String TAG = TestAlarmReceiver.class.getSimpleName();
+    private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
+    public static final String ACTION_REPORT_ALARM_EXPIRED = PACKAGE_NAME + ".action.ALARM_EXPIRED";
+    public static final String EXTRA_ALARM_COUNT = PACKAGE_NAME + ".extra.ALARM_COUNT";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final int count = intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 1);
+        Log.d(TAG, "Alarm expired " + count + " times");
+        final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM_EXPIRED);
+        reportAlarmIntent.putExtra(EXTRA_ALARM_COUNT, count);
+        reportAlarmIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        context.sendBroadcast(reportAlarmIntent);
+    }
+}
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
new file mode 100644
index 0000000..35763be
--- /dev/null
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
@@ -0,0 +1,92 @@
+/*
+ * 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.alarmmanager.alarmtestapp.cts;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * This receiver is to be used to schedule alarms as part of tests in
+ * {@link android.alarmmanager.cts}
+ */
+public class TestAlarmScheduler extends BroadcastReceiver {
+    private static final String TAG = TestAlarmScheduler.class.getSimpleName();
+    private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
+
+    public static final String ACTION_SET_ALARM = PACKAGE_NAME + ".action.SET_ALARM";
+    public static final String EXTRA_TRIGGER_TIME = PACKAGE_NAME + ".extra.TRIGGER_TIME";
+    public static final String EXTRA_REPEAT_INTERVAL = PACKAGE_NAME + ".extra.REPEAT_INTERVAL";
+    public static final String EXTRA_TYPE = PACKAGE_NAME + ".extra.TYPE";
+    public static final String EXTRA_ALLOW_WHILE_IDLE = PACKAGE_NAME + ".extra.ALLOW_WHILE_IDLE";
+    public static final String ACTION_SET_ALARM_CLOCK = PACKAGE_NAME + ".action.SET_ALARM_CLOCK";
+    public static final String EXTRA_ALARM_CLOCK_INFO = PACKAGE_NAME + ".extra.ALARM_CLOCK_INFO";
+    public static final String ACTION_CANCEL_ALL_ALARMS = PACKAGE_NAME + ".action.CANCEL_ALARMS";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final AlarmManager am = context.getSystemService(AlarmManager.class);
+        final Intent receiverIntent = new Intent(context, TestAlarmReceiver.class);
+        receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final PendingIntent alarmClockSender =
+                PendingIntent.getBroadcast(context, 0, receiverIntent, 0);
+        final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1, receiverIntent, 0);
+        switch (intent.getAction()) {
+            case ACTION_SET_ALARM_CLOCK:
+                if (!intent.hasExtra(EXTRA_ALARM_CLOCK_INFO)) {
+                    Log.e(TAG, "No alarm clock supplied");
+                    break;
+                }
+                final AlarmManager.AlarmClockInfo alarmClockInfo =
+                        intent.getParcelableExtra(EXTRA_ALARM_CLOCK_INFO);
+                Log.d(TAG, "Setting alarm clock " + alarmClockInfo);
+                am.setAlarmClock(alarmClockInfo, alarmClockSender);
+                break;
+            case ACTION_SET_ALARM:
+                if (!intent.hasExtra(EXTRA_TYPE) || !intent.hasExtra(EXTRA_TRIGGER_TIME)) {
+                    Log.e(TAG, "Alarm type or trigger time not supplied");
+                    break;
+                }
+                final int type = intent.getIntExtra(EXTRA_TYPE, 0);
+                final long triggerTime = intent.getLongExtra(EXTRA_TRIGGER_TIME, 0);
+                final long interval = intent.getLongExtra(EXTRA_REPEAT_INTERVAL, 0);
+                final boolean allowWhileIdle = intent.getBooleanExtra(EXTRA_ALLOW_WHILE_IDLE,
+                        false);
+                Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+                        + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
+                if (interval > 0) {
+                    am.setRepeating(type, triggerTime, interval, alarmSender);
+                } else if (allowWhileIdle) {
+                    am.setExactAndAllowWhileIdle(type, triggerTime, alarmSender);
+                } else {
+                    am.setExact(type, triggerTime, alarmSender);
+                }
+                break;
+            case ACTION_CANCEL_ALL_ALARMS:
+                Log.d(TAG, "Cancelling all alarms");
+                am.cancel(alarmClockSender);
+                am.cancel(alarmSender);
+                break;
+            default:
+                Log.e(TAG, "Unspecified action " + intent.getAction());
+                break;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
new file mode 100644
index 0000000..d9d621b
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -0,0 +1,304 @@
+/*
+ * 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.alarmmanager.cts;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
+import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Tests that app standby imposes the appropriate restrictions on alarms
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class AppStandbyTests {
+    private static final String TAG = AppStandbyTests.class.getSimpleName();
+    private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
+    private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
+
+    private static final long DEFAULT_WAIT = 1_000;
+    private static final long POLL_INTERVAL = 200;
+
+    // Tweaked alarm manager constants to facilitate testing
+    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 15_000;
+    private static final long MIN_FUTURITY = 2_000;
+    private static final long[] APP_STANDBY_DELAYS = {0, 10_000, 20_000, 30_000, 600_000};
+    private static final String[] APP_BUCKET_TAGS = {
+            "active",
+            "working_set",
+            "frequent",
+            "rare",
+            "never"
+    };
+    private static final String[] APP_BUCKET_KEYS = {
+            "standby_active_delay",
+            "standby_working_delay",
+            "standby_frequent_delay",
+            "standby_rare_delay",
+            "standby_never_delay",
+    };
+
+    private Context mContext;
+    private ComponentName mAlarmScheduler;
+    private UiDevice mUiDevice;
+    private AtomicInteger mAlarmCount;
+    private volatile long mLastAlarmTime;
+
+    private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1));
+            mLastAlarmTime = SystemClock.elapsedRealtime();
+            Log.d(TAG, "No. of expirations: " + mAlarmCount
+                    + " elapsed: " + SystemClock.elapsedRealtime());
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
+        mAlarmCount = new AtomicInteger(0);
+        updateAlarmManagerConstants();
+        setBatteryCharging(false);
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
+        mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
+        setAppStandbyBucket("active");
+        scheduleAlarm(SystemClock.elapsedRealtime(), false, 0);
+        Thread.sleep(MIN_FUTURITY);
+        assertTrue("Alarm not sent when app in active", waitForAlarms(1));
+    }
+
+    private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
+        final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
+        setAlarmIntent.setComponent(mAlarmScheduler);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
+        mContext.sendBroadcast(setAlarmIntent);
+    }
+
+    private void testBucketDelay(int bucketIndex) throws Exception {
+        setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
+        final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        final long minTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[bucketIndex];
+        scheduleAlarm(triggerTime, false, 0);
+        Thread.sleep(MIN_FUTURITY);
+        if (triggerTime + DEFAULT_WAIT < minTriggerTime) {
+            assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay",
+                    waitForAlarms(1));
+            Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
+        }
+        assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
+                waitForAlarms(1));
+    }
+
+    @Test
+    public void testWorkingSetDelay() throws Exception {
+        testBucketDelay(1);
+    }
+
+    @Test
+    public void testFrequentDelay() throws Exception {
+        testBucketDelay(2);
+    }
+
+    @Test
+    public void testRareDelay() throws Exception {
+        testBucketDelay(3);
+    }
+
+    @Test
+    public void testBucketUpgradeToSmallerDelay() throws Exception {
+        setAppStandbyBucket(APP_BUCKET_TAGS[2]);
+        final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        final long workingSetExpectedTrigger = mLastAlarmTime + APP_STANDBY_DELAYS[1];
+        scheduleAlarm(triggerTime, false, 0);
+        Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
+        assertFalse("The alarm went off before frequent delay", waitForAlarms(1));
+        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
+        assertTrue("The alarm did not go off when app bucket upgraded to working_set",
+                waitForAlarms(1));
+    }
+
+
+    /**
+     * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
+     * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
+     * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
+     * change, whichever happened later.
+     */
+    @Test
+    public void testBucketUpgradeToNoDelay() throws Exception {
+        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
+        final long triggerTime1 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        scheduleAlarm(triggerTime1, false, 0);
+        Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
+        assertFalse("The alarm went off after frequent delay when app in rare bucket",
+                waitForAlarms(1));
+        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
+        assertTrue("The alarm did not go off when app bucket upgraded to working_set",
+                waitForAlarms(1));
+
+        // Once more
+        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
+        final long triggerTime2 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        scheduleAlarm(triggerTime2, false, 0);
+        setAppStandbyBucket(APP_BUCKET_TAGS[0]);
+        Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
+        assertTrue("The alarm did not go off as scheduled when the app was in active",
+                waitForAlarms(1));
+    }
+
+    @Test
+    public void testAllowWhileIdleAlarms() throws Exception {
+        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(firstTrigger, true, 0);
+        Thread.sleep(MIN_FUTURITY);
+        assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarms(1));
+        scheduleAlarm(mLastAlarmTime + 9_000, true, 0);
+        // First check for the case where allow_while_idle delay should supersede app standby
+        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
+        Thread.sleep(APP_STANDBY_DELAYS[1]);
+        assertFalse("allow_while_idle alarm went off before short time", waitForAlarms(1));
+        long expectedTriggerTime = mLastAlarmTime + ALLOW_WHILE_IDLE_SHORT_TIME;
+        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
+        assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarms(1));
+
+        // Now the other case, app standby delay supersedes the allow_while_idle delay
+        scheduleAlarm(mLastAlarmTime + 12_000, true, 0);
+        setAppStandbyBucket(APP_BUCKET_TAGS[2]);
+        Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
+        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[2]
+                + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
+        expectedTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
+        assertTrue("allow_while_idle alarm did not go off even after " + APP_STANDBY_DELAYS[2]
+                + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
+    }
+
+    @Test
+    public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
+        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
+        setPowerWhitelisted(true);
+        final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(triggerTime, false, 0);
+        Thread.sleep(MIN_FUTURITY);
+        assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarms(1));
+        setPowerWhitelisted(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        setPowerWhitelisted(false);
+        setBatteryCharging(true);
+        deleteAlarmManagerConstants();
+        final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
+        cancelAlarmsIntent.setComponent(mAlarmScheduler);
+        mContext.sendBroadcast(cancelAlarmsIntent);
+        mContext.unregisterReceiver(mAlarmStateReceiver);
+        // Broadcast unregister may race with the next register in setUp
+        Thread.sleep(500);
+    }
+
+    private void updateAlarmManagerConstants() throws IOException {
+        final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
+        cmd.append("min_futurity="); cmd.append(MIN_FUTURITY);
+        cmd.append(",allow_while_idle_short_time="); cmd.append(ALLOW_WHILE_IDLE_SHORT_TIME);
+        for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
+            cmd.append(",");
+            cmd.append(APP_BUCKET_KEYS[i]); cmd.append("="); cmd.append(APP_STANDBY_DELAYS[i]);
+        }
+        executeAndLog(cmd.toString());
+    }
+
+    private void setPowerWhitelisted(boolean whitelist) throws IOException {
+        final StringBuffer cmd = new StringBuffer("cmd deviceidle whitelist ");
+        cmd.append(whitelist ? "+" : "-");
+        cmd.append(TEST_APP_PACKAGE);
+        executeAndLog(cmd.toString());
+    }
+
+    private void deleteAlarmManagerConstants() throws IOException {
+        executeAndLog("settings delete global alarm_manager_constants");
+    }
+
+    private void setAppStandbyBucket(String bucket) throws IOException {
+        executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
+    }
+
+    private void setBatteryCharging(final boolean charging) throws Exception {
+        final BatteryManager bm = mContext.getSystemService(BatteryManager.class);
+        final String cmd = "dumpsys battery " + (charging ? "reset" : "unplug");
+        executeAndLog(cmd);
+        if (!charging) {
+            assertTrue("Battery could not be unplugged", waitUntil(() -> !bm.isCharging(), 5_000));
+        }
+    }
+
+    private String executeAndLog(String cmd) throws IOException {
+        final String output = mUiDevice.executeShellCommand(cmd);
+        Log.d(TAG, "command: [" + cmd + "], output: [" + output.trim() + "]");
+        return output;
+    }
+
+    private boolean waitForAlarms(final int numAlarms) throws InterruptedException {
+        final boolean success = waitUntil(() -> (mAlarmCount.get() == numAlarms), DEFAULT_WAIT);
+        mAlarmCount.set(0);
+        return success;
+    }
+
+    private boolean waitUntil(Condition condition, long timeout) throws InterruptedException {
+        final long deadLine = SystemClock.uptimeMillis() + timeout;
+        while (!condition.isMet() && SystemClock.uptimeMillis() < deadLine) {
+            Thread.sleep(POLL_INTERVAL);
+        }
+        return condition.isMet();
+    }
+
+    @FunctionalInterface
+    interface Condition {
+        boolean isMet();
+    }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
new file mode 100644
index 0000000..a981e5a
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.alarmmanager.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
+import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests that apps put in forced app standby by the user do not get to run alarms while in the
+ * background
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundRestrictedAlarmsTest {
+    private static final String TAG = BackgroundRestrictedAlarmsTest.class.getSimpleName();
+    private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
+    private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
+    private static final String APP_OP_RUN_ANY_IN_BACKGROUND = "RUN_ANY_IN_BACKGROUND";
+    private static final String APP_OP_MODE_ALLOWED = "allow";
+    private static final String APP_OP_MODE_IGNORED = "ignore";
+
+    private static final long DEFAULT_WAIT = 1_000;
+    private static final long POLL_INTERVAL = 200;
+    private static final long MIN_REPEATING_INTERVAL = 10_000;
+
+    private Object mLock = new Object();
+    private Context mContext;
+    private ComponentName mAlarmScheduler;
+    private UiDevice mUiDevice;
+    private int mAlarmCount;
+
+    private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG, "Received action " + intent.getAction()
+                    + " elapsed: " + SystemClock.elapsedRealtime());
+            synchronized (mLock) {
+                mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
+            }
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
+        mAlarmCount = 0;
+        updateAlarmManagerConstants();
+        setAppOpsMode(APP_OP_MODE_IGNORED);
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
+        mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
+        setAppStandbyBucket("active");
+    }
+
+    private void scheduleAlarm(int type, long triggerMillis, long interval) {
+        final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
+        setAlarmIntent.setComponent(mAlarmScheduler);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, type);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
+        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
+        mContext.sendBroadcast(setAlarmIntent);
+    }
+
+    private void scheduleAlarmClock(long triggerRTC) {
+        AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);
+
+        final Intent setAlarmClockIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM_CLOCK);
+        setAlarmClockIntent.setComponent(mAlarmScheduler);
+        setAlarmClockIntent.putExtra(TestAlarmScheduler.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
+        mContext.sendBroadcast(setAlarmClockIntent);
+    }
+
+    private static int getMinExpectedExpirations(long now, long start, long interval) {
+        if (now - start <= 1000) {
+            return 0;
+        }
+        return 1 + (int)((now - start - 1000)/interval);
+    }
+
+    @Test
+    public void testRepeatingAlarmBlocked() throws Exception {
+        final long interval = MIN_REPEATING_INTERVAL;
+        final long triggerElapsed = SystemClock.elapsedRealtime() + interval;
+        scheduleAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed, interval);
+        Thread.sleep(DEFAULT_WAIT);
+        Thread.sleep(2 * interval);
+        assertFalse("Alarm got triggered even under restrictions", waitForAlarms(1, DEFAULT_WAIT));
+        Thread.sleep(interval);
+        setAppOpsMode(APP_OP_MODE_ALLOWED);
+        // The alarm is due to go off about 3 times by now. Adding some tolerance just in case
+        // an expiration is due right about now.
+        final int minCount = getMinExpectedExpirations(SystemClock.elapsedRealtime(),
+                triggerElapsed, interval);
+        assertTrue("Alarm should have expired at least " + minCount
+                + " times when restrictions were lifted", waitForAlarms(minCount, DEFAULT_WAIT));
+    }
+
+    @Test
+    public void testAlarmClockNotBlocked() throws Exception {
+        final long nowRTC = System.currentTimeMillis();
+        final long waitInterval = 10_000;
+        final long triggerRTC = nowRTC + waitInterval;
+        scheduleAlarmClock(triggerRTC);
+        Thread.sleep(waitInterval);
+        assertTrue("AlarmClock did not go off as scheduled when under restrictions",
+                waitForAlarms(1, DEFAULT_WAIT));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        deleteAlarmManagerConstants();
+        setAppOpsMode(APP_OP_MODE_ALLOWED);
+        // Cancel any leftover alarms
+        final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
+        cancelAlarmsIntent.setComponent(mAlarmScheduler);
+        mContext.sendBroadcast(cancelAlarmsIntent);
+        mContext.unregisterReceiver(mAlarmStateReceiver);
+        // Broadcast unregister may race with the next register in setUp
+        Thread.sleep(DEFAULT_WAIT);
+    }
+
+    private void updateAlarmManagerConstants() throws IOException {
+        String cmd = "settings put global alarm_manager_constants min_interval="
+                + MIN_REPEATING_INTERVAL;
+        mUiDevice.executeShellCommand(cmd);
+    }
+
+    private void deleteAlarmManagerConstants() throws IOException {
+        mUiDevice.executeShellCommand("settings delete global alarm_manager_constants");
+    }
+
+    private void setAppStandbyBucket(String bucket) throws IOException {
+        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
+    }
+
+    private void setAppOpsMode(String mode) throws IOException {
+        StringBuilder commandBuilder = new StringBuilder("appops set ")
+                .append(TEST_APP_PACKAGE)
+                .append(" ")
+                .append(APP_OP_RUN_ANY_IN_BACKGROUND)
+                .append(" ")
+                .append(mode);
+        mUiDevice.executeShellCommand(commandBuilder.toString());
+    }
+
+    private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
+        final long deadLine = SystemClock.uptimeMillis() + timeout;
+        int alarmCount;
+        do {
+            Thread.sleep(POLL_INTERVAL);
+            synchronized (mLock) {
+                alarmCount = mAlarmCount;
+            }
+        } while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
+        return alarmCount >= expectedAlarms;
+    }
+}
diff --git a/tests/JobScheduler/Android.mk b/tests/JobScheduler/Android.mk
index e58a494..622c06e 100755
--- a/tests/JobScheduler/Android.mk
+++ b/tests/JobScheduler/Android.mk
@@ -22,9 +22,12 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ub-uiautomator android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, JobTestApp/src)
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
@@ -32,7 +35,8 @@
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_PACKAGE_NAME := CtsJobSchedulerTestCases
 
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index e433252..4c9625d 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -43,8 +43,5 @@
         android:name="android.support.test.runner.AndroidJUnitRunner"
         android:label="JobScheduler device-side tests"
         android:targetPackage="android.jobscheduler.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 </manifest>
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index 855ad48..d7a5988 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -14,12 +14,14 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Job Scheduler 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="CtsJobSchedulerTestCases.apk" />
         <option name="test-file-name" value="CtsJobSchedulerJobPerm.apk" />
         <option name="test-file-name" value="CtsJobSchedulerSharedUid.apk" />
+        <option name="test-file-name" value="CtsJobTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.jobscheduler.cts" />
diff --git a/tests/JobScheduler/JobTestApp/Android.mk b/tests/JobScheduler/JobTestApp/Android.mk
new file mode 100644
index 0000000..61f7116
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/Android.mk
@@ -0,0 +1,31 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+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 := CtsJobTestApp
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/JobScheduler/JobTestApp/AndroidManifest.xml b/tests/JobScheduler/JobTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..866c5d4
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.jobscheduler.cts.jobtestapp">
+
+    <!-- This application schedules jobs independently of the test instrumentation to make
+    it possible to test behaviour for different app states, whitelists and device idle modes -->
+    <application>
+        <service android:name=".TestJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" />
+        <activity android:name=".TestActivity"
+                  android:exported="true" />
+        <receiver android:name=".TestJobSchedulerReceiver"
+                  android:exported="true" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java
new file mode 100644
index 0000000..0368f05
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Just a dummy activity to keep the test app process in the foreground state when desired.
+ */
+public class TestActivity extends Activity {
+    private static final String TAG = TestActivity.class.getSimpleName();
+    private static final String PACKAGE_NAME = "android.jobscheduler.cts.jobtestapp";
+    private static final long DEFAULT_WAIT_DURATION = 30_000;
+
+    static final int FINISH_ACTIVITY_MSG = 1;
+    public static final String ACTION_FINISH_ACTIVITY = PACKAGE_NAME + ".action.FINISH_ACTIVITY";
+
+    Handler mFinishHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case FINISH_ACTIVITY_MSG:
+                    Log.d(TAG, "Finishing test activity: " + TestActivity.class.getCanonicalName());
+                    unregisterReceiver(mFinishReceiver);
+                    finish();
+            }
+        }
+    };
+
+    final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mFinishHandler.removeMessages(FINISH_ACTIVITY_MSG);
+            mFinishHandler.sendEmptyMessage(FINISH_ACTIVITY_MSG);
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstance) {
+        Log.d(TAG, "Started test activity: " + TestActivity.class.getCanonicalName());
+        super.onCreate(savedInstance);
+        // automatically finish after 30 seconds.
+        mFinishHandler.sendEmptyMessageDelayed(FINISH_ACTIVITY_MSG, DEFAULT_WAIT_DURATION);
+        registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+    }
+}
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
new file mode 100644
index 0000000..97e8d74
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Schedules jobs for this package but does not, by itself, occupy a foreground uid state
+ * while doing so.
+ */
+public class TestJobSchedulerReceiver extends BroadcastReceiver {
+    private static final String TAG = TestJobSchedulerReceiver.class.getSimpleName();
+    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+
+    public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
+    public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
+    public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
+    public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
+    public static final int JOB_INITIAL_BACKOFF = 10_000;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final ComponentName jobServiceComponent = new ComponentName(context, TestJobService.class);
+        final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        switch (intent.getAction()) {
+            case ACTION_CANCEL_JOBS:
+                jobScheduler.cancelAll();
+                Log.d(TAG, "Cancelled all jobs for " + context.getPackageName());
+                break;
+            case ACTION_SCHEDULE_JOB:
+                final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
+                final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
+                JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
+                        .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
+                        .setOverrideDeadline(0)
+                        .setImportantWhileForeground(allowInIdle);
+                final int result = jobScheduler.schedule(jobBuilder.build());
+                if (result != JobScheduler.RESULT_SUCCESS) {
+                    Log.e(TAG, "Could not schedule job " + jobId);
+                } else {
+                    Log.d(TAG, "Successfully scheduled job with id " + jobId);
+                }
+                break;
+            default:
+                Log.e(TAG, "Unknown action " + intent.getAction());
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java
new file mode 100644
index 0000000..55031c3
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java
@@ -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
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestJobService extends JobService {
+    private static final String TAG = TestJobService.class.getSimpleName();
+    private static final String PACKAGE_NAME = "android.jobscheduler.cts.jobtestapp";
+    public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
+    public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
+    public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        Log.i(TAG, "Test job executing: " + params.getJobId());
+        final Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
+        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        sendBroadcast(reportJobStartIntent);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        Log.i(TAG, "Test job stopped executing: " + params.getJobId());
+        final Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
+        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        sendBroadcast(reportJobStopIntent);
+        return true;
+    }
+}
diff --git a/tests/JobScheduler/jobperm/AndroidManifest.xml b/tests/JobScheduler/jobperm/AndroidManifest.xml
index 14eb02b..493c3e8 100755
--- a/tests/JobScheduler/jobperm/AndroidManifest.xml
+++ b/tests/JobScheduler/jobperm/AndroidManifest.xml
@@ -27,6 +27,8 @@
     <uses-permission android:name="android.jobscheduler.cts.jobperm.perm" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <!-- Need a way for another app to try to access the permission. So create a content
         provider which is enforced by the permission -->
         <provider android:name=".JobPermProvider"
diff --git a/tests/JobScheduler/shareduid/AndroidManifest.xml b/tests/JobScheduler/shareduid/AndroidManifest.xml
index 756a067..7b4bb56 100755
--- a/tests/JobScheduler/shareduid/AndroidManifest.xml
+++ b/tests/JobScheduler/shareduid/AndroidManifest.xml
@@ -19,5 +19,6 @@
           package="android.jobscheduler.cts.shareduid"
           android:sharedUserId="android.jobscheduler.cts.shared.uid">
     <application>
+        <uses-library android:name="android.test.runner" />
     </application>
 </manifest>
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 94aee20..964853c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -52,6 +52,8 @@
 
     ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
 
+    ArrayList<JobWorkItem> mPendingCompletions = new ArrayList<>();
+
     private boolean mWaitingForStop;
 
     @Override
@@ -104,6 +106,8 @@
                 Log.i(TAG, "Received work #" + index + ": " + work.getIntent());
                 mReceivedWork.add(work);
 
+                int flags = 0;
+
                 if (index < expectedWork.length) {
                     TestWorkItem expected = expectedWork[index];
                     int grantFlags = work.getIntent().getFlags();
@@ -170,15 +174,33 @@
                         }
                     }
 
-                    if ((expected.flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
+                    flags = expected.flags;
+
+                    if ((flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
                         Log.i(TAG, "Now waiting to stop");
                         mWaitingForStop = true;
                         TestEnvironment.getTestEnvironment().notifyWaitingForStop();
                         return true;
                     }
+
+                    if ((flags & TestWorkItem.FLAG_COMPLETE_NEXT) != 0) {
+                        if (!processNextPendingCompletion()) {
+                            TestEnvironment.getTestEnvironment().notifyExecution(params,
+                                    0, 0, null,
+                                    "Expected to complete next pending work but there was none: "
+                                            + " @ #" + index);
+                            return false;
+                        }
+                    }
                 }
 
-                mParams.completeWork(work);
+                if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) != 0) {
+                    mPendingCompletions.add(work);
+                } else if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) != 0) {
+                    mPendingCompletions.add(0, work);
+                } else {
+                    mParams.completeWork(work);
+                }
 
                 if (index < expectedWork.length) {
                     TestWorkItem expected = expectedWork[index];
@@ -195,6 +217,20 @@
 
                 index++;
             }
+
+            if (processNextPendingCompletion()) {
+                // We had some pending completions, clean them all out...
+                while (processNextPendingCompletion()) {
+                }
+                // ...and we need to do a final dequeue to complete the job, which should not
+                // return any remaining work.
+                if ((work = params.dequeueWork()) != null) {
+                    TestEnvironment.getTestEnvironment().notifyExecution(params,
+                            0, 0, null,
+                            "Expected no remaining work after dequeue pending, but got: " + work);
+                }
+            }
+
             Log.i(TAG, "Done with all work at #" + index);
             // We don't notifyExecution here because we want to make sure the job properly
             // stops itself.
@@ -219,6 +255,16 @@
         }
     }
 
+    boolean processNextPendingCompletion() {
+        if (mPendingCompletions.size() <= 0) {
+            return false;
+        }
+
+        JobWorkItem next = mPendingCompletions.remove(0);
+        mParams.completeWork(next);
+        return true;
+    }
+
     @Override
     public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "Received stop callback");
@@ -227,7 +273,24 @@
     }
 
     public static final class TestWorkItem {
+        /**
+         * Stop processing work for now, waiting for the service to be stopped.
+         */
         public static final int FLAG_WAIT_FOR_STOP = 1<<0;
+        /**
+         * Don't complete this work now, instead push it on the back of the stack of
+         * pending completions.
+         */
+        public static final int FLAG_DELAY_COMPLETE_PUSH_BACK = 1<<1;
+        /**
+         * Don't complete this work now, instead insert to the top of the stack of
+         * pending completions.
+         */
+        public static final int FLAG_DELAY_COMPLETE_PUSH_TOP = 1<<2;
+        /**
+         * Complete next pending completion on the stack before completing this one.
+         */
+        public static final int FLAG_COMPLETE_NEXT = 1<<3;
 
         public final Intent intent;
         public final JobInfo jobInfo;
@@ -247,6 +310,16 @@
             requireUrisNotGranted = null;
         }
 
+        public TestWorkItem(Intent _intent, int _flags) {
+            intent = _intent;
+            jobInfo = null;
+            flags = _flags;
+            deliveryCount = 1;
+            subitems = null;
+            requireUrisGranted = null;
+            requireUrisNotGranted = null;
+        }
+
         public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
             intent = _intent;
             jobInfo = null;
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index 65cd02b..e28675d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -28,6 +28,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -106,6 +107,10 @@
         boolean curNotLow = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
                 "cmd jobscheduler get-battery-not-low").trim());
         assertEquals(notLow, curNotLow);
+        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Intent batteryState = getContext().registerReceiver(null, filter);
+        assertEquals(notLow,
+                !batteryState.getBooleanExtra(BatteryManager.EXTRA_BATTERY_LOW, notLow));
     }
 
     String getJobState() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
new file mode 100644
index 0000000..bcad169
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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 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 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 Context mContext;
+    private UiDevice mUiDevice;
+    private PowerManager mPowerManager;
+    private long mTempWhitelistExpiryElapsed;
+    private int mTestJobId;
+    private int mTestPackageUid;
+    private boolean mDeviceInDoze;
+    /* 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;
+            }
+        }
+    };
+
+    @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();
+        makeTestPackageStandbyActive();
+    }
+
+
+    @Test
+    public void testAllowWhileIdleJobInForeground() throws Exception {
+        toggleDeviceIdleState(true);
+        sendScheduleJobBroadcast(true);
+        assertFalse("Job started while in background", awaitJobStart(5_000));
+        startAndKeepTestActivity();
+        assertTrue("Job with allow_while_idle flag did not start when the app was in fg",
+                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
+        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 {
+        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(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testBackgroundJobsDelayed() throws Exception {
+        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 {
+        makeTestPackageStandbyNever();
+        enterFakeUnpluggedState();
+        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
+        sendScheduleJobBroadcast(false);
+        assertFalse("New job started in NEVER standby", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        resetFakeUnpluggedState();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        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.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(DEFAULT_WAIT_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 makeTestPackageStandbyActive() throws Exception {
+        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " active");
+    }
+
+    private void makeTestPackageStandbyNever() throws Exception {
+        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " never");
+    }
+
+    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(DEFAULT_WAIT_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/EnqueueJobWorkTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
index 6d2599f..604ccce 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
@@ -186,6 +186,87 @@
     }
 
     /**
+     * Test basic enqueueing batches of work that will be executed in parallel.
+     */
+    public void testEnqueueParallel2Work() throws Exception {
+        Intent work1 = new Intent("work1");
+        Intent work2 = new Intent("work2");
+        TestWorkItem[] work = new TestWorkItem[] {
+                new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+                new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) };
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(work);
+        JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+        kTestEnvironment.readyToWork();
+        assertTrue("Job with work enqueued did not fire.",
+                kTestEnvironment.awaitExecution());
+        compareWork(work, kTestEnvironment.getLastReceivedWork());
+    }
+
+    /**
+     * Test basic enqueueing batches of work that will be executed in parallel and completed
+     * in reverse order.
+     */
+    public void testEnqueueParallel2ReverseWork() throws Exception {
+        Intent work1 = new Intent("work1");
+        Intent work2 = new Intent("work2");
+        TestWorkItem[] work = new TestWorkItem[] {
+                new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+                new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) };
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(work);
+        JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+        kTestEnvironment.readyToWork();
+        assertTrue("Job with work enqueued did not fire.",
+                kTestEnvironment.awaitExecution());
+        compareWork(work, kTestEnvironment.getLastReceivedWork());
+    }
+
+    /**
+     * Test basic enqueueing batches of work that will be executed in parallel.
+     */
+    public void testEnqueueMultipleParallelWork() throws Exception {
+        Intent work1 = new Intent("work1");
+        Intent work2 = new Intent("work2");
+        Intent work3 = new Intent("work3");
+        Intent work4 = new Intent("work4");
+        Intent work5 = new Intent("work5");
+        Intent work6 = new Intent("work6");
+        Intent work7 = new Intent("work7");
+        Intent work8 = new Intent("work8");
+        TestWorkItem[] work = new TestWorkItem[] {
+                new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+                new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+                new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK
+                        | TestWorkItem.FLAG_COMPLETE_NEXT),
+                new TestWorkItem(work4, TestWorkItem.FLAG_COMPLETE_NEXT),
+                new TestWorkItem(work5, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+                new TestWorkItem(work6, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+                new TestWorkItem(work7, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP
+                        | TestWorkItem.FLAG_COMPLETE_NEXT),
+                new TestWorkItem(work8) };
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(work);
+        JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work7));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work8));
+        kTestEnvironment.readyToWork();
+        assertTrue("Job with work enqueued did not fire.",
+                kTestEnvironment.awaitExecution());
+        compareWork(work, kTestEnvironment.getLastReceivedWork());
+    }
+
+    /**
      * Test job getting stopped while processing work and that work being redelivered.
      */
     public void testEnqueueMultipleRedeliver() throws Exception {
@@ -238,6 +319,61 @@
     }
 
     /**
+     * Test job getting stopped while processing work in parallel and that work being redelivered.
+     */
+    public void testEnqueueMultipleParallelRedeliver() throws Exception {
+        Intent work1 = new Intent("work1");
+        Intent work2 = new Intent("work2");
+        Intent work3 = new Intent("work3");
+        Intent work4 = new Intent("work4");
+        Intent work5 = new Intent("work5");
+        Intent work6 = new Intent("work6");
+        TestWorkItem[] initialWork = new TestWorkItem[] {
+                new TestWorkItem(work1),
+                new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+                new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+                new TestWorkItem(work4, TestWorkItem.FLAG_WAIT_FOR_STOP, 1) };
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForStop();
+        kTestEnvironment.setExpectedWork(initialWork);
+        JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+        kTestEnvironment.readyToWork();
+
+        // Now wait for the job to get to the point where it is processing the last
+        // work and waiting for it to be stopped.
+        assertTrue("Job with work enqueued did not wait to stop.",
+                kTestEnvironment.awaitWaitingForStop());
+
+        // Cause the job to timeout (stop) immediately, and wait for its execution to finish.
+        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler timeout "
+                + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+        assertTrue("Job with work enqueued did not finish.",
+                kTestEnvironment.awaitExecution());
+        compareWork(initialWork, kTestEnvironment.getLastReceivedWork());
+
+        // Now we are going to add some more work, restart the job, and see if it correctly
+        // redelivers the last work and delivers the new work.
+        TestWorkItem[] finalWork = new TestWorkItem[] {
+                new TestWorkItem(work2, 0, 2), new TestWorkItem(work3, 0, 2),
+                new TestWorkItem(work4, 0, 2), new TestWorkItem(work5), new TestWorkItem(work6) };
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(finalWork);
+        mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+        mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+        kTestEnvironment.readyToWork();
+        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run "
+                + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+
+        assertTrue("Restarted with work enqueued did not execute.",
+                kTestEnvironment.awaitExecution());
+        compareWork(finalWork, kTestEnvironment.getLastReceivedWork());
+    }
+
+    /**
      * Test basic enqueueing batches of work.
      */
     public void testEnqueueMultipleUriGrantWork() throws Exception {
diff --git a/tests/ProcessTest/Android.mk b/tests/ProcessTest/Android.mk
index 2feff2e..3084b61 100644
--- a/tests/ProcessTest/Android.mk
+++ b/tests/ProcessTest/Android.mk
@@ -24,7 +24,9 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_PACKAGE_NAME := ProcessTests
 
@@ -33,6 +35,6 @@
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/ProcessTest/NoShareUidApp/Android.mk b/tests/ProcessTest/NoShareUidApp/Android.mk
index f951fc7..7645b5c 100644
--- a/tests/ProcessTest/NoShareUidApp/Android.mk
+++ b/tests/ProcessTest/NoShareUidApp/Android.mk
@@ -29,4 +29,4 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/ProcessTest/ShareUidApp/Android.mk b/tests/ProcessTest/ShareUidApp/Android.mk
index e22e7f1..fdd3feb 100644
--- a/tests/ProcessTest/ShareUidApp/Android.mk
+++ b/tests/ProcessTest/ShareUidApp/Android.mk
@@ -29,4 +29,4 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/acceleration/Android.mk b/tests/acceleration/Android.mk
index 81869bf..cef4379 100644
--- a/tests/acceleration/Android.mk
+++ b/tests/acceleration/Android.mk
@@ -25,7 +25,9 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner compatibility-device-util legacy-android-test
+    ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/acceleration/AndroidTest.xml b/tests/acceleration/AndroidTest.xml
index 01ff4cc..b28f845 100644
--- a/tests/acceleration/AndroidTest.xml
+++ b/tests/acceleration/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Acceleration test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="location" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/accessibility/Android.mk b/tests/accessibility/Android.mk
index 4cd7e92..cf41720 100644
--- a/tests/accessibility/Android.mk
+++ b/tests/accessibility/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index aa9db4f..75c3563 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -18,6 +18,7 @@
 
 import android.os.Message;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 import android.view.accessibility.AccessibilityEvent;
@@ -29,10 +30,11 @@
 /**
  * Class for testing {@link AccessibilityEvent}.
  */
+@Presubmit
 public class AccessibilityEventTest extends TestCase {
 
     /** The number of properties of the {@link AccessibilityEvent} class. */
-    private static final int NON_STATIC_FIELD_COUNT = 29;
+    private static final int NON_STATIC_FIELD_COUNT = 32;
 
     /**
      * Test that no new fields have been added without updating the
@@ -63,6 +65,8 @@
 
         // make sure all fields properly marshaled
         assertEqualsAccessiblityEvent(sentEvent, receivedEvent);
+
+        parcel.recycle();
     }
 
     /**
@@ -157,6 +161,8 @@
         AccessibilityEvent unmarshaledEvent = AccessibilityEvent.obtain();
         unmarshaledEvent.initFromParcel(parcel);
         assertEqualsAccessiblityEvent(marshaledEvent, unmarshaledEvent);
+
+        parcel.recycle();
     }
 
     /**
@@ -206,6 +212,8 @@
         sentEvent.setMaxScrollY(1);
         sentEvent.setScrollX(1);
         sentEvent.setScrollY(1);
+        sentEvent.setScrollDeltaX(3);
+        sentEvent.setScrollDeltaY(3);
         sentEvent.setToIndex(1);
         sentEvent.setScrollable(true);
         sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
@@ -261,6 +269,10 @@
                 receivedEvent.getScrollX());
         assertSame("scrollY has incorect value", expectedEvent.getScrollY(),
                 receivedEvent.getScrollY());
+        assertSame("scrollDeltaX has incorect value", expectedEvent.getScrollDeltaX(),
+                receivedEvent.getScrollDeltaX());
+        assertSame("scrollDeltaY has incorect value", expectedEvent.getScrollDeltaY(),
+                receivedEvent.getScrollDeltaY());
         assertSame("toIndex has incorect value", expectedEvent.getToIndex(),
                 receivedEvent.getToIndex());
         assertSame("scrollable has incorect value", expectedEvent.isScrollable(),
@@ -269,6 +281,8 @@
                 receivedEvent.getMovementGranularity());
         assertSame("action has incorect value", expectedEvent.getAction(),
                 receivedEvent.getAction());
+        assertSame("windowChangeTypes has incorect value", expectedEvent.getWindowChanges(),
+                receivedEvent.getWindowChanges());
 
         assertSame("parcelableData has incorect value",
                 ((Message) expectedEvent.getParcelableData()).what,
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index 6a4efd8..ccb5a6d 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -46,8 +46,6 @@
     private static final String MULTIPLE_FEEDBACK_TYPES_ACCESSIBILITY_SERVICE_NAME =
         "android.view.accessibility.cts.SpeakingAndVibratingAccessibilityService";
 
-    private static final long WAIT_FOR_ACCESSIBILITY_ENABLED_TIMEOUT = 3000; // 3s
-
     private AccessibilityManager mAccessibilityManager;
 
     private Context mTargetContext;
@@ -334,7 +332,8 @@
     private void assertAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
             boolean expectedValue, Object waitObject, String message)
             throws Exception {
-        long timeoutTime = System.currentTimeMillis() + WAIT_FOR_ACCESSIBILITY_ENABLED_TIMEOUT;
+        long timeoutTime =
+                System.currentTimeMillis() + ServiceControlUtils.TIMEOUT_FOR_SERVICE_ENABLE;
         synchronized (waitObject) {
             while ((atomicBoolean.get() != expectedValue)
                     && (System.currentTimeMillis() < timeoutTime)) {
@@ -353,7 +352,8 @@
             }
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
-        long timeoutTime = System.currentTimeMillis() + WAIT_FOR_ACCESSIBILITY_ENABLED_TIMEOUT;
+        long timeoutTime =
+                System.currentTimeMillis() + ServiceControlUtils.TIMEOUT_FOR_SERVICE_ENABLE;
         synchronized (waitObject) {
             while (!mAccessibilityManager.isEnabled()
                     && (System.currentTimeMillis() < timeoutTime)) {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 1dfbf94..95d8f2d 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
@@ -25,7 +26,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.accessibility.cts.R;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -34,10 +34,11 @@
 /**
  * Class for testing {@link AccessibilityNodeInfo}.
  */
+@Presubmit
 public class AccessibilityNodeInfoTest extends AndroidTestCase {
 
     /** The number of properties of the {@link AccessibilityNodeInfo} class that are marshalled. */
-    private static final int NUM_MARSHALLED_PROPERTIES = 33;
+    private static final int NUM_MARSHALLED_PROPERTIES = 35;
 
     /**
      * The number of properties that are purposely not marshalled
@@ -57,12 +58,14 @@
 
         // marshal and unmarshal the node info
         Parcel parcel = Parcel.obtain();
-        sentInfo.writeToParcel(parcel, 0);
+        sentInfo.writeToParcelNoRecycle(parcel, 0);
         parcel.setDataPosition(0);
         AccessibilityNodeInfo receivedInfo = AccessibilityNodeInfo.CREATOR.createFromParcel(parcel);
 
         // make sure all fields properly marshaled
         assertEqualsAccessibilityNodeInfo(sentInfo, receivedInfo);
+
+        parcel.recycle();
     }
 
     /**
@@ -206,6 +209,7 @@
         info.setBoundsInScreen(new Rect(2,2,2,2));
         info.setClassName("foo.bar.baz.Class");
         info.setContentDescription("content description");
+        info.setTooltipText("tooltip");
         info.setPackageName("foo.bar.baz");
         info.setText("text");
         info.setHintText("hint");
@@ -221,6 +225,7 @@
         info.setPassword(true);
         info.setScrollable(true);
         info.setSelected(true);
+        info.setHeading(true);
         info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
         info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
         info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS, "Foo"));
@@ -233,6 +238,7 @@
         info.setDrawingOrder(5);
         info.setAvailableExtraData(
                 Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+        info.setPaneTitle("Pane title");
     }
 
     /**
@@ -254,6 +260,8 @@
                 receivedInfo.getClassName());
         assertEquals("contentDescription has incorrect value", expectedInfo.getContentDescription(),
                 receivedInfo.getContentDescription());
+        assertEquals("tooltip text has incorrect value", expectedInfo.getTooltipText(),
+                receivedInfo.getTooltipText());
         assertEquals("packageName has incorrect value", expectedInfo.getPackageName(),
                 receivedInfo.getPackageName());
         assertEquals("text has incorrect value", expectedInfo.getText(), receivedInfo.getText());
@@ -284,6 +292,8 @@
                 receivedInfo.isScrollable());
         assertSame("selected has incorrect value", expectedInfo.isSelected(),
                 receivedInfo.isSelected());
+        assertSame("isHeading has incorrect value",
+                expectedInfo.isHeading(), receivedInfo.isHeading());
         assertSame("actions has incorrect value", expectedInfo.getActions(),
                 receivedInfo.getActions());
         assertEquals("actionsSet has incorrect value", expectedInfo.getActionList(),
@@ -304,6 +314,8 @@
                 receivedInfo.getDrawingOrder());
         assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
                 receivedInfo.getAvailableExtraData());
+        assertEquals("Pane title has incorrect value", expectedInfo.getPaneTitle(),
+                receivedInfo.getPaneTitle());
     }
 
     /**
@@ -319,9 +331,11 @@
         assertTrue("boundsInScreen not properly recycled", bounds.isEmpty());
         assertNull("className not properly recycled", info.getClassName());
         assertNull("contentDescription not properly recycled", info.getContentDescription());
+        assertNull("tooltiptext not properly recycled", info.getTooltipText());
         assertNull("packageName not properly recycled", info.getPackageName());
         assertNull("text not properly recycled", info.getText());
         assertNull("Hint text not properly recycled", info.getHintText());
+        assertNull("Pane title not properly recycled", info.getPaneTitle());
         assertFalse("checkable not properly recycled", info.isCheckable());
         assertFalse("checked not properly recycled", info.isChecked());
         assertFalse("clickable not properly recycled", info.isClickable());
@@ -335,6 +349,7 @@
         assertFalse("password not properly recycled", info.isPassword());
         assertFalse("scrollable not properly recycled", info.isScrollable());
         assertFalse("selected not properly recycled", info.isSelected());
+        assertFalse("isHeading depth not properly reset", info.isHeading());
         assertSame("actions not properly recycled", 0, info.getActions());
         assertFalse("accessibilityFocused not properly recycled", info.isAccessibilityFocused());
         assertSame("movementGranularities not properly recycled", 0,
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
index d64bd94..98b87fc 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
@@ -16,6 +16,7 @@
 
 package android.view.accessibility.cts;
 
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
@@ -23,6 +24,7 @@
 /**
  * Class for testing {@link CollectionInfo}.
  */
+@Presubmit
 public class AccessibilityNodeInfo_CollectionInfoTest extends AndroidTestCase {
 
     @SmallTest
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
index 954d762..4b01129 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
@@ -16,6 +16,7 @@
 
 package android.view.accessibility.cts;
 
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -24,6 +25,7 @@
 /**
  * Class for testing {@link AccessibilityNodeInfo.RangeInfo}.
  */
+@Presubmit
 public class AccessibilityNodeInfo_RangeInfoTest extends AndroidTestCase {
 
     /** Allowed tolerance for floating point equality comparisons. */
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
index eeee235..0c2a2dd 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
@@ -16,6 +16,7 @@
 
 package android.view.accessibility.cts;
 
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeProvider;
@@ -23,6 +24,7 @@
 /**
  * Class for testing {@link AccessibilityNodeProvider}.
  */
+@Presubmit
 public class AccessibilityNodeProviderTest extends AndroidTestCase {
     @SmallTest
     public void testDefaultBehavior() {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 7862cb4..a12ccce 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -17,6 +17,7 @@
 package android.view.accessibility.cts;
 
 import android.os.Message;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityEvent;
@@ -32,10 +33,11 @@
 /**
  * Class for testing {@link AccessibilityRecord}.
  */
+@Presubmit
 public class AccessibilityRecordTest extends AndroidTestCase {
 
     /** The number of properties of the {@link AccessibilityEvent} class. */
-    private static final int NON_STATIC_FIELD_COUNT = 22;
+    private static final int NON_STATIC_FIELD_COUNT = 24;
 
     /**
      * Test that no new fields have been added without updating the
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
index ffbe833..e959544 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
@@ -26,6 +27,7 @@
 /**
  * Class for testing {@link AccessibilityWindowInfo}.
  */
+@Presubmit
 public class AccessibilityWindowInfoTest extends AndroidTestCase {
 
     @SmallTest
@@ -47,6 +49,7 @@
         AccessibilityWindowInfo w2 = AccessibilityWindowInfo.CREATOR.createFromParcel(parcel);
         assertNotSame(w1, w2);
         assertTrue(areWindowsEqual(w1, w2));
+        parcel.recycle();
     }
 
     @SmallTest
diff --git a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
index 86c6f41..d0db868 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
@@ -16,19 +16,6 @@
 
 package android.view.accessibility.cts;
 
-import android.app.UiAutomation;
-import android.os.ParcelFileDescriptor;
-import android.test.InstrumentationTestCase;
-import android.view.accessibility.CaptioningManager;
-import android.view.accessibility.CaptioningManager.CaptionStyle;
-import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
-import org.mockito.Mockito;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Locale;
-
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Mockito.mock;
@@ -36,6 +23,20 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.test.InstrumentationTestCase;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+
+import org.mockito.Mockito;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
 /**
  * Tests whether the CaptioningManager APIs are functional.
  */
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
index 9783cb8..5e8755f 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
@@ -16,6 +16,10 @@
 
 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 android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ContentResolver;
@@ -28,12 +32,13 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Utility methods for enabling and disabling the services used in this package
  */
 public class ServiceControlUtils {
-    private static final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
+    public static final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
 
     private static final String SETTING_ENABLE_SPEAKING_AND_VIBRATING_SERVICES =
             "android.view.accessibility.cts/.SpeakingAccessibilityService:"
@@ -181,28 +186,32 @@
         final Object waitLockForA11yOff = new Object();
         AccessibilityManager manager = (AccessibilityManager) instrumentation
                 .getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        manager.addAccessibilityStateChangeListener(
-                new AccessibilityManager.AccessibilityStateChangeListener() {
-                    @Override
-                    public void onAccessibilityStateChanged(boolean b) {
-                        synchronized (waitLockForA11yOff) {
-                            waitLockForA11yOff.notifyAll();
-                        }
-                    }
-                });
-        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
-        while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+        // Updates to manager.isEnabled() aren't synchronized
+        AtomicBoolean accessibilityEnabled = new AtomicBoolean(manager.isEnabled());
+        AccessibilityManager.AccessibilityStateChangeListener listener = (boolean b) -> {
             synchronized (waitLockForA11yOff) {
-                if (!manager.isEnabled()) {
-                    return;
-                }
-                try {
-                    waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
-                } catch (InterruptedException e) {
-                    // Ignored; loop again
+                waitLockForA11yOff.notifyAll();
+                accessibilityEnabled.set(b);
+            }
+        };
+        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
+                    }
                 }
             }
+        } finally {
+            manager.removeAccessibilityStateChangeListener(listener);
         }
-        throw new RuntimeException("Unable to turn accessibility off");
+        assertThat("Unable to turn accessibility off", manager.isEnabled(), is(equalTo(false)));
     }
 }
diff --git a/tests/accessibilityservice/Android.mk b/tests/accessibilityservice/Android.mk
index 9a3bbe2..4f23a25 100644
--- a/tests/accessibilityservice/Android.mk
+++ b/tests/accessibilityservice/Android.mk
@@ -21,7 +21,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	ctstestrunner \
 	mockito-target-minus-junit4 \
-	legacy-android-test
+	compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -33,3 +35,5 @@
 LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index c40f7aa..e709c6f 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -28,27 +28,28 @@
 
         <activity
             android:label="@string/accessibility_end_to_end_test_activity"
-            android:name=".AccessibilityEndToEndActivity" />
+            android:name=".activities.AccessibilityEndToEndActivity" />
 
         <activity
             android:label="@string/accessibility_query_window_test_activity"
-            android:name=".AccessibilityWindowQueryActivity"
+            android:name=".activities.AccessibilityWindowQueryActivity"
             android:supportsPictureInPicture="true" />
 
         <activity
             android:label="@string/accessibility_view_tree_reporting_test_activity"
-            android:name=".AccessibilityViewTreeReportingActivity" />
+            android:name=".activities.AccessibilityViewTreeReportingActivity" />
 
         <activity
             android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
-            android:name=".AccessibilityFocusAndInputFocusSyncActivity" />
+            android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity" />
 
         <activity
             android:label="@string/accessibility_text_traversal_test_activity"
-            android:name=".AccessibilityTextTraversalActivity"/>
+            android:name=".activities.AccessibilityTextTraversalActivity"/>
 
         <activity android:label="Activity for testing window accessibility reporting"
-             android:name=".AccessibilityWindowReportingActivity"/>
+             android:name=".activities.AccessibilityWindowReportingActivity"
+             android:supportsPictureInPicture="true"/>
 
         <activity
             android:label="Full screen activity for gesture dispatch testing"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index 6edfe8d..0454a298 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -14,11 +14,16 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAccessibilityServiceTestCases.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAccessibilityWidgetProvider.apk" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.accessibilityservice.cts" />
         <option name="runtime-hint" value="2m12s" />
diff --git a/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml b/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
index cb3d153..6a7ccf5 100644
--- a/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
+++ b/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
@@ -77,6 +77,7 @@
               android:id="@+id/secondButton"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
+              android:screenReaderFocusable="true"
               android:text="@string/secondButton" />
 
       </LinearLayout>
diff --git a/tests/accessibilityservice/res/layout/end_to_end_test.xml b/tests/accessibilityservice/res/layout/end_to_end_test.xml
index 79f87dc..fdc3998 100644
--- a/tests/accessibilityservice/res/layout/end_to_end_test.xml
+++ b/tests/accessibilityservice/res/layout/end_to_end_test.xml
@@ -29,16 +29,20 @@
     <EditText android:id="@+id/edittext"
               android:text="@string/text_input_blah"
               android:layout_height="wrap_content"
+              android:accessibilityHeading="true"
               android:layout_width="fill_parent">
     </EditText>
 
     <LinearLayout android:layout_width="fill_parent"
-                  android:layout_height="wrap_content"
-                  android:gravity="center">
+              android:layout_height="wrap_content"
+              android:gravity="center">
         <Button android:id="@+id/button"
                 android:text="@string/button_title"
+                android:accessibilityPaneTitle="@string/paneTitle"
+                android:tooltipText="@string/button_tooltip"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content" android:bufferType="normal">
+                android:layout_height="wrap_content"
+                android:bufferType="normal">
         </Button>
     </LinearLayout>
 
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 2e3d5dd..ad5d3fd 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -34,6 +34,8 @@
     <!-- String title of the button -->
     <string name="button_title">Click me</string>
 
+    <string name="button_tooltip">Never press this button</string>
+
     <!-- String value of the first list item -->
     <string name="first_list_item">First list item</string>
 
@@ -122,6 +124,8 @@
     <!-- String second Button text -->
     <string name="secondButton">sB</string>
 
+    <string name="paneTitle">Pane Title</string>
+
     <!-- String title of the accessibility focus and input focus sync test activity -->
     <string name="accessibility_focus_and_input_focus_sync_test_activity">Accessibility focus and input focus sync test</string>
 
@@ -135,6 +139,8 @@
 
     <string name="a_b">A B</string>
 
+    <string name="german_text_with_strong_s">ß</string>
+
     <string name="android_wiki_search">Android is a Linux-based</string>
 
     <string name="foo_bar_baz">Foo bar baz.</string>
diff --git a/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
index 912c9c4..f4f6089 100644
--- a/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
@@ -16,9 +16,9 @@
 -->
 
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
-                       android:description="@string/stub_gesture_dispatch_a11y_service_description"
-                       android:accessibilityEventTypes="typeAllMask"
-                       android:accessibilityFeedbackType="feedbackGeneric"
-                       android:accessibilityFlags="flagDefault"
-                       android:canPerformGestures="true"
-                       android:notificationTimeout="0" />
+        android:description="@string/stub_gesture_dispatch_a11y_service_description"
+        android:accessibilityEventTypes="typeAllMask"
+        android:accessibilityFeedbackType="feedbackGeneric"
+        android:accessibilityFlags="flagDefault"
+        android:canPerformGestures="true"
+        android:notificationTimeout="0" />
diff --git a/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
index 110f741..2559392 100644
--- a/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
@@ -15,10 +15,11 @@
 -->
 
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
-    android:accessibilityEventTypes="typeAllMask"
-    android:accessibilityFeedbackType="feedbackGeneric"
-    android:canRetrieveWindowContent="true"
-    android:canRequestTouchExplorationMode="true"
-    android:canRequestEnhancedWebAccessibility="true"
-    android:canRequestFilterKeyEvents="true"
-    android:canControlMagnification="true" />
+        android:accessibilityEventTypes="typeAllMask"
+        android:accessibilityFeedbackType="feedbackGeneric"
+        android:canRetrieveWindowContent="true"
+        android:canRequestTouchExplorationMode="true"
+        android:canRequestEnhancedWebAccessibility="true"
+        android:canRequestFilterKeyEvents="true"
+        android:canControlMagnification="true"
+        android:canPerformGestures="true"/>
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
index 8521a56..359c6ac 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
@@ -16,12 +16,11 @@
 
 import android.accessibilityservice.AccessibilityButtonController;
 import android.app.Instrumentation;
-import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java
deleted file mode 100644
index 9a26ac7..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java
+++ /dev/null
@@ -1,68 +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.accessibilityservice.cts;
-
-import android.accessibilityservice.cts.R;
-
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * This class is an {@link android.app.Activity} used to perform end-to-end
- * testing of the accessibility feature by interaction with the
- * UI widgets.
- */
-public class AccessibilityEndToEndActivity extends AccessibilityTestActivity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.end_to_end_test);
-
-        ListAdapter listAdapter = new BaseAdapter() {
-            public View getView(int position, View convertView, ViewGroup parent) {
-                TextView textView = (TextView) View
-                        .inflate(AccessibilityEndToEndActivity.this, R.layout.list_view_row, null);
-                textView.setText((String) getItem(position));
-                return textView;
-            }
-
-            public long getItemId(int position) {
-                return position;
-            }
-
-            public Object getItem(int position) {
-                if (position == 0) {
-                    return AccessibilityEndToEndActivity.this.getString(R.string.first_list_item);
-                } else {
-                    return AccessibilityEndToEndActivity.this.getString(R.string.second_list_item);
-                }
-            }
-
-            public int getCount() {
-                return 2;
-            }
-        };
-
-        ListView listView = (ListView) findViewById(R.id.listview);
-        listView.setAdapter(listAdapter);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index b14b76a..b24846c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,30 +16,52 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils
+        .filterForEventType;
+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 org.hamcrest.Matchers.in;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.app.UiAutomation;
+import android.appwidget.AppWidgetHost;
+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.content.res.Configuration;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ListView;
 
-import android.accessibilityservice.cts.R;
-
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class performs end-to-end testing of the accessibility feature by
@@ -51,6 +73,14 @@
 
     private static final String LOG_TAG = "AccessibilityEndToEndTest";
 
+    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget grantbind --package android.accessibilityservice.cts --user 0";
+
+    private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget revokebind --package android.accessibilityservice.cts --user 0";
+
+    private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz";
+
     /**
      * Creates a new instance for testing {@link AccessibilityEndToEndActivity}.
      */
@@ -59,6 +89,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeViewSelectedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
@@ -100,6 +131,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeViewClickedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
@@ -136,6 +168,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeViewLongClickedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
@@ -172,6 +205,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeViewFocusedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
@@ -210,6 +244,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeViewTextChangedAccessibilityEvent() throws Throwable {
         // focus the edit text
         final EditText editText = (EditText) getActivity().findViewById(R.id.edittext);
@@ -278,6 +313,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
@@ -315,6 +351,7 @@
 
     @MediumTest
     @SuppressWarnings("deprecation")
+    @Presubmit
     public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable {
         // No notification UI on televisions.
         if ((getActivity().getResources().getConfiguration().uiMode
@@ -430,6 +467,226 @@
         }
     }
 
+    @MediumTest
+    public void testPackageNameCannotBeFaked() throws Exception {
+        getActivity().runOnUiThread(() -> {
+            // Set the activity to report fake package for events and nodes
+            getActivity().setReportedPackageName("foo.bar.baz");
+
+            // Make sure node package cannot be faked
+            AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+                    .getRootInActiveWindow();
+            assertPackageName(root, getActivity().getPackageName());
+        });
+
+        // Make sure event package cannot be faked
+        try {
+            getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
+                getInstrumentation().runOnMainSync(() ->
+                    getActivity().findViewById(R.id.button).requestFocus())
+                , (AccessibilityEvent event) ->
+                    event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+                            && event.getPackageName().equals(getActivity().getPackageName())
+                , TIMEOUT_ASYNC_PROCESSING);
+        } catch (TimeoutException e) {
+            fail("Events from fake package should be fixed to use the correct package");
+        }
+    }
+
+    @MediumTest
+    @Presubmit
+    public void testPackageNameCannotBeFakedAppWidget() throws Exception {
+        if (!hasAppWidgets()) {
+            return;
+        }
+
+        getInstrumentation().runOnMainSync(() -> {
+            // Set the activity to report fake package for events and nodes
+            getActivity().setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE);
+
+            // Make sure we cannot report nodes as if from the widget package
+            AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+                    .getRootInActiveWindow();
+            assertPackageName(root, getActivity().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())
+                , (AccessibilityEvent event) ->
+                    event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+                            && event.getPackageName().equals(getActivity().getPackageName())
+                , TIMEOUT_ASYNC_PROCESSING);
+        } 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);
+        host.deleteHost();
+        host.startListening();
+
+        // Well, app do not have this permission unless explicitly granted
+        // by the user. Now we will pretend for the user and grant it.
+        grantBindAppWidgetPermission();
+
+        // Allocate an app widget id to bind.
+        final int appWidgetId = host.allocateAppWidgetId();
+        try {
+            // Grab a provider we defined to be bound.
+            final AppWidgetProviderInfo provider = getAppWidgetProviderInfo();
+
+            // Bind the widget.
+            final boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed(
+                    appWidgetId, provider.getProfile(), provider.provider, null);
+            assertTrue(widgetBound);
+
+            // Make sure the app can use the package of a widget it hosts
+            getInstrumentation().runOnMainSync(() -> {
+                // Make sure we can report nodes as if from the widget package
+                AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+                        .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())
+                    , (AccessibilityEvent event) ->
+                            event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED
+                                    && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE)
+                    , TIMEOUT_ASYNC_PROCESSING);
+            } catch (TimeoutException e) {
+                fail("Should be able to send events from a widget package if widget hosted");
+            }
+        } finally {
+            // Clean up.
+            host.deleteAppWidgetId(appWidgetId);
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    @MediumTest
+    @Presubmit
+    public void testViewHeadingReportedToAccessibility() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+        final EditText editText = (EditText) getOnMain(instrumentation, () -> {
+            return getActivity().findViewById(R.id.edittext);
+        });
+        // Make sure the edittext was populated properly from xml
+        final boolean editTextIsHeading = getOnMain(instrumentation, () -> {
+            return editText.isAccessibilityHeading();
+        });
+        assertTrue("isAccessibilityHeading not populated properly from xml", editTextIsHeading);
+
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        final AccessibilityNodeInfo editTextNode = uiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/edittext")
+                .get(0);
+        assertTrue("isAccessibilityHeading not reported to accessibility",
+                editTextNode.isHeading());
+
+        uiAutomation.executeAndWaitForEvent(() -> instrumentation.runOnMainSync(() ->
+                        editText.setAccessibilityHeading(false)),
+                filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
+                TIMEOUT_ASYNC_PROCESSING);
+        editTextNode.refresh();
+        assertFalse("isAccessibilityHeading not reported to accessibility after update",
+                editTextNode.isHeading());
+    }
+
+    @MediumTest
+    @Presubmit
+    public void testTooltipTextReportedToAccessibility() {
+        final Instrumentation instrumentation = getInstrumentation();
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button")
+                .get(0);
+        assertEquals("Tooltip text not reported to accessibility",
+                instrumentation.getContext().getString(R.string.button_tooltip),
+                buttonNode.getTooltipText());
+    }
+
+    @MediumTest
+    public void testTooltipTextActionsReportedToAccessibility() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button")
+                .get(0);
+        assertFalse(hasTooltip(R.id.button));
+        assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList()));
+        assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList())));
+        uiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction(
+                ACTION_SHOW_TOOLTIP.getId()),
+                filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
+                TIMEOUT_ASYNC_PROCESSING);
+
+        // The button should now be showing the tooltip, so it should have the option to hide it.
+        buttonNode.refresh();
+        assertThat(ACTION_HIDE_TOOLTIP, in(buttonNode.getActionList()));
+        assertThat(ACTION_SHOW_TOOLTIP, not(in(buttonNode.getActionList())));
+        assertTrue(hasTooltip(R.id.button));
+    }
+
+    private static void assertPackageName(AccessibilityNodeInfo node, String packageName) {
+        if (node == null) {
+            return;
+        }
+        assertEquals(packageName, node.getPackageName());
+        final int childCount = node.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            AccessibilityNodeInfo child = node.getChild(i);
+            if (child != null) {
+                assertPackageName(child, packageName);
+            }
+        }
+    }
+
+    private AppWidgetProviderInfo getAppWidgetProviderInfo() {
+        final ComponentName componentName = new ComponentName(
+                "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider");
+        final List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+        final int providerCount = providers.size();
+        for (int i = 0; i < providerCount; i++) {
+            final AppWidgetProviderInfo provider = providers.get(i);
+            if (componentName.equals(provider.provider)
+                    && Process.myUserHandle().equals(provider.getProfile())) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+    private void grantBindAppWidgetPermission() throws Exception {
+        ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+                GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+    private void revokeBindAppWidgetPermission() throws Exception {
+        ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+                REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+    private AppWidgetManager getAppWidgetManager() {
+        return (AppWidgetManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.APPWIDGET_SERVICE);
+    }
+
+    private boolean hasAppWidgets() {
+        return getInstrumentation().getTargetContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
+    }
+
     /**
      * Compares all properties of the <code>first</code> and the
      * <code>second</code>.
@@ -490,4 +747,15 @@
         }
         return true;
     }
+
+    private boolean hasTooltip(int id) {
+        return getOnMain(getInstrumentation(), () -> {
+            final View viewWithTooltip = getActivity().findViewById(id);
+            if (viewWithTooltip == null) {
+                return false;
+            }
+            final View tooltipView = viewWithTooltip.getTooltipView();
+            return (tooltipView != null) && (tooltipView.getParent() != null);
+        });
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
index 3eebff9..0e9c181 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
@@ -14,31 +14,33 @@
 
 package android.accessibilityservice.cts;
 
-import android.accessibilityservice.FingerprintGestureController;
-import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
-import android.app.Instrumentation;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.CancellationSignal;
-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 org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeastOnce;
-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 android.accessibilityservice.FingerprintGestureController;
+import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
+import android.app.Instrumentation;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+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 org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 /**
  * Verify that a service listening for fingerprint gestures gets called back when apps
  * use the fingerprint sensor to authenticate.
@@ -53,6 +55,11 @@
     FingerprintGestureController mFingerprintGestureController;
     CancellationSignal mCancellationSignal = new CancellationSignal();
 
+    @Rule
+    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+
+
     @Mock FingerprintManager.AuthenticationCallback mMockAuthenticationCallback;
     @Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
 
@@ -86,6 +93,8 @@
         if (!mIsHardwareAvailable) {
             return;
         }
+        // Launch an activity to make sure we're in the foreground
+        mActivityRule.launchActivity(null);
         mFingerprintGestureController.registerFingerprintGestureCallback(
                 mMockFingerprintGestureCallback, null);
         try {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java
deleted file mode 100644
index 62831a4..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java
+++ /dev/null
@@ -1,35 +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.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity 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 AccessibilityFocusAndInputFocusSyncActivity extends AccessibilityTestActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.accessibility_focus_and_input_focus_sync_test);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 08e231f..7cb569c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -17,15 +17,19 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+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.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
-import android.accessibilityservice.cts.R;
-
 import java.util.LinkedList;
 import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Test cases for testing the accessibility focus APIs exposed to accessibility
@@ -41,6 +45,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testFindAccessibilityFocus() throws Exception {
         getInstrumentation().runOnMainSync(() -> {
             getActivity().findViewById(R.id.firstEditText).requestFocus();
@@ -77,6 +82,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testInitialStateNoAccessibilityFocus() throws Exception {
         // Get the root which is only accessibility focused.
         AccessibilityNodeInfo focused = getInstrumentation().getUiAutomation()
@@ -114,6 +120,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testActionClearAccessibilityFocus() throws Exception {
         // Get the root linear layout info.
         final AccessibilityNodeInfo rootLinearLayout = getInstrumentation().getUiAutomation()
@@ -162,6 +169,7 @@
     }
 
     @MediumTest
+    @Presubmit
     public void testOnlyOneNodeHasAccessibilityFocus() throws Exception {
         // Get the first not focused edit text.
         final AccessibilityNodeInfo firstEditText = getInstrumentation().getUiAutomation()
@@ -228,4 +236,30 @@
             }
         }
     }
+
+    @Presubmit
+    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);
+        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);
+            secondButtonView.setScreenReaderFocusable(false);
+            isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable());
+        });
+
+        assertFalse("isScreenReaderFocusable did not change after value set",
+                isScreenReaderFocusableAtomic.get());
+
+        secondButton.refresh();
+        assertFalse(
+                "Screen reader focusability not propagated to accessibility after calling setter",
+                secondButton.isScreenReaderFocusable());
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index fd1a9fd..6ac693f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -15,7 +15,6 @@
 package android.accessibilityservice.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -32,8 +31,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import java.util.ArrayList;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,6 +39,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+
 /**
  * Verify that motion events are recognized as accessibility gestures.
  */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index aa30f77..60867cd 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -14,6 +14,17 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.AsyncUtils.awaitCancellation;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.diff;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
+import static android.accessibilityservice.cts.utils.GestureUtils.path;
+import static android.accessibilityservice.cts.utils.GestureUtils.times;
+
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.any;
 import static org.hamcrest.CoreMatchers.both;
@@ -21,19 +32,20 @@
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Matrix;
 import android.graphics.Path;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.MotionEvent;
@@ -41,6 +53,7 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.widget.TextView;
+
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -76,7 +89,6 @@
     final List<MotionEvent> mMotionEvents = new ArrayList<>();
     StubGestureAccessibilityService mService;
     MyTouchListener mMyTouchListener = new MyTouchListener();
-    MyGestureCallback mCallback;
     TextView mFullScreenTextView;
     int[] mViewLocation = new int[2];
     boolean mGotUpEvent;
@@ -111,7 +123,6 @@
         mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
 
         mMotionEvents.clear();
-        mCallback = new MyGestureCallback();
         mGotUpEvent = false;
     }
 
@@ -130,10 +141,8 @@
             return;
         }
 
-        Point clickPoint = new Point(10, 20);
-        GestureDescription click = createClickInViewBounds(clickPoint);
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(click, mCallback, null));
-        mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+        PointF clickPoint = new PointF(10, 20);
+        dispatch(clickWithinView(clickPoint), GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(any(MotionEvent.class), 2);
 
         assertEquals(2, mMotionEvents.size());
@@ -165,10 +174,8 @@
             return;
         }
 
-        Point clickPoint = new Point(10, 20);
-        GestureDescription longClick = createLongClickInViewBounds(clickPoint);
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(longClick, mCallback, null));
-        mCallback.assertGestureCompletes(
+        PointF clickPoint = new PointF(10, 20);
+        dispatch(longClickWithinView(clickPoint),
                 ViewConfiguration.getLongPressTimeout() + GESTURE_COMPLETION_TIMEOUT);
 
         waitForMotionEvents(any(MotionEvent.class), 2);
@@ -187,13 +194,12 @@
             return;
         }
 
-        Point startPoint = new Point(10, 20);
-        Point endPoint = new Point(20, 40);
+        PointF startPoint = new PointF(10, 20);
+        PointF endPoint = new PointF(20, 40);
         int gestureTime = 500;
 
-        GestureDescription swipe = createSwipeInViewBounds(startPoint, endPoint, gestureTime);
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(swipeWithinView(startPoint, endPoint, gestureTime),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(IS_ACTION_UP, 1);
 
         int numEvents = mMotionEvents.size();
@@ -210,30 +216,31 @@
             assertTrue(moveEvent.getEventTime() >= lastEventTime);
             float fractionOfSwipe =
                     ((float) (moveEvent.getEventTime() - downEvent.getEventTime())) / gestureTime;
-            float fractionX = ((float) (endPoint.x - startPoint.x)) * fractionOfSwipe + 0.5f;
-            float fractionY = ((float) (endPoint.y - startPoint.y)) * fractionOfSwipe + 0.5f;
-            Point intermediatePoint = new Point(startPoint);
-            intermediatePoint.offset((int) fractionX, (int) fractionY);
+            PointF intermediatePoint = add(startPoint,
+                    ceil(times(fractionOfSwipe, diff(endPoint, startPoint))));
             assertThat(moveEvent, both(IS_ACTION_MOVE).and(isAtPoint(intermediatePoint)));
             lastEventTime = moveEvent.getEventTime();
         }
     }
 
+    public void dispatch(GestureDescription gesture, int timeoutMs) {
+        await(dispatchGesture(mService, gesture), timeoutMs, MILLISECONDS);
+    }
+
     public void testSlowSwipe_shouldNotContainMovesForTinyMovement() throws InterruptedException {
         if (!mHasTouchScreen) {
             return;
         }
 
-        Point startPoint = new Point(10, 20);
-        Point intermediatePoint1 = new Point(10, 21);
-        Point intermediatePoint2 = new Point(11, 21);
-        Point intermediatePoint3 = new Point(11, 22);
-        Point endPoint = new Point(11, 22);
+        PointF startPoint = new PointF(10, 20);
+        PointF intermediatePoint1 = new PointF(10, 21);
+        PointF intermediatePoint2 = new PointF(11, 21);
+        PointF intermediatePoint3 = new PointF(11, 22);
+        PointF endPoint = new PointF(11, 22);
         int gestureTime = 1000;
 
-        GestureDescription swipe = createSwipeInViewBounds(startPoint, endPoint, gestureTime);
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(swipeWithinView(startPoint, endPoint, gestureTime),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(IS_ACTION_UP, 1);
 
         assertEquals(5, mMotionEvents.size());
@@ -249,16 +256,14 @@
             return;
         }
 
-        Point centerPoint = new Point(50, 60);
+        PointF centerPoint = new PointF(50, 60);
         int startSpacing = 100;
         int endSpacing = 50;
         int gestureTime = 500;
         float pinchTolerance = 2.0f;
 
-        GestureDescription pinch = createPinchInViewBounds(centerPoint, startSpacing,
-                endSpacing, 45.0F, gestureTime);
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(pinch, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(pinchWithinView(centerPoint, startSpacing, endSpacing, 45.0F, gestureTime),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(IS_ACTION_UP, 1);
         int numEvents = mMotionEvents.size();
 
@@ -323,13 +328,10 @@
             magRegionCenterPoint.set(magnificationController.getCenterX(),
                     magnificationController.getCenterY());
         });
-        final PointF magRegionOffsetPoint = new PointF();
-        magRegionOffsetPoint.set(magRegionCenterPoint);
-        magRegionOffsetPoint.offset(CLICK_OFFSET_X, CLICK_OFFSET_Y);
+        final PointF magRegionOffsetPoint
+                = add(magRegionCenterPoint, CLICK_OFFSET_X, CLICK_OFFSET_Y);
 
-        final PointF magRegionOffsetClickPoint = new PointF();
-        magRegionOffsetClickPoint.set(magRegionCenterPoint);
-        magRegionOffsetClickPoint.offset(
+        final PointF magRegionOffsetClickPoint = add(magRegionCenterPoint,
                 CLICK_OFFSET_X * MAGNIFICATION_FACTOR, CLICK_OFFSET_Y * MAGNIFICATION_FACTOR);
 
         try {
@@ -341,16 +343,16 @@
             assertTrue("Failed to set scale", setScale.get());
 
             // Click in the center of the magnification region
-            GestureDescription magRegionCenterClick = createClick(magRegionCenterPoint);
-            mService.runOnServiceSync(() -> mService.doDispatchGesture(
-                    magRegionCenterClick, mCallback, null));
-            mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+            dispatch(new GestureDescription.Builder()
+                    .addStroke(click(magRegionCenterPoint))
+                    .build(),
+                    GESTURE_COMPLETION_TIMEOUT);
 
             // Click at a slightly offset point
-            GestureDescription magRegionOffsetClick = createClick(magRegionOffsetClickPoint);
-            mService.runOnServiceSync(() -> mService.doDispatchGesture(
-                    magRegionOffsetClick, mCallback, null));
-            mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+            dispatch(new GestureDescription.Builder()
+                    .addStroke(click(magRegionOffsetClickPoint))
+                    .build(),
+                    GESTURE_COMPLETION_TIMEOUT);
             waitForMotionEvents(any(MotionEvent.class), 4);
         } finally {
             // Reset magnification
@@ -386,30 +388,25 @@
             return;
         }
 
-        Point start = new Point(10, 20);
-        Point mid1 = new Point(20, 20);
-        Point mid2 = new Point(20, 25);
-        Point end = new Point(20, 30);
+        PointF start = new PointF(10, 20);
+        PointF mid1 = new PointF(20, 20);
+        PointF mid2 = new PointF(20, 25);
+        PointF end = new PointF(20, 30);
         int gestureTime = 500;
 
         StrokeDescription s1 = new StrokeDescription(
-                linePathInViewBounds(start, mid1), 0, gestureTime, true);
+                lineWithinView(start, mid1), 0, gestureTime, true);
         StrokeDescription s2 = s1.continueStroke(
-                linePathInViewBounds(mid1, mid2), 0, gestureTime, true);
+                lineWithinView(mid1, mid2), 0, gestureTime, true);
         StrokeDescription s3 = s2.continueStroke(
-                linePathInViewBounds(mid2, end), 0, gestureTime, false);
+                lineWithinView(mid2, end), 0, gestureTime, false);
+
         GestureDescription gesture1 = new GestureDescription.Builder().addStroke(s1).build();
         GestureDescription gesture2 = new GestureDescription.Builder().addStroke(s2).build();
         GestureDescription gesture3 = new GestureDescription.Builder().addStroke(s3).build();
-
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
-        mCallback.reset();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
-        mCallback.reset();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture3, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(gesture1, gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(gesture2, gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        dispatch(gesture3, gestureTime + GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(IS_ACTION_UP, 1);
 
         assertThat(mMotionEvents.get(0), allOf(IS_ACTION_DOWN, isAtPoint(start)));
@@ -425,25 +422,24 @@
             return;
         }
 
-        Point startPoint = new Point(10, 20);
-        Point midPoint = new Point(20, 20);
-        Point endPoint = new Point(20, 30);
+        PointF startPoint = new PointF(10, 20);
+        PointF midPoint = new PointF(20, 20);
+        PointF endPoint = new PointF(20, 30);
         int gestureTime = 500;
 
-        StrokeDescription stroke1 = new StrokeDescription(
-                linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
-        GestureDescription gesture1 = new GestureDescription.Builder().addStroke(stroke1).build();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        StrokeDescription stroke1 =
+                new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
+        dispatch(new GestureDescription.Builder().addStroke(stroke1).build(),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
         waitForMotionEvents(both(IS_ACTION_MOVE).and(isAtPoint(midPoint)), 1);
 
-        StrokeDescription stroke2 = stroke1.continueStroke(
-                linePathInViewBounds(endPoint, midPoint), 0, gestureTime, false);
-        GestureDescription gesture2 = new GestureDescription.Builder().addStroke(stroke2).build();
-        mCallback.reset();
+        StrokeDescription stroke2 =
+                stroke1.continueStroke(lineWithinView(endPoint, midPoint), 0, gestureTime, false);
         mMotionEvents.clear();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
-        mCallback.assertGestureCancels(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        awaitCancellation(
+                dispatchGesture(mService,
+                        new GestureDescription.Builder().addStroke(stroke2).build()),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT, MILLISECONDS);
 
         waitForMotionEvents(IS_ACTION_CANCEL, 1);
         assertEquals(1, mMotionEvents.size());
@@ -454,23 +450,20 @@
             return;
         }
 
-        Point startPoint = new Point(10, 20);
-        Point midPoint = new Point(20, 20);
-        Point endPoint = new Point(20, 30);
+        PointF startPoint = new PointF(10, 20);
+        PointF midPoint = new PointF(20, 20);
+        PointF endPoint = new PointF(20, 30);
         int gestureTime = 500;
 
-        StrokeDescription stroke1 = new StrokeDescription(
-                linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
-        GestureDescription gesture1 = new GestureDescription.Builder().addStroke(stroke1).build();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        StrokeDescription stroke1 =
+                new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
+        dispatch(new GestureDescription.Builder().addStroke(stroke1).build(),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
 
-        StrokeDescription stroke2 = new StrokeDescription(
-                linePathInViewBounds(midPoint, endPoint), 0, gestureTime, false);
-        GestureDescription gesture2 = new GestureDescription.Builder().addStroke(stroke2).build();
-        mCallback.reset();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
-        mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        StrokeDescription stroke2 =
+                new StrokeDescription(lineWithinView(midPoint, endPoint), 0, gestureTime, false);
+        dispatch(new GestureDescription.Builder().addStroke(stroke2).build(),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT);
 
         waitForMotionEvents(IS_ACTION_UP, 1);
 
@@ -489,20 +482,20 @@
             return;
         }
 
-        Point startPoint = new Point(10, 20);
-        Point midPoint = new Point(20, 20);
-        Point endPoint = new Point(20, 30);
+        PointF startPoint = new PointF(10, 20);
+        PointF midPoint = new PointF(20, 20);
+        PointF endPoint = new PointF(20, 30);
         int gestureTime = 500;
 
-        StrokeDescription stroke1 = new StrokeDescription(
-                linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
+        StrokeDescription stroke1 =
+                new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
 
-        StrokeDescription stroke2 = stroke1.continueStroke(
-                linePathInViewBounds(midPoint, endPoint), 0, gestureTime, false);
-        GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke2).build();
-        mCallback.reset();
-        mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture, mCallback, null));
-        mCallback.assertGestureCancels(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+        StrokeDescription stroke2 =
+                stroke1.continueStroke(lineWithinView(midPoint, endPoint), 0, gestureTime, false);
+        awaitCancellation(
+                dispatchGesture(mService,
+                        new GestureDescription.Builder().addStroke(stroke2).build()),
+                gestureTime + GESTURE_COMPLETION_TIMEOUT, MILLISECONDS);
     }
 
     public static class GestureDispatchActivity extends AccessibilityTestActivity {
@@ -517,52 +510,6 @@
         }
     }
 
-    public static class MyGestureCallback extends AccessibilityService.GestureResultCallback {
-        private boolean mCompleted;
-        private boolean mCancelled;
-
-        @Override
-        public synchronized void onCompleted(GestureDescription gestureDescription) {
-            mCompleted = true;
-            notifyAll();
-        }
-
-        @Override
-        public synchronized void onCancelled(GestureDescription gestureDescription) {
-            mCancelled = true;
-            notifyAll();
-        }
-
-        public synchronized void assertGestureCompletes(long timeout) {
-            if (mCompleted) {
-                return;
-            }
-            try {
-                wait(timeout);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-            assertTrue("Gesture did not complete. Canceled = " + mCancelled, mCompleted);
-        }
-
-        public synchronized void assertGestureCancels(long timeout) {
-            if (mCancelled) {
-                return;
-            }
-            try {
-                wait(timeout);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-            assertTrue("Gesture did not cancel. Completed = " + mCompleted, mCancelled);
-        }
-
-        public synchronized void reset() {
-            mCancelled = false;
-            mCompleted = false;
-        }
-    }
-
     private void waitForMotionEvents(Matcher<MotionEvent> matcher, int numEventsExpected)
             throws InterruptedException {
         synchronized (mMotionEvents) {
@@ -607,53 +554,38 @@
         }
     }
 
-    private GestureDescription createClickInViewBounds(Point clickPoint) {
-        Point offsetClick = new Point(clickPoint);
-        offsetClick.offset(mViewLocation[0], mViewLocation[1]);
-        return createClick(offsetClick);
-    }
-
-    private GestureDescription createClick(Point clickPoint) {
-        return createClick(new PointF(clickPoint.x, clickPoint.y));
-    }
-
-    private GestureDescription createClick(PointF clickPoint) {
-        Path clickPath = new Path();
-        clickPath.moveTo(clickPoint.x, clickPoint.y);
-        StrokeDescription clickStroke =
-                new StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout());
-        GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
-        clickBuilder.addStroke(clickStroke);
-        return clickBuilder.build();
-    }
-
-    private GestureDescription createLongClickInViewBounds(Point clickPoint) {
-        Point offsetPoint = new Point(clickPoint);
-        offsetPoint.offset(mViewLocation[0], mViewLocation[1]);
-        Path clickPath = new Path();
-        clickPath.moveTo(offsetPoint.x, offsetPoint.y);
-        int longPressTime = ViewConfiguration.getLongPressTimeout();
-
-        StrokeDescription longClickStroke =
-                new StrokeDescription(clickPath, 0, longPressTime + (longPressTime / 2));
-        GestureDescription.Builder longClickBuilder = new GestureDescription.Builder();
-        longClickBuilder.addStroke(longClickStroke);
-        return longClickBuilder.build();
-    }
-
-    private GestureDescription createSwipeInViewBounds(Point start, Point end, long duration) {
-        return new GestureDescription.Builder().addStroke(
-                new StrokeDescription(linePathInViewBounds(start, end), 0, duration, false))
+    private GestureDescription clickWithinView(PointF clickPoint) {
+        return new GestureDescription.Builder()
+                .addStroke(click(withinView(clickPoint)))
                 .build();
     }
 
-    private GestureDescription createPinchInViewBounds(Point centerPoint, int startSpacing,
+    private GestureDescription longClickWithinView(PointF clickPoint) {
+        return new GestureDescription.Builder()
+                .addStroke(longClick(withinView(clickPoint)))
+                .build();
+    }
+
+    private PointF withinView(PointF clickPoint) {
+        return add(clickPoint, mViewLocation[0], mViewLocation[1]);
+    }
+
+    private GestureDescription swipeWithinView(PointF start, PointF end, long duration) {
+        return new GestureDescription.Builder()
+                .addStroke(new StrokeDescription(lineWithinView(start, end), 0, duration))
+                .build();
+    }
+
+    private Path lineWithinView(PointF startPoint, PointF endPoint) {
+        return path(withinView(startPoint), withinView(endPoint));
+    }
+
+    private GestureDescription pinchWithinView(PointF centerPoint, int startSpacing,
             int endSpacing, float orientation, long duration) {
         if ((startSpacing < 0) || (endSpacing < 0)) {
             throw new IllegalArgumentException("Pinch spacing cannot be negative");
         }
-        Point offsetCenter = new Point(centerPoint);
-        offsetCenter.offset(mViewLocation[0], mViewLocation[1]);
+        PointF offsetCenter = withinView(centerPoint);
         float[] startPoint1 = new float[2];
         float[] endPoint1 = new float[2];
         float[] startPoint2 = new float[2];
@@ -685,19 +617,10 @@
         path2.moveTo(startPoint2[0], startPoint2[1]);
         path2.lineTo(endPoint2[0], endPoint2[1]);
 
-        StrokeDescription path1Stroke = new StrokeDescription(path1, 0, duration);
-        StrokeDescription path2Stroke = new StrokeDescription(path2, 0, duration);
-        GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
-        swipeBuilder.addStroke(path1Stroke);
-        swipeBuilder.addStroke(path2Stroke);
-        return swipeBuilder.build();
-    }
-
-    Path linePathInViewBounds(Point startPoint, Point endPoint) {
-        Path path = new Path();
-        path.moveTo(startPoint.x + mViewLocation[0], startPoint.y + mViewLocation[1]);
-        path.lineTo(endPoint.x + mViewLocation[0], endPoint.y + mViewLocation[1]);
-        return path;
+        return new GestureDescription.Builder()
+                .addStroke(new StrokeDescription(path1, 0, duration))
+                .addStroke(new StrokeDescription(path2, 0, duration))
+                .build();
     }
 
     private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
@@ -715,13 +638,13 @@
 
         @Override
         public void describeTo(Description description) {
-            description.appendText("Matching to action " + mAction);
+            description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
         }
     }
 
 
-    Matcher<MotionEvent> isAtPoint(final Point point) {
-        return isAtPoint(new PointF(point.x, point.y), 0.01f);
+    Matcher<MotionEvent> isAtPoint(final PointF point) {
+        return isAtPoint(point, 0.01f);
     }
 
     Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
index 486e4fa..95e4753 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -18,6 +18,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -26,6 +27,7 @@
 /**
  * Test global actions
  */
+@Presubmit
 public class AccessibilityGlobalActionsTest extends InstrumentationTestCase {
     /**
      * Timeout required for pending Binder calls or event processing to
@@ -134,6 +136,16 @@
         waitForIdle();
     }
 
+    @MediumTest
+    public void testPerformActionScreenshot() throws Exception {
+        // Action should succeed
+        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+                AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT));
+        // Ideally should verify that we actually have a screenshot, but it's also possible
+        // for the screenshot to fail
+        waitForIdle();
+    }
+
     private void waitForIdle() throws TimeoutException {
         getInstrumentation().getUiAutomation().waitForIdle(
                 TIMEOUT_ACCESSIBILITY_STATE_IDLE,
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
new file mode 100644
index 0000000..9299807
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.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 android.accessibilityservice.cts;
+
+import static android.app.AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AccessibilityLoggingTest {
+
+    /**
+     * Tests that new accessibility services are logged by the system.
+     */
+    @Test
+    public void testServiceLogged() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        String packageName = context.getPackageName();
+
+        // There are accessibility services defined in this test package, and this fact must be
+        // logged.
+        assertTrue("Accessibility service was bound, but this wasn't logged by app ops",
+                AppOpsUtils.allowedOperationLogged(packageName, OPSTR_BIND_ACCESSIBILITY_SERVICE));
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 1abb2ab..8446702 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -16,22 +16,25 @@
 
 package android.accessibilityservice.cts;
 
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import android.accessibilityservice.AccessibilityService.MagnificationController;
 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Region;
-import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.mockito.Mockito.*;
-
 /**
  * Class for testing {@link AccessibilityServiceInfo}.
  */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
new file mode 100644
index 0000000..1eedee1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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.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;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+import static org.hamcrest.Matchers.both;
+import static org.junit.Assert.assertEquals;
+
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.app.UiAutomation.AccessibilityEventFilter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests reporting of window-like views
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityPaneTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private Activity mActivity;
+    private View mPaneView;
+
+    @Rule
+    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+        sInstrumentation.runOnMainSync(() ->  {
+            mPaneView = mActivity.findViewById(R.id.button);
+        });
+    }
+
+    @Test
+    public void paneTitleFromXml_reportedToAccessibility() {
+        String paneTitle = sInstrumentation.getContext().getString(R.string.paneTitle);
+        assertEquals(paneTitle, mPaneView.getAccessibilityPaneTitle());
+        AccessibilityNodeInfo paneNode = getPaneNode();
+        assertEquals(paneTitle, paneNode.getPaneTitle());
+    }
+
+    @Test
+    public void windowLikeViewSettersWork_andNewValuesReportedToAccessibility() throws Exception {
+        final String newTitle = "Here's a new title";
+
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> {
+            mPaneView.setAccessibilityPaneTitle(newTitle);
+            assertEquals(newTitle, mPaneView.getAccessibilityPaneTitle());
+        }), (new ContentChangesMatcher(CONTENT_CHANGE_TYPE_PANE_TITLE))::matches,
+                TIMEOUT_ASYNC_PROCESSING);
+
+        AccessibilityNodeInfo windowLikeNode = getPaneNode();
+        assertEquals(newTitle, windowLikeNode.getPaneTitle());
+    }
+
+    @Test
+    public void windowLikeViewVisibility_reportAsWindowStateChanges() throws Exception {
+        final AccessibilityEventFilter paneAppearsFilter =
+                both(new AccessibilityEventTypeMatcher(TYPE_WINDOW_STATE_CHANGED)).and(
+                        new ContentChangesMatcher(CONTENT_CHANGE_TYPE_PANE_APPEARED))::matches;
+        final AccessibilityEventFilter paneDisappearsFilter =
+                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);
+
+        sUiAutomation.executeAndWaitForEvent(setPaneViewVisibility(View.VISIBLE),
+                paneAppearsFilter, TIMEOUT_ASYNC_PROCESSING);
+
+        sUiAutomation.executeAndWaitForEvent(setPaneViewParentVisibility(View.GONE),
+                paneDisappearsFilter, TIMEOUT_ASYNC_PROCESSING);
+
+        sUiAutomation.executeAndWaitForEvent(setPaneViewParentVisibility(View.VISIBLE),
+                paneAppearsFilter, TIMEOUT_ASYNC_PROCESSING);
+    }
+
+    private AccessibilityNodeInfo getPaneNode() {
+        return sUiAutomation.getRootInActiveWindow().findAccessibilityNodeInfosByText(
+                ((TextView) mPaneView).getText().toString()).get(0);
+    }
+
+    private Runnable setPaneViewVisibility(int visibility) {
+        return () -> sInstrumentation.runOnMainSync(
+                () -> mPaneView.setVisibility(visibility));
+    }
+
+    private Runnable setPaneViewParentVisibility(int visibility) {
+        return () -> sInstrumentation.runOnMainSync(
+                () -> ((View) mPaneView.getParent()).setVisibility(visibility));
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index 1761117..b27ceaf 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -18,6 +18,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.accessibility.AccessibilityEvent;
@@ -25,6 +26,7 @@
 /**
  * Class for testing {@link AccessibilityServiceInfo}.
  */
+@Presubmit
 public class AccessibilityServiceInfoTest extends AndroidTestCase {
 
     @MediumTest
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
index ccbd1b5..6575034 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -29,6 +30,7 @@
  * This test case is responsible to verify that the intent for launching
  * accessibility settings has an activity that handles it.
  */
+@Presubmit
 public class AccessibilitySettingsTest extends AndroidTestCase {
 
     @MediumTest
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index eb8e0e8..c179409 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -14,9 +14,12 @@
 
 package android.accessibilityservice.cts;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.R;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.os.Bundle;
 import android.os.Handler;
@@ -26,8 +29,6 @@
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-
-import android.accessibilityservice.cts.R;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.inputmethod.InputMethodManager;
 
@@ -36,6 +37,7 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Test cases for testing the accessibility APIs for interacting with the soft keyboard show mode.
@@ -70,6 +72,8 @@
     private InstrumentedAccessibilityService mService;
     private SoftKeyboardController mKeyboardController;
     private UiAutomation mUiAutomation;
+    private Activity mActivity;
+    private View mKeyboardTargetView;
 
     private Object mLock = new Object();
 
@@ -83,7 +87,7 @@
 
         // If we don't call getActivity(), we get an empty list when requesting the number of
         // windows on screen.
-        getActivity();
+        mActivity = getActivity();
 
         mService = InstrumentedAccessibilityService.enableService(
                 getInstrumentation(), InstrumentedAccessibilityService.class);
@@ -93,6 +97,8 @@
         AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
         mUiAutomation.setServiceInfo(info);
+        getInstrumentation().runOnMainSync(
+                () -> mKeyboardTargetView = mActivity.findViewById(R.id.edit_text));
     }
 
     @Override
@@ -100,8 +106,11 @@
         mKeyboardController.setShowMode(SHOW_MODE_AUTO);
         mService.runOnServiceSync(() -> mService.disableSelf());
         Activity activity = getActivity();
-        activity.getSystemService(InputMethodManager.class)
-                .hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
+        View currentFocus = activity.getCurrentFocus();
+        if (currentFocus != null) {
+            activity.getSystemService(InputMethodManager.class)
+                    .hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
+        }
     }
 
     public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java
deleted file mode 100644
index 42a4375..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public abstract class AccessibilityTestActivity extends Activity {
-
-    @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);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index abc759c..115eb5f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -14,10 +14,23 @@
 
 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 org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.accessibilityservice.cts.R;
+import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
 import android.app.UiAutomation;
 import android.graphics.RectF;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Message;
 import android.os.Parcelable;
 import android.text.SpannableString;
@@ -33,27 +46,11 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
-import android.accessibilityservice.cts.R;
-
 import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
-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;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
 /**
  * Test cases for actions taken on text views.
  */
@@ -199,10 +196,14 @@
 
     public void testTextLocations_textViewShouldProvideWhenRequested() {
         final TextView textView = (TextView) getActivity().findViewById(R.id.text);
-        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+        // 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);
+        makeTextViewVisibleAndSetText(textView, stringToSet);
+        getInstrumentation().runOnMainSync(() -> textView.setAllCaps(true));
 
         final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+                .findAccessibilityNodeInfosByText(stringToSet).get(0);
         List<String> textAvailableExtraData = text.getAvailableExtraData();
         assertTrue("Text view should offer text location to accessibility",
                 textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
deleted file mode 100644
index 2b93a08..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity for testing the accessibility APIs for traversing the
- * text content of a View at several granularities.
- */
-public class AccessibilityTextTraversalActivity extends AccessibilityTestActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.accessibility_text_traversal_test);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 6bc2969..d0bab51 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -14,6 +14,8 @@
 
 package android.accessibilityservice.cts;
 
+import android.accessibilityservice.cts.R;
+import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
 import android.app.UiAutomation;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
@@ -26,8 +28,6 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
-import android.accessibilityservice.cts.R;
-
 /**
  * Test cases for testing the accessibility APIs for traversing the text content of
  * a View at several granularities.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java
deleted file mode 100644
index c28e7e8..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java
+++ /dev/null
@@ -1,35 +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.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity 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 hierarchical movement of the
- * accessibility focus.
- */
-public class AccessibilityViewTreeReportingActivity extends AccessibilityTestActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.accessibility_view_tree_reporting_test);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 305050b..720e23f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -14,114 +14,147 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils
+        .launchActivityAndWaitForItToBeOnscreen;
+
+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.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.R;
+import android.accessibilityservice.cts.activities.AccessibilityViewTreeReportingActivity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
-
-import android.accessibilityservice.cts.R;
 import android.widget.Button;
 import android.widget.LinearLayout;
 
+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. This test checks how the view hierarchy is reported to accessibility
  * services.
  */
-public class AccessibilityViewTreeReportingTest
-        extends AccessibilityActivityTestCase<AccessibilityViewTreeReportingActivity>{
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityViewTreeReportingTest {
+    private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
 
-    public AccessibilityViewTreeReportingTest() {
-        super(AccessibilityViewTreeReportingActivity.class);
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private AccessibilityViewTreeReportingActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityViewTreeReportingActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
     }
 
-    @MediumTest
+    @AfterClass
+    public static void finalTearDown() throws Exception {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = (AccessibilityViewTreeReportingActivity) launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+        setGetNonImportantViews(false);
+    }
+
+
+    @Test
     public void testDescendantsOfNotImportantViewReportedInOrder1() throws Exception {
-        UiAutomation uiAutomation = getUiAutomation(false);
-        AccessibilityNodeInfo firstFrameLayout =
-                getNodeByText(uiAutomation, R.string.firstFrameLayout);
+        AccessibilityNodeInfo firstFrameLayout = getNodeByText(R.string.firstFrameLayout);
         assertNotNull(firstFrameLayout);
         assertSame(3, firstFrameLayout.getChildCount());
 
         // Check if the first child is the right one.
-        AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
+        AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
         assertEquals(firstTextView, firstFrameLayout.getChild(0));
 
         // Check if the second child is the right one.
-        AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
+        AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
         assertEquals(firstEditText, firstFrameLayout.getChild(1));
 
         // Check if the third child is the right one.
-        AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
         assertEquals(firstButton, firstFrameLayout.getChild(2));
     }
 
-    @MediumTest
+    @Test
     public void testDescendantsOfNotImportantViewReportedInOrder2() throws Exception {
-        UiAutomation uiAutomation = getUiAutomation(false);
-        AccessibilityNodeInfo secondFrameLayout =
-                getNodeByText(uiAutomation, R.string.secondFrameLayout);
+        AccessibilityNodeInfo secondFrameLayout = getNodeByText(R.string.secondFrameLayout);
         assertNotNull(secondFrameLayout);
         assertSame(3, secondFrameLayout.getChildCount());
 
         // Check if the first child is the right one.
-        AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
+        AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
         assertEquals(secondTextView, secondFrameLayout.getChild(0));
 
         // Check if the second child is the right one.
-        AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
+        AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
         assertEquals(secondEditText, secondFrameLayout.getChild(1));
 
         // Check if the third child is the right one.
-        AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+        AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
         assertEquals(secondButton, secondFrameLayout.getChild(2));
     }
 
-    @MediumTest
+    @Test
     public void testDescendantsOfNotImportantViewReportedInOrder3() throws Exception {
-        UiAutomation uiAutomation = getUiAutomation(false);
         AccessibilityNodeInfo rootLinearLayout =
-                getNodeByText(uiAutomation, R.string.rootLinearLayout);
+                getNodeByText(R.string.rootLinearLayout);
         assertNotNull(rootLinearLayout);
         assertSame(4, rootLinearLayout.getChildCount());
 
         // Check if the first child is the right one.
         AccessibilityNodeInfo firstFrameLayout =
-                getNodeByText(uiAutomation, R.string.firstFrameLayout);
+                getNodeByText(R.string.firstFrameLayout);
         assertEquals(firstFrameLayout, rootLinearLayout.getChild(0));
 
         // Check if the second child is the right one.
-        AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
+        AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
         assertEquals(secondTextView, rootLinearLayout.getChild(1));
 
         // Check if the third child is the right one.
-        AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
+        AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
         assertEquals(secondEditText, rootLinearLayout.getChild(2));
 
         // Check if the fourth child is the right one.
-        AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+        AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
         assertEquals(secondButton, rootLinearLayout.getChild(3));
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderInImportantParentFollowsXmlOrder() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                getActivity().findViewById(R.id.firstLinearLayout)
-                        .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            }
-        });
+        sInstrumentation.runOnMainSync(() -> mActivity.findViewById(R.id.firstLinearLayout)
+                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
 
-        UiAutomation uiAutomation = getUiAutomation(false);
-        AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
-        AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
-        AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+        AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+        AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
 
         // Drawing order is: firstTextView, firstEditText, firstButton
         assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
@@ -133,202 +166,160 @@
         assertTrue(copyOfFirstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderGettingAllViewsFollowsXmlOrder() throws Exception {
-        UiAutomation uiAutomation = getUiAutomation(true);
-        AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
-        AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
-        AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+        setGetNonImportantViews(true);
+        AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+        AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+        AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
 
         // Drawing order is: firstTextView, firstEditText, firstButton
         assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
         assertTrue(firstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderWithZCoordsDrawsHighestZLast() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-           @Override
-           public void run() {
-               AccessibilityViewTreeReportingActivity activity = getActivity();
-               activity.findViewById(R.id.firstTextView).setZ(50);
-               activity.findViewById(R.id.firstEditText).setZ(100);
-           }
+        setGetNonImportantViews(true);
+        sInstrumentation.runOnMainSync(() -> {
+            mActivity.findViewById(R.id.firstTextView).setZ(50);
+            mActivity.findViewById(R.id.firstEditText).setZ(100);
         });
 
-        UiAutomation uiAutomation = getUiAutomation(true);
-        AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
-        AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
-        AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+        AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+        AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
 
         // Drawing order is firstButton (no z), firstTextView (z=50), firstEditText (z=100)
         assertTrue(firstButton.getDrawingOrder() < firstTextView.getDrawingOrder());
         assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderWithCustomDrawingOrder() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Reorganize the hiearchy to replace firstLinearLayout with one that allows us to
-                // control the draw order
-                AccessibilityViewTreeReportingActivity activity = getActivity();
-                LinearLayout rootLinearLayout =
-                        (LinearLayout) activity.findViewById(R.id.rootLinearLayout);
-                LinearLayout firstLinearLayout =
-                        (LinearLayout) activity.findViewById(R.id.firstLinearLayout);
-                View firstTextView = activity.findViewById(R.id.firstTextView);
-                View firstEditText = activity.findViewById(R.id.firstEditText);
-                View firstButton = activity.findViewById(R.id.firstButton);
-                firstLinearLayout.removeAllViews();
-                LinearLayoutWithDrawingOrder layoutWithDrawingOrder =
-                        new LinearLayoutWithDrawingOrder(activity);
-                rootLinearLayout.addView(layoutWithDrawingOrder);
-                layoutWithDrawingOrder.addView(firstTextView);
-                layoutWithDrawingOrder.addView(firstEditText);
-                layoutWithDrawingOrder.addView(firstButton);
-                layoutWithDrawingOrder.childDrawingOrder = new int[] {2, 0, 1};
-            }
+        setGetNonImportantViews(true);
+        sInstrumentation.runOnMainSync(() -> {
+            // Reorganize the hiearchy to replace firstLinearLayout with one that allows us to
+            // control the draw order
+            LinearLayout rootLinearLayout =
+                    (LinearLayout) mActivity.findViewById(R.id.rootLinearLayout);
+            LinearLayout firstLinearLayout =
+                    (LinearLayout) mActivity.findViewById(R.id.firstLinearLayout);
+            View firstTextView = mActivity.findViewById(R.id.firstTextView);
+            View firstEditText = mActivity.findViewById(R.id.firstEditText);
+            View firstButton = mActivity.findViewById(R.id.firstButton);
+            firstLinearLayout.removeAllViews();
+            LinearLayoutWithDrawingOrder layoutWithDrawingOrder =
+                    new LinearLayoutWithDrawingOrder(mActivity);
+            rootLinearLayout.addView(layoutWithDrawingOrder);
+            layoutWithDrawingOrder.addView(firstTextView);
+            layoutWithDrawingOrder.addView(firstEditText);
+            layoutWithDrawingOrder.addView(firstButton);
+            layoutWithDrawingOrder.childDrawingOrder = new int[] {2, 0, 1};
         });
 
-        UiAutomation uiAutomation = getUiAutomation(true);
-        AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
-        AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
-        AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+        AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+        AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
 
         // Drawing order is firstEditText, firstButton, firstTextView
         assertTrue(firstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
         assertTrue(firstButton.getDrawingOrder() < firstTextView.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderWithNotImportantSiblingConsidersItsChildren() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Make the first frame layout a higher Z so it's drawn last
-                getActivity().findViewById(R.id.firstFrameLayout).setZ(100);
-            }
-        });
-        UiAutomation uiAutomation = getUiAutomation(false);
-        AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
-        AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
-        AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
-        AccessibilityNodeInfo firstFrameLayout =
-                getNodeByText(uiAutomation, R.string.firstFrameLayout);
+        // Make the first frame layout a higher Z so it's drawn last
+        sInstrumentation.runOnMainSync(
+                () -> mActivity.findViewById(R.id.firstFrameLayout).setZ(100));
+        AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
+        AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
+        AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
+        AccessibilityNodeInfo firstFrameLayout = getNodeByText( R.string.firstFrameLayout);
         assertTrue(secondTextView.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
         assertTrue(secondEditText.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
         assertTrue(secondButton.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderWithNotImportantParentConsidersParentSibling() throws Exception {
-        UiAutomation uiAutomation = getUiAutomation(false);
-        AccessibilityNodeInfo firstFrameLayout =
-                getNodeByText(uiAutomation, R.string.firstFrameLayout);
-        AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
-        AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
-        AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+        AccessibilityNodeInfo firstFrameLayout = getNodeByText(R.string.firstFrameLayout);
+        AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
+        AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
+        AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
 
         assertTrue(secondTextView.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
         assertTrue(secondEditText.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
         assertTrue(secondButton.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testDrawingOrderRootNodeHasIndex0() throws Exception {
-        assertEquals(0, getUiAutomation(false).getRootInActiveWindow().getDrawingOrder());
+        assertEquals(0, sUiAutomation.getRootInActiveWindow().getDrawingOrder());
     }
 
-    @MediumTest
+    @Test
     public void testAccessibilityImportanceReportingForImportantView() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Manually control importance for firstButton
-                AccessibilityViewTreeReportingActivity activity = getActivity();
-                View firstButton = activity.findViewById(R.id.firstButton);
-                firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            }
+        setGetNonImportantViews(true);
+        sInstrumentation.runOnMainSync(() -> {
+            // Manually control importance for firstButton
+            View firstButton = mActivity.findViewById(R.id.firstButton);
+            firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         });
 
-        UiAutomation uiAutomation = getUiAutomation(true);
-        AccessibilityNodeInfo firstButtonNode = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstButtonNode = getNodeByText(R.string.firstButton);
         assertTrue(firstButtonNode.isImportantForAccessibility());
     }
 
-    @MediumTest
+    @Test
     public void testAccessibilityImportanceReportingForUnimportantView() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                // Manually control importance for firstButton
-                AccessibilityViewTreeReportingActivity activity = getActivity();
-                View firstButton = activity.findViewById(R.id.firstButton);
-                firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-            }
+        setGetNonImportantViews(true);
+        sInstrumentation.runOnMainSync(() -> {
+            View firstButton = mActivity.findViewById(R.id.firstButton);
+            firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         });
 
-        UiAutomation uiAutomation = getUiAutomation(true);
-        AccessibilityNodeInfo firstButtonNode = getNodeByText(uiAutomation, R.string.firstButton);
+        AccessibilityNodeInfo firstButtonNode = getNodeByText(R.string.firstButton);
         assertFalse(firstButtonNode.isImportantForAccessibility());
     }
 
-    @MediumTest
+    @Test
     public void testAddViewToLayout_receiveSubtreeEvent() throws Throwable {
         final LinearLayout layout =
-                (LinearLayout) getActivity().findViewById(R.id.secondLinearLayout);
-        final Button newButton = new Button(getActivity());
+                (LinearLayout) mActivity.findViewById(R.id.secondLinearLayout);
+        final Button newButton = new Button(mActivity);
         newButton.setText("New Button");
         newButton.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
         newButton.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
         AccessibilityEvent awaitedEvent =
-                getInstrumentation().getUiAutomation().executeAndWaitForEvent(
-                        new Runnable() {
-                            @Override
-                            public void run() {
-                                // trigger the event
-                                getActivity().runOnUiThread(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        layout.addView(newButton);
-                                    }
-                                });
-                            }},
-                        new UiAutomation.AccessibilityEventFilter() {
-                            // check the received event
-                            @Override
-                            public boolean accept(AccessibilityEvent event) {
-                                boolean isContentChanged = event.getEventType()
-                                        == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-                                int isSubTree = (event.getContentChangeTypes()
-                                        & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-                                boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
-                                        getActivity().getPackageName());
-                                return isContentChanged && (isSubTree != 0) && isFromThisPackage;
-                            }
-                        },
-                        TIMEOUT_ASYNC_PROCESSING);
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> mActivity.runOnUiThread(() -> layout.addView(newButton)),
+                        (event) -> {
+                            boolean isContentChanged = event.getEventType()
+                                    == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+                            int isSubTree = (event.getContentChangeTypes()
+                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                            boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+                                    mActivity.getPackageName());
+                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+                        }, TIMEOUT_ASYNC_PROCESSING);
         // The event should come from a view that's important for accessibility, even though the
         // layout we added it to isn't important. Otherwise services may not find out about the
         // new button.
         assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
     }
 
-    private UiAutomation getUiAutomation(boolean getNonImportantViews) {
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        AccessibilityServiceInfo serviceInfo = uiAutomation.getServiceInfo();
+    private void setGetNonImportantViews(boolean getNonImportantViews) {
+        AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
         serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
         serviceInfo.flags |= getNonImportantViews ?
                 AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
-        uiAutomation.setServiceInfo(serviceInfo);
-        return uiAutomation;
+        sUiAutomation.setServiceInfo(serviceInfo);
     }
 
-    private AccessibilityNodeInfo getNodeByText(UiAutomation uiAutomation, int stringId) {
-        return uiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(stringId)).get(0);
+    private AccessibilityNodeInfo getNodeByText(int stringId) {
+        return sUiAutomation.getRootInActiveWindow().findAccessibilityNodeInfosByText(
+                sInstrumentation.getContext().getString(stringId)).get(0);
     }
 
     class LinearLayoutWithDrawingOrder extends LinearLayout {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
index b67fc28..c718718 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -13,18 +13,21 @@
  */
 package android.accessibilityservice.cts;
 
+import static android.content.Context.AUDIO_SERVICE;
+
+import static org.junit.Assert.assertEquals;
+
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
-import android.media.AudioManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static android.content.Context.AUDIO_SERVICE;
-import static org.junit.Assert.assertEquals;
-
 /**
  * Verify that accessibility services can control the accessibility volume.
  */
@@ -47,6 +50,7 @@
     }
 
     @Test
+    @Presubmit
     public void testChangeAccessibilityVolume_outsideValidAccessibilityService_shouldFail() {
         if (mSingleVolume) {
             return;
@@ -63,16 +67,22 @@
         if (mSingleVolume) {
             return;
         }
-        int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
-        int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
-        InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                mInstrumentation, InstrumentedAccessibilityService.class);
-
-        service.runOnServiceSync(() ->
-                mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume, 0));
-        assertEquals("Accessibility service should be able to change accessibility volume",
-                otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
-        service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
-                AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
+        final int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
+        final int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
+        final InstrumentedAccessibilityService service = InstrumentedAccessibilityService
+                .enableService(mInstrumentation, InstrumentedAccessibilityService.class);
+        try {
+            service.runOnServiceSync(() ->
+                    mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume,
+                            0));
+            assertEquals("Accessibility service should be able to change accessibility volume",
+                    otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
+            service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+                    AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
+        } finally {
+            if (service != null) {
+                service.runOnServiceSync(() -> service.disableSelf());
+            }
+        }
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
deleted file mode 100644
index 9070a81..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import android.os.Bundle;
-import android.view.View;
-
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity 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.
- */
-public class AccessibilityWindowQueryActivity extends AccessibilityTestActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.query_window_test);
-
-        findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
-            public void onClick(View v) {
-                /* do nothing */
-            }
-        });
-        findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
-            public boolean onLongClick(View v) {
-                return true;
-            }
-        });
-
-        findViewById(R.id.button5).setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
-            }
-
-            @Override
-            public boolean performAccessibilityAction(View host, int action, Bundle args) {
-                if (action == R.id.foo_custom_action) {
-                    return true;
-                }
-                return super.performAccessibilityAction(host, action, args);
-            }
-        });
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 18c02e5..6c77471 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -16,7 +16,17 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+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;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -26,8 +36,10 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
 import android.app.ActivityManager;
 import android.app.UiAutomation;
+import android.app.UiAutomation.AccessibilityEventFilter;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -35,7 +47,6 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.Gravity;
 import android.view.View;
-import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -63,23 +74,16 @@
     private static String CONTENT_VIEW_RES_NAME =
             "android.accessibilityservice.cts:id/added_content";
     private static final long TIMEOUT_WINDOW_STATE_IDLE = 500;
-    private final UiAutomation.AccessibilityEventFilter mWindowsChangedFilter =
-            new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED);
-                }
-            };
-    private final UiAutomation.AccessibilityEventFilter mDividerPresentFilter =
-            new UiAutomation.AccessibilityEventFilter() {
+    private final AccessibilityEventFilter mDividerPresentFilter =
+            new AccessibilityEventFilter() {
                 @Override
                 public boolean accept(AccessibilityEvent event) {
                     return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
                             isDividerWindowPresent(getInstrumentation().getUiAutomation())    );
                 }
             };
-    private final UiAutomation.AccessibilityEventFilter mDividerAbsentFilter =
-            new UiAutomation.AccessibilityEventFilter() {
+    private final AccessibilityEventFilter mDividerAbsentFilter =
+            new AccessibilityEventFilter() {
                 @Override
                 public boolean accept(AccessibilityEvent event) {
                     return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
@@ -95,13 +99,8 @@
     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(new Runnable() {
-            @Override
-            public void run() {
-                getActivity().findViewById(R.id.added_content)
-                    .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            }
-        });
+        runTestOnUiThread(() -> getActivity().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
@@ -146,30 +145,12 @@
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/button1").get(0);
 
-        // Argh...
-        final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
-        // Click the button.
-        uiAutomation.executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
-            }
-        },
-        new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
-                    events.add(event);
-                    return true;
-                }
-                return false;
-            }
-        },
-        TIMEOUT_ASYNC_PROCESSING);
+        // Click the button to generate an event
+        AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+                () -> button1.performAction(ACTION_CLICK),
+                filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
 
         // Make sure the source window cannot be accessed.
-        AccessibilityEvent event = events.get(0);
         assertNull(event.getSource().getWindow());
     }
 
@@ -218,30 +199,12 @@
                     .findAccessibilityNodeInfosByViewId(
                             "android.accessibilityservice.cts:id/button1").get(0);
 
-            // Argh...
-            final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
             // Click the button.
-            uiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
-                }
-            },
-            new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
-                        events.add(event);
-                        return true;
-                    }
-                    return false;
-                }
-            },
-            TIMEOUT_ASYNC_PROCESSING);
+            AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+                    () -> button1.performAction(ACTION_CLICK),
+                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
 
             // Get the source window.
-            AccessibilityEvent event = events.get(0);
             AccessibilityWindowInfo window = event.getSource().getWindow();
 
             // Verify the application window.
@@ -273,30 +236,12 @@
                     .findAccessibilityNodeInfosByViewId(
                             "android.accessibilityservice.cts:id/button1").get(0);
 
-            // Argh...
-            final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
             // Click the button.
-            uiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
-                }
-            },
-            new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
-                        events.add(event);
-                        return true;
-                    }
-                    return false;
-                }
-            },
-            TIMEOUT_ASYNC_PROCESSING);
+            AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+                    () -> button1.performAction(ACTION_CLICK),
+                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
 
             // Get the source window.
-            AccessibilityEvent event = events.get(0);
             AccessibilityWindowInfo window = event.getSource().getWindow();
 
             // Find a another button from the event's window.
@@ -305,19 +250,8 @@
                             "android.accessibilityservice.cts:id/button2").get(0);
 
             // Click the second button.
-            uiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    button2.performAction(AccessibilityNodeInfo.ACTION_CLICK);
-                }
-            },
-            new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED;
-                }
-            },
-            TIMEOUT_ASYNC_PROCESSING);
+            uiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
+                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
         } finally {
             clearAccessInteractiveWindowsFlag();
         }
@@ -328,22 +262,34 @@
         setAccessInteractiveWindowsFlag();
         try {
             // Add two more windows.
-            addTwoAppPanelWindows();
+            final View views[];
+            views = addTwoAppPanelWindows();
 
-            // Put accessibility focus in the first app window.
-            ensureAppWindowFocusedOrFail(0);
-            // Make sure there only one accessibility focus.
-            assertSingleAccessibilityFocus();
+            try {
+                // Put accessibility focus in the first app window.
+                ensureAppWindowFocusedOrFail(0);
+                // Make sure there only one accessibility focus.
+                assertSingleAccessibilityFocus();
 
-            // Put accessibility focus in the second app window.
-            ensureAppWindowFocusedOrFail(1);
-            // Make sure there only one accessibility focus.
-            assertSingleAccessibilityFocus();
+                // Put accessibility focus in the second app window.
+                ensureAppWindowFocusedOrFail(1);
+                // Make sure there only one accessibility focus.
+                assertSingleAccessibilityFocus();
 
-            // Put accessibility focus in the third app window.
-            ensureAppWindowFocusedOrFail(2);
-            // Make sure there only one accessibility focus.
-            assertSingleAccessibilityFocus();
+                // Put accessibility focus in the third app window.
+                ensureAppWindowFocusedOrFail(2);
+                // Make sure there only one accessibility focus.
+                assertSingleAccessibilityFocus();
+            } finally {
+                // Clean up panel windows
+                getInstrumentation().runOnMainSync(() -> {
+                    WindowManager wm =
+                            getInstrumentation().getContext().getSystemService(WindowManager.class);
+                    for (View view : views) {
+                        wm.removeView(view);
+                    }
+                });
+            }
         } finally {
             ensureAccessibilityFocusCleared();
             clearAccessInteractiveWindowsFlag();
@@ -351,24 +297,7 @@
     }
 
     @MediumTest
-    public void testPerformActionFocus() throws Exception {
-        // find a view and make sure it is not focused
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
-                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        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);
-        assertTrue(button.isFocused());
-    }
-
-    @MediumTest
-    public void testPerformActionClearFocus() throws Exception {
+    public void testPerformActionSetAndClearFocus() throws Exception {
         // find a view and make sure it is not focused
         AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
@@ -443,20 +372,10 @@
                         getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
-        // Make an action and wait for an event.
-        AccessibilityEvent expected = getInstrumentation().getUiAutomation()
-                .executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                button.performAction(ACTION_CLICK);
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED);
-            }
-        },
-        TIMEOUT_ASYNC_PROCESSING);
+        // Perform an action and wait for an event
+        AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> button.performAction(ACTION_CLICK),
+                filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -470,20 +389,10 @@
                         getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
-        // Make an action and wait for an event.
-        AccessibilityEvent expected = getInstrumentation().getUiAutomation()
-                .executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                button.performAction(ACTION_LONG_CLICK);
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-            }
-        },
-        TIMEOUT_ASYNC_PROCESSING);
+        // Perform an action and wait for an event.
+        AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> button.performAction(ACTION_LONG_CLICK),
+                filterForEventType(TYPE_VIEW_LONG_CLICKED), TIMEOUT_ASYNC_PROCESSING);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -521,20 +430,8 @@
 
         // focus and wait for the event
         AccessibilityEvent awaitedEvent = getInstrumentation().getUiAutomation()
-            .executeAndWaitForEvent(
-                new Runnable() {
-            @Override
-            public void run() {
-                assertTrue(button.performAction(ACTION_FOCUS));
-            }
-        },
-                new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED);
-            }
-        },
-        TIMEOUT_ASYNC_PROCESSING);
+                .executeAndWaitForEvent(() -> button.performAction(ACTION_FOCUS),
+                        filterForEventType(TYPE_VIEW_FOCUSED), TIMEOUT_ASYNC_PROCESSING);
 
         assertNotNull(awaitedEvent);
 
@@ -640,13 +537,8 @@
         setAccessInteractiveWindowsFlag();
         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         assertFalse(isDividerWindowPresent(uiAutomation));
-        Runnable toggleSplitScreenRunnable = new Runnable() {
-            @Override
-            public void run() {
-                assertTrue(uiAutomation.performGlobalAction(
+        Runnable toggleSplitScreenRunnable = () -> assertTrue(uiAutomation.performGlobalAction(
                         AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN));
-            }
-        };
 
         uiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerPresentFilter,
                 TIMEOUT_ASYNC_PROCESSING);
@@ -665,7 +557,7 @@
             getInstrumentation().runOnMainSync(() -> {
                 getActivity().enterPictureInPictureMode();
             });
-        }, mWindowsChangedFilter, TIMEOUT_ASYNC_PROCESSING);
+        }, filterForEventType(TYPE_WINDOWS_CHANGED), TIMEOUT_ASYNC_PROCESSING);
         waitForIdle();
 
         // We should be able to find a picture-in-picture window now
@@ -686,8 +578,6 @@
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
             AccessibilityWindowInfo window = windows.get(i);
-            Rect bounds = new Rect();
-            window.getBoundsInScreen(bounds);
             if (window.getType() == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER) {
                 return true;
             }
@@ -717,8 +607,11 @@
                     throw new AssertionError("Duplicate accessibility focus");
                 }
             } else {
-                assertNull(window.getRoot().findFocus(
-                        AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+                AccessibilityNodeInfo root = window.getRoot();
+                if (root != null) {
+                    assertNull(root.findFocus(
+                            AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+                }
             }
         }
     }
@@ -726,7 +619,7 @@
     private void ensureAppWindowFocusedOrFail(int appWindowIndex) throws TimeoutException {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
-        AccessibilityWindowInfo focusTareget = null;
+        AccessibilityWindowInfo focusTarget = null;
 
         int visitedAppWindows = -1;
         final int windowCount = windows.size();
@@ -735,118 +628,81 @@
             if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
                 visitedAppWindows++;
                 if (appWindowIndex <= visitedAppWindows) {
-                    focusTareget = window;
+                    focusTarget = window;
                     break;
                 }
             }
         }
 
-        if (focusTareget == null) {
+        if (focusTarget == null) {
             throw new IllegalStateException("Couldn't find app window: " + appWindowIndex);
         }
 
-        if (focusTareget.isAccessibilityFocused()) {
+        if (focusTarget.isAccessibilityFocused()) {
             return;
         }
 
-        final AccessibilityWindowInfo finalFocusTarget = focusTareget;
-        uiAutomation.executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                assertTrue(finalFocusTarget.getRoot().performAction(
-                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        final AccessibilityWindowInfo finalFocusTarget = focusTarget;
+        uiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
+                .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+                TIMEOUT_ASYNC_PROCESSING);
 
         windows = uiAutomation.getWindows();
         for (int i = 0; i < windowCount; i++) {
             AccessibilityWindowInfo window = windows.get(i);
-            if (window.getId() == focusTareget.getId()) {
+            if (window.getId() == focusTarget.getId()) {
                 assertTrue(window.isAccessibilityFocused());
                 break;
             }
         }
     }
 
-    private void addTwoAppPanelWindows() throws TimeoutException {
+    private View[] addTwoAppPanelWindows() throws TimeoutException {
         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
 
         uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
 
+        final View views[] = new View[2];
         // Add the first window.
-        uiAutomation.executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                getInstrumentation().runOnMainSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-                        params.gravity = Gravity.TOP;
-                        params.y = getStatusBarHeight();
-                        params.width = WindowManager.LayoutParams.MATCH_PARENT;
-                        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-                        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-                        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-                        params.token = getActivity().getWindow().getDecorView().getWindowToken();
+        uiAutomation.executeAndWaitForEvent(() -> getInstrumentation().runOnMainSync(() -> {
+            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.gravity = Gravity.TOP;
+            params.y = getStatusBarHeight(getActivity());
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+            params.token = getActivity().getWindow().getDecorView().getWindowToken();
 
-                        Button button = new Button(getActivity());
-                        button.setText(R.string.button1);
-                        getActivity().getWindowManager().addView(button, params);
-                    }
-                });
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+            final Button button = new Button(getActivity());
+            button.setText(R.string.button1);
+            views[0] = button;
+            getActivity().getWindowManager().addView(button, params);
+        }), filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), TIMEOUT_ASYNC_PROCESSING);
 
         // Add the second window.
-        uiAutomation.executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                getInstrumentation().runOnMainSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-                        params.gravity = Gravity.BOTTOM;
-                        params.width = WindowManager.LayoutParams.MATCH_PARENT;
-                        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-                        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-                        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-                        params.token = getActivity().getWindow().getDecorView().getWindowToken();
+        uiAutomation.executeAndWaitForEvent(() -> getInstrumentation().runOnMainSync(() -> {
+            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.gravity = Gravity.BOTTOM;
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+            params.token = getActivity().getWindow().getDecorView().getWindowToken();
 
-                        Button button = new Button(getActivity());
-                        button.setText(R.string.button2);
-                        getActivity().getWindowManager().addView(button, params);
-                    }
-                });
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
-    }
-
-    private int getStatusBarHeight() {
-        final Rect rect = new Rect();
-        Window window = getActivity().getWindow();
-        window.getDecorView().getWindowVisibleDisplayFrame(rect);
-        return rect.top;
+            final Button button = new Button(getActivity());
+            button.setText(R.string.button2);
+            views[1] = button;
+            getActivity().getWindowManager().addView(button, params);
+        }), filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), TIMEOUT_ASYNC_PROCESSING);
+        return views;
     }
 
     private void setAccessInteractiveWindowsFlag () {
@@ -866,26 +722,20 @@
     private void ensureAccessibilityFocusCleared() {
         try {
             final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-            uiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
-                    final int windowCount = windows.size();
-                    for (int i = 0; i < windowCount; i++) {
-                        AccessibilityWindowInfo window = windows.get(i);
-                        if (window.isAccessibilityFocused()) {
-                            window.getRoot().performAction(
+            uiAutomation.executeAndWaitForEvent(() -> {
+                List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+                final int windowCount = windows.size();
+                for (int i = 0; i < windowCount; i++) {
+                    AccessibilityWindowInfo window = windows.get(i);
+                    if (window.isAccessibilityFocused()) {
+                        AccessibilityNodeInfo root = window.getRoot();
+                        if (root != null) {
+                            root.performAction(
                                     AccessibilityNodeInfo.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);
+            }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), TIMEOUT_ASYNC_PROCESSING);
         } catch (TimeoutException te) {
             /* ignore */
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java
deleted file mode 100644
index c638951..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java
+++ /dev/null
@@ -1,29 +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.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-/**
- * Activity used by ActivityWindowReportingTest
- */
-public class AccessibilityWindowReportingActivity extends AccessibilityTestActivity {
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.accessibility_window_reporting_test);
-        setTitle("AccessibilityWindowReportingActivity");
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index c9eef93..e9f3f52 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -16,80 +16,249 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+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.DisplayUtils.getStatusBarHeight;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_REMOVED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
+import android.app.Activity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
-import android.text.TextUtils;
-import android.view.accessibility.AccessibilityEvent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.ArrayAdapter;
 import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+
+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.List;
 import java.util.concurrent.TimeoutException;
 
 /**
- * Tests that AccessibilityWindowInfos are properly populated
+ * Tests that window changes produce the correct events and that AccessibilityWindowInfos are
+ * properly populated
  */
-public class AccessibilityWindowReportingTest
-        extends AccessibilityActivityTestCase<AccessibilityWindowReportingActivity> {
-    UiAutomation mUiAutomation;
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityWindowReportingTest {
+    private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
+    private static final CharSequence TOP_WINDOW_TITLE =
+            "android.accessibilityservice.cts.AccessibilityWindowReportingTest.TOP_WINDOW_TITLE";
 
-    public AccessibilityWindowReportingTest() {
-        super(AccessibilityWindowReportingActivity.class);
-    }
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+    private Activity mActivity;
+    private CharSequence mActivityTitle;
 
-    public void setUp() throws Exception {
-        super.setUp();
-        mUiAutomation = getInstrumentation().getUiAutomation();
-        AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
+    @Rule
+    public ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        mUiAutomation.setServiceInfo(info);
+        sUiAutomation.setServiceInfo(info);
     }
 
-    public void tearDown() throws Exception {
-        mUiAutomation.destroy();
-        super.tearDown();
+    @AfterClass
+    public static void finalTearDown() throws Exception {
+        sUiAutomation.destroy();
     }
 
-    public void testWindowTitle_getTitleReturnsTitle() {
-        AccessibilityWindowInfo window = findWindowByTitle(getActivity().getTitle());
-        assertNotNull("Window title not reported to accessibility", window);
-        window.recycle();
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+        mActivityTitle = getActivityTitle(sInstrumentation, mActivity);
     }
 
+    @Test
     public void testUpdatedWindowTitle_generatesEventAndIsReturnedByGetTitle() {
         final String updatedTitle = "Updated Title";
         try {
-            mUiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    getInstrumentation().runOnMainSync(new Runnable() {
-                        @Override
-                        public void run() {
-                            getActivity().setTitle(updatedTitle);
-                        }
-                    });
-                }
-            }, new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED);
-                }
-            }, TIMEOUT_ASYNC_PROCESSING);
+            sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                    () -> mActivity.setTitle(updatedTitle)),
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_TITLE),
+                    TIMEOUT_ASYNC_PROCESSING);
         } catch (TimeoutException exception) {
             throw new RuntimeException(
                     "Failed to get windows changed event for title update", exception);
         }
-        AccessibilityWindowInfo window = findWindowByTitle(updatedTitle);
+        final AccessibilityWindowInfo window = findWindowByTitle(sUiAutomation, updatedTitle);
         assertNotNull("Updated window title not reported to accessibility", window);
         window.recycle();
     }
 
+    @Test
+    public void testWindowAddedMovedAndRemoved_generatesEventsForAllThree() throws Exception {
+        final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
+        final WindowManager.LayoutParams paramsForBottom = layoutParmsForWindowOnBottom();
+        final Button button = new Button(mActivity);
+        button.setText(R.string.button1);
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.getWindowManager().addView(button, paramsForTop)),
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
+                TIMEOUT_ASYNC_PROCESSING);
+
+        // Move window from top to bottom
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.getWindowManager().updateViewLayout(button, paramsForBottom)),
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_BOUNDS),
+                TIMEOUT_ASYNC_PROCESSING);
+        // Remove the view
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.getWindowManager().removeView(button)),
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED),
+                TIMEOUT_ASYNC_PROCESSING);
+    }
+
+    @Test
+    public void putWindowInPictureInPicture_generatesEventAndReportsProperty() throws Exception {
+        if (!sInstrumentation.getContext().getPackageManager()
+                .hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            return;
+        }
+        sUiAutomation.executeAndWaitForEvent(
+                () -> sInstrumentation.runOnMainSync(() -> mActivity.enterPictureInPictureMode()),
+                (event) -> {
+                    if (event.getEventType() != TYPE_WINDOWS_CHANGED) return false;
+                    // Look for a picture-in-picture window
+                    final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+                    final int windowCount = windows.size();
+                    for (int i = 0; i < windowCount; i++) {
+                        if (windows.get(i).isInPictureInPictureMode()) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }, TIMEOUT_ASYNC_PROCESSING);
+
+        // There should be exactly one picture-in-picture window now
+        int numPictureInPictureWindows = 0;
+        final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+        final int windowCount = windows.size();
+        for (int i = 0; i < windowCount; i++) {
+            final AccessibilityWindowInfo window = windows.get(i);
+            if (window.isInPictureInPictureMode()) {
+                numPictureInPictureWindows++;
+            }
+        }
+        assertTrue(numPictureInPictureWindows >= 1);
+    }
+
+    @Test
+    public void moveFocusToAnotherWindow_generatesEventsAndMovesActiveAndFocus() throws Exception {
+        final View topWindowView = showTopWindowAndWaitForItToShowUp();
+        final AccessibilityWindowInfo topWindow =
+                findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
+
+        AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+        final AccessibilityNodeInfo buttonNode =
+                topWindow.getRoot().findAccessibilityNodeInfosByText(
+                        sInstrumentation.getContext().getString(R.string.button1)).get(0);
+
+        // Make sure activityWindow is not focused
+        if (activityWindow.isFocused()) {
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS),
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED),
+                    TIMEOUT_ASYNC_PROCESSING);
+        }
+
+        // Windows may have changed - refresh
+        activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+        assertFalse(activityWindow.isActive());
+        assertFalse(activityWindow.isFocused());
+
+        // Find a focusable view in the main activity menu
+        final AccessibilityNodeInfo autoCompleteTextInfo = activityWindow.getRoot()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/autoCompleteLayout")
+                .get(0);
+
+        // Remove the top window and focus on the main activity
+        sUiAutomation.executeAndWaitForEvent(
+                () -> {
+                    sInstrumentation.runOnMainSync(
+                            () -> mActivity.getWindowManager().removeView(topWindowView));
+                    buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
+                },
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED | WINDOWS_CHANGE_ACTIVE),
+                TIMEOUT_ASYNC_PROCESSING);
+    }
+
+    @Test
+    public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
+        final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        sUiAutomation.setServiceInfo(info);
+
+        try {
+            showTopWindowAndWaitForItToShowUp();
+
+            final AccessibilityWindowInfo activityWindow =
+                    findWindowByTitle(sUiAutomation, mActivityTitle);
+            final AccessibilityWindowInfo topWindow =
+                    findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
+            final AccessibilityNodeInfo win2Node =
+                    topWindow.getRoot().findAccessibilityNodeInfosByText(
+                            sInstrumentation.getContext().getString(R.string.button1)).get(0);
+            final AccessibilityNodeInfo win1Node = activityWindow.getRoot()
+                    .findAccessibilityNodeInfosByViewId(
+                            "android.accessibilityservice.cts:id/autoCompleteLayout")
+                    .get(0);
+
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        win2Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                        win1Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    },
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+                    TIMEOUT_ASYNC_PROCESSING);
+        } finally {
+            info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+            sUiAutomation.setServiceInfo(info);
+        }
+    }
+
+    @Test
     public void testGetAnchorForDropDownForAutoCompleteTextView_returnsTextViewNode() {
         final AutoCompleteTextView autoCompleteTextView =
-                (AutoCompleteTextView) getActivity().findViewById(R.id.autoCompleteLayout);
-        AccessibilityNodeInfo autoCompleteTextInfo = mUiAutomation.getRootInActiveWindow()
+                (AutoCompleteTextView) mActivity.findViewById(R.id.autoCompleteLayout);
+        final AccessibilityNodeInfo autoCompleteTextInfo = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/autoCompleteLayout")
                 .get(0);
@@ -98,25 +267,15 @@
         final String[] COUNTRIES = new String[] {"Belgium", "France", "Italy", "Germany", "Spain"};
 
         try {
-            mUiAutomation.executeAndWaitForEvent(new Runnable() {
-                @Override
-                public void run() {
-                    getInstrumentation().runOnMainSync(new Runnable() {
-                        @Override
-                        public void run() {
-                            ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
-                                    android.R.layout.simple_dropdown_item_1line, COUNTRIES);
-                            autoCompleteTextView.setAdapter(adapter);
-                            autoCompleteTextView.showDropDown();
-                        }
-                    });
-                }
-            }, new UiAutomation.AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
-                }
-            }, TIMEOUT_ASYNC_PROCESSING);
+            sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                    () -> {
+                        final ArrayAdapter<String> adapter = new ArrayAdapter<>(
+                                mActivity, android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+                        autoCompleteTextView.setAdapter(adapter);
+                        autoCompleteTextView.showDropDown();
+                    }),
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_CHILDREN),
+                    TIMEOUT_ASYNC_PROCESSING);
         } catch (TimeoutException exception) {
             throw new RuntimeException(
                     "Failed to get window changed event when showing dropdown", exception);
@@ -124,9 +283,9 @@
 
         // Find the pop-up window
         boolean foundPopup = false;
-        List<AccessibilityWindowInfo> windows = mUiAutomation.getWindows();
+        final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         for (int i = 0; i < windows.size(); i++) {
-            AccessibilityWindowInfo window = windows.get(i);
+            final AccessibilityWindowInfo window = windows.get(i);
             if (window.getAnchor() == null) {
                 continue;
             }
@@ -137,17 +296,48 @@
         assertTrue("Failed to find accessibility window for auto-complete pop-up", foundPopup);
     }
 
-    private AccessibilityWindowInfo findWindowByTitle(CharSequence title) {
-        List<AccessibilityWindowInfo> windows = mUiAutomation.getWindows();
-        AccessibilityWindowInfo returnValue = null;
-        for (int i = 0; i < windows.size(); i++) {
-            AccessibilityWindowInfo window = windows.get(i);
-            if (TextUtils.equals(title, window.getTitle())) {
-                returnValue = window;
-            } else {
-                window.recycle();
-            }
-        }
-        return returnValue;
+    private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
+        final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
+        final Button button = new Button(mActivity);
+        button.setText(R.string.button1);
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.getWindowManager().addView(button, paramsForTop)),
+                (event) -> {
+                    return (event.getEventType() == TYPE_WINDOWS_CHANGED)
+                            && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
+                            && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
+                },
+                TIMEOUT_ASYNC_PROCESSING);
+        return button;
     }
-}
+
+    private WindowManager.LayoutParams layoutParmsForWindowOnTop() {
+        final WindowManager.LayoutParams params = layoutParmsForTestWindow();
+        params.gravity = Gravity.TOP;
+        params.setTitle(TOP_WINDOW_TITLE);
+        sInstrumentation.runOnMainSync(() -> {
+            params.y = getStatusBarHeight(mActivity);
+        });
+        return params;
+    }
+
+    private WindowManager.LayoutParams layoutParmsForWindowOnBottom() {
+        final WindowManager.LayoutParams params = layoutParmsForTestWindow();
+        params.gravity = Gravity.BOTTOM;
+        return params;
+    }
+
+    private WindowManager.LayoutParams layoutParmsForTestWindow() {
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        sInstrumentation.runOnMainSync(() -> {
+            params.token = mActivity.getWindow().getDecorView().getWindowToken();
+        });
+        return params;
+    }
+}
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
index 3df8d0c..4c1d064 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
@@ -18,11 +18,13 @@
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.graphics.Path;
 import android.graphics.PathMeasure;
+import android.platform.test.annotations.Presubmit;
 import android.test.InstrumentationTestCase;
 
 /**
  * Tests for creating gesture descriptions.
  */
+@Presubmit
 public class GestureDescriptionTest extends InstrumentationTestCase {
     static final int NOMINAL_PATH_DURATION = 100;
     private Path mNominalPath;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 2b6d2dd..d6dc7fa 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -22,11 +22,15 @@
 import static junit.framework.Assert.assertTrue;
 
 public class InstrumentedAccessibilityService extends AccessibilityService {
+
+    private static final boolean DEBUG = false;
+
     // Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
     private static final String COMPONENT_NAME_SEPARATOR = ":";
 
-    private static final int TIMEOUT_SERVICE_ENABLE = 10000;
-    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = 5000;
+    // Timeout disabled in #DEBUG mode to prevent breakpoint-related failures
+    private static final int TIMEOUT_SERVICE_ENABLE = DEBUG ? Integer.MAX_VALUE : 10000;
+    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
 
     private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
             sInstances = new HashMap<>();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
new file mode 100644
index 0000000..a9bd769
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.distance;
+import static android.accessibilityservice.cts.utils.GestureUtils.drag;
+import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerDown;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
+import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+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.Arrays;
+import java.util.Collection;
+
+/**
+ * Class for testing magnification.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MagnificationGestureHandlerTest {
+
+    private static final double MIN_SCALE = 1.2;
+
+    private InstrumentedAccessibilityService mService;
+    private Instrumentation mInstrumentation;
+    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener();
+    float mCurrentScale = 1f;
+    PointF mCurrentZoomCenter = null;
+    PointF mTapLocation;
+    PointF mTapLocation2;
+    private boolean mHasTouchscreen;
+    private boolean mOriginalIsMagnificationEnabled;
+
+    private final Object mZoomLock = new Object();
+
+    @Rule
+    public ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+        PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        if (!mHasTouchscreen) return;
+
+        mOriginalIsMagnificationEnabled =
+                Settings.Secure.getInt(mInstrumentation.getContext().getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
+        setMagnificationEnabled(true);
+
+        mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+        mService.getMagnificationController().addListener(
+                (controller, region, scale, centerX, centerY) -> {
+                    mCurrentScale = scale;
+                    mCurrentZoomCenter = isZoomed() ? new PointF(centerX, centerY) : null;
+
+                    synchronized (mZoomLock) {
+                        mZoomLock.notifyAll();
+                    }
+                });
+
+        TextView view = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+        mInstrumentation.runOnMainSync(() -> {
+            view.setOnTouchListener(mTouchListener);
+            int[] xy = new int[2];
+            view.getLocationOnScreen(xy);
+            mTapLocation = new PointF(xy[0] + view.getWidth() / 2, xy[1] + view.getHeight() / 2);
+            mTapLocation2 = add(mTapLocation, 31, 29);
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mHasTouchscreen) return;
+
+        setMagnificationEnabled(mOriginalIsMagnificationEnabled);
+
+        if (mService != null) {
+            mService.runOnServiceSync(() -> mService.disableSelfAndRemove());
+            mService = null;
+        }
+    }
+
+    @Test
+    public void testZoomOnOff() {
+        if (!mHasTouchscreen) return;
+
+        assertFalse(isZoomed());
+
+        assertGesturesPropagateToView();
+        assertFalse(isZoomed());
+
+        setZoomByTripleTapping(true);
+
+        assertGesturesPropagateToView();
+        assertTrue(isZoomed());
+
+        setZoomByTripleTapping(false);
+    }
+
+    @Test
+    public void testViewportDragging() {
+        if (!mHasTouchscreen) return;
+
+        assertFalse(isZoomed());
+        tripleTapAndDragViewport();
+        waitOn(mZoomLock, () -> !isZoomed());
+
+        setZoomByTripleTapping(true);
+        tripleTapAndDragViewport();
+        assertTrue(isZoomed());
+
+        setZoomByTripleTapping(false);
+    }
+
+    @Test
+    public void testPanning() {
+        if (!mHasTouchscreen) return;
+        assertFalse(isZoomed());
+
+        float pan = Math.min(mTapLocation.x, mTapLocation2.x) / 2;
+
+        setZoomByTripleTapping(true);
+        PointF oldCenter = mCurrentZoomCenter;
+
+        dispatch(
+                swipe(mTapLocation, add(mTapLocation, -pan, 0)),
+                swipe(mTapLocation2, add(mTapLocation2, -pan, 0)));
+
+        waitOn(mZoomLock,
+                () -> (mCurrentZoomCenter.x - oldCenter.x >= pan / mCurrentScale * 0.9));
+
+        setZoomByTripleTapping(false);
+    }
+
+    private void setZoomByTripleTapping(boolean desiredZoomState) {
+        if (isZoomed() == desiredZoomState) return;
+        dispatch(tripleTap());
+        waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
+        assertNoTouchInputPropagated();
+    }
+
+    private void tripleTapAndDragViewport() {
+        StrokeDescription down = tripleTapAndHold();
+
+        float pan = mTapLocation.x / 2;
+        PointF oldCenter = mCurrentZoomCenter;
+
+        StrokeDescription drag = drag(down, add(lastPointOf(down), pan, 0f));
+        dispatch(drag);
+        waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= pan / 5);
+        assertTrue(isZoomed());
+        assertNoTouchInputPropagated();
+
+        dispatch(pointerUp(drag));
+        assertNoTouchInputPropagated();
+    }
+
+    private StrokeDescription tripleTapAndHold() {
+        StrokeDescription tap1 = click(mTapLocation);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation2));
+        StrokeDescription down = startingAt(endTimeOf(tap2) + 20, pointerDown(mTapLocation));
+        dispatch(tap1, tap2, down);
+        waitOn(mZoomLock, () -> isZoomed());
+        return down;
+    }
+
+    private void assertGesturesPropagateToView() {
+        dispatch(click(mTapLocation));
+        assertPropagated(ACTION_DOWN, ACTION_UP);
+
+        dispatch(longClick(mTapLocation));
+        assertPropagated(ACTION_DOWN, ACTION_UP);
+
+        dispatch(doubleTap());
+        assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
+
+        dispatch(swipe(
+                mTapLocation,
+                add(mTapLocation, 31, 29)));
+        assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+    }
+
+    private void assertNoTouchInputPropagated() {
+        assertThat(prettyPrintable(mTouchListener.events), is(empty()));
+    }
+
+    private void setMagnificationEnabled(boolean enabled) {
+        Settings.Secure.putInt(mInstrumentation.getContext().getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, enabled ? 1 : 0);
+    }
+
+    private boolean isZoomed() {
+        return mCurrentScale >= MIN_SCALE;
+    }
+
+    private void assertPropagated(int... eventTypes) {
+        MotionEvent ev;
+        try {
+            while (true) {
+                if (eventTypes.length == 0) return;
+                int expectedEventType = eventTypes[0];
+                long startedPollingAt = SystemClock.uptimeMillis();
+                ev = mTouchListener.events.poll(5, SECONDS);
+                assertNotNull("Expected "
+                        + MotionEvent.actionToString(expectedEventType)
+                        + " but none present after "
+                        + (SystemClock.uptimeMillis() - startedPollingAt) + "ms",
+                        ev);
+                int action = ev.getActionMasked();
+                if (action == expectedEventType) {
+                    eventTypes = Arrays.copyOfRange(eventTypes, 1, eventTypes.length);
+                } else {
+                    if (action != ACTION_MOVE) fail("Unexpected event: " + ev);
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private GestureDescription doubleTap() {
+        return multiTap(2);
+    }
+
+    private GestureDescription tripleTap() {
+        return multiTap(3);
+    }
+
+    private GestureDescription multiTap(int taps) {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        long time = 0;
+        for (int i = 0; i < taps; i++) {
+            StrokeDescription stroke = click(mTapLocation);
+            builder.addStroke(startingAt(time, stroke));
+            time += stroke.getDuration() + 20;
+        }
+        return builder.build();
+    }
+
+    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+        GestureDescription.Builder builder =
+                new GestureDescription.Builder().addStroke(firstStroke);
+        for (StrokeDescription stroke : rest) {
+            builder.addStroke(stroke);
+        }
+        dispatch(builder.build());
+    }
+
+    public void dispatch(GestureDescription gesture) {
+        await(dispatchGesture(mService, gesture));
+    }
+
+    private static <T> Collection<T> prettyPrintable(Collection<T> c) {
+        return new ArrayList<T>(c) {
+
+            @Override
+            public String toString() {
+                return stream()
+                        .map(t -> "\n" + t)
+                        .reduce(String::concat)
+                        .orElse("");
+            }
+        };
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
index 823b8d6..6bad735 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
@@ -69,7 +69,7 @@
         return this;
     }
 
-    private static void execShellCommand(UiAutomation automation, String command) {
+    public static void execShellCommand(UiAutomation automation, String command) {
         try (ParcelFileDescriptor fd = automation.executeShellCommand(command)) {
             try (InputStream inputStream = new FileInputStream(fd.getFileDescriptor())) {
                 try (BufferedReader reader = new BufferedReader(
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
new file mode 100644
index 0000000..cd9055d
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.accessibilityservice.cts.activities;
+
+import android.accessibilityservice.cts.R;
+
+import android.os.Bundle;
+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.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * This class is an {@link android.app.Activity} used to perform end-to-end
+ * testing of the accessibility feature by interaction with the
+ * UI widgets.
+ */
+public class AccessibilityEndToEndActivity extends AccessibilityTestActivity {
+    private PackageNameInjector mPackageNameInjector;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.end_to_end_test);
+
+        ListAdapter listAdapter = new BaseAdapter() {
+            public View getView(int position, View convertView, ViewGroup parent) {
+                TextView textView = (TextView) View
+                        .inflate(AccessibilityEndToEndActivity.this, R.layout.list_view_row, null);
+                textView.setText((String) getItem(position));
+                return textView;
+            }
+
+            public long getItemId(int position) {
+                return position;
+            }
+
+            public Object getItem(int position) {
+                if (position == 0) {
+                    return AccessibilityEndToEndActivity.this.getString(R.string.first_list_item);
+                } else {
+                    return AccessibilityEndToEndActivity.this.getString(R.string.second_list_item);
+                }
+            }
+
+            public int getCount() {
+                return 2;
+            }
+        };
+
+        ListView listView = (ListView) findViewById(R.id.listview);
+        listView.setAdapter(listAdapter);
+    }
+
+    public void setReportedPackageName(String packageName) {
+        if (packageName != null) {
+            mPackageNameInjector = new PackageNameInjector(packageName);
+        } else {
+            mPackageNameInjector = null;
+        }
+        setPackageNameInjector(getWindow().getDecorView(), mPackageNameInjector);
+    }
+
+    private static void setPackageNameInjector(View node, PackageNameInjector injector) {
+        if (node == null) {
+            return;
+        }
+        node.setAccessibilityDelegate(injector);
+        if (node instanceof ViewGroup) {
+            final ViewGroup viewGroup = (ViewGroup) node;
+            final int childCount = viewGroup.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                setPackageNameInjector(viewGroup.getChildAt(i), injector);
+            }
+        }
+    }
+
+    private static class PackageNameInjector extends View.AccessibilityDelegate {
+        private final String mPackageName;
+
+        PackageNameInjector(String packageName) {
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(host, event);
+            event.setPackageName(mPackageName);
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            info.setPackageName(mPackageName);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java
new file mode 100644
index 0000000..921f01b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java
@@ -0,0 +1,35 @@
+/**
+ * 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.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity 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 AccessibilityFocusAndInputFocusSyncActivity extends AccessibilityTestActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_focus_and_input_focus_sync_test);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
new file mode 100644
index 0000000..49be337
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
@@ -0,0 +1,31 @@
+/**
+ * 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.accessibilityservice.cts.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public abstract class AccessibilityTestActivity extends Activity {
+
+    @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);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java
new file mode 100644
index 0000000..2cd28c5
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity for testing the accessibility APIs for traversing the
+ * text content of a View at several granularities.
+ */
+public class AccessibilityTextTraversalActivity extends AccessibilityTestActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_text_traversal_test);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java
new file mode 100644
index 0000000..28fada1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java
@@ -0,0 +1,35 @@
+/**
+ * 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.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity 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 hierarchical movement of the
+ * accessibility focus.
+ */
+public class AccessibilityViewTreeReportingActivity extends AccessibilityTestActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_view_tree_reporting_test);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java
new file mode 100644
index 0000000..4424f58
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+import android.view.View;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity 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.
+ */
+public class AccessibilityWindowQueryActivity extends AccessibilityTestActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.query_window_test);
+
+        findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                /* do nothing */
+            }
+        });
+        findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+            public boolean onLongClick(View v) {
+                return true;
+            }
+        });
+
+        findViewById(R.id.button5).setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
+            }
+
+            @Override
+            public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                if (action == R.id.foo_custom_action) {
+                    return true;
+                }
+                return super.performAccessibilityAction(host, action, args);
+            }
+        });
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java
new file mode 100644
index 0000000..dfbbfa3
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+/**
+ * Activity used by ActivityWindowReportingTest
+ */
+public class AccessibilityWindowReportingActivity extends AccessibilityTestActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(
+                android.accessibilityservice.cts.R.layout.accessibility_window_reporting_test);
+        setTitle("AccessibilityWindowReportingActivity");
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
new file mode 100644
index 0000000..846acd9
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -0,0 +1,95 @@
+/**
+ * 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.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;
+
+/**
+ * Utility class for creating AccessibilityEventFilters
+ */
+public class AccessibilityEventFilterUtils {
+    public static AccessibilityEventFilter filterForEventType(int eventType) {
+        return (new AccessibilityEventTypeMatcher(eventType))::matches;
+    }
+
+    public static AccessibilityEventFilter filterWindowsChangedWithChangeTypes(int changes) {
+        return (both(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED))
+                        .and(new WindowChangesMatcher(changes)))::matches;
+    }
+    public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private int mType;
+
+        public AccessibilityEventTypeMatcher(int type) {
+            super();
+            mType = type;
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return event.getEventType() == mType;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to type " + mType);
+        }
+    }
+
+    public static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private int mWindowChanges;
+
+        public WindowChangesMatcher(int windowChanges) {
+            super();
+            mWindowChanges = windowChanges;
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return (event.getWindowChanges() & mWindowChanges) == mWindowChanges;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("With window change type " + mWindowChanges);
+        }
+    }
+
+    public static class ContentChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+        private int mContentChanges;
+
+        public ContentChangesMatcher(int contentChanges) {
+            super();
+            mContentChanges = contentChanges;
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return (event.getContentChangeTypes() & mContentChanges) == mContentChanges;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("With window change type " + mContentChanges);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
new file mode 100644
index 0000000..822b2a6
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -0,0 +1,100 @@
+/**
+ * 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.accessibilityservice.cts.utils;
+
+import static android.accessibilityservice.cts.AccessibilityActivityTestCase
+        .TIMEOUT_ASYNC_PROCESSING;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Rect;
+import android.support.test.rule.ActivityTestRule;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import java.util.List;
+
+/**
+ * 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 {
+    // 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 {
+        final int[] location = new int[2];
+        final StringBuilder activityPackage = new StringBuilder();
+        final Rect bounds = new Rect();
+        final StringBuilder activityTitle = new StringBuilder();
+        // Make sure we get window events, so we'll know when the window appears
+        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        uiAutomation.setServiceInfo(info);
+        final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+                () -> {
+                    mTempActivity = rule.launchActivity(null);
+                    final StringBuilder builder = new StringBuilder();
+                    instrumentation.runOnMainSync(() -> {
+                        mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                        activityPackage.append(mTempActivity.getPackageName());
+                    });
+                    instrumentation.waitForIdleSync();
+                    activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+                },
+                (event) -> {
+                    final AccessibilityWindowInfo window =
+                            findWindowByTitle(uiAutomation, activityTitle);
+                    if (window == null) return false;
+                    window.getBoundsInScreen(bounds);
+                    mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                    if (bounds.isEmpty()) {
+                        return false;
+                    }
+                    return (!bounds.isEmpty())
+                            && (bounds.left == location[0]) && (bounds.top == location[1]);
+                }, TIMEOUT_ASYNC_PROCESSING);
+        assertNotNull(awaitedEvent);
+        return mTempActivity;
+    }
+
+    public static CharSequence getActivityTitle(
+            Instrumentation instrumentation, Activity activity) {
+        final StringBuilder titleBuilder = new StringBuilder();
+        instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle()));
+        return titleBuilder;
+    }
+
+    public static AccessibilityWindowInfo findWindowByTitle(
+            UiAutomation uiAutomation, CharSequence title) {
+        final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+        AccessibilityWindowInfo returnValue = null;
+        for (int i = 0; i < windows.size(); i++) {
+            final AccessibilityWindowInfo window = windows.get(i);
+            if (TextUtils.equals(title, window.getTitle())) {
+                returnValue = window;
+            } else {
+                window.recycle();
+            }
+        }
+        return returnValue;
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
new file mode 100644
index 0000000..12bb737
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.accessibilityservice.cts.utils;
+
+import static android.accessibilityservice.cts.utils.CtsTestUtils.assertThrows;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.SystemClock;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+public class AsyncUtils {
+    public static final long DEFAULT_TIMEOUT_MS = 5000;
+
+    private AsyncUtils() {}
+
+    public static <T> T await(Future<T> f) {
+        return await(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+    }
+
+    public static <T> T await(Future<T> f, long time, TimeUnit timeUnit) {
+        try {
+            return f.get(time, timeUnit);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Throwable awaitFailure(Future<?> f) {
+        return awaitFailure(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+    }
+
+    public static Throwable awaitFailure(Future<?> f, long time, TimeUnit timeUnit) {
+        return assertThrows(() -> await(f, time, timeUnit));
+    }
+
+    public static <T> CancellationException awaitCancellation(Future<T> f) {
+       return awaitCancellation(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+    }
+
+    public static <T> CancellationException awaitCancellation(
+            Future<T> f, long time, TimeUnit timeUnit) {
+        Throwable ex = awaitFailure(f, time, timeUnit);
+        Throwable current = ex;
+        while (current != null) {
+            if (current instanceof CancellationException) {
+                return (CancellationException) current;
+            }
+            current = current.getCause();
+        }
+        throw new AssertionError("Expected cancellation", ex);
+    }
+
+    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);
+            }
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java
new file mode 100644
index 0000000..545483b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+
+public class CtsTestUtils {
+    private CtsTestUtils() {}
+
+    public static Throwable assertThrows(Runnable action) {
+        return assertThrows(Throwable.class, action);
+    }
+
+    public static <E extends Throwable> E assertThrows(Class<E> exceptionClass, Runnable action) {
+        try {
+            action.run();
+            throw new AssertionError("Expected an exception");
+        } catch (Throwable e) {
+            if (exceptionClass.isInstance(e)) {
+                return (E) e;
+            }
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
new file mode 100644
index 0000000..a65d859
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
@@ -0,0 +1,31 @@
+/**
+ * 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.accessibilityservice.cts.utils;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.view.Window;
+
+/**
+ * Utilities needed when interacting with the display
+ */
+public class DisplayUtils {
+    public static int getStatusBarHeight(Activity activity) {
+        final Rect rect = new Rect();
+        Window window = activity.getWindow();
+        window.getDecorView().getWindowVisibleDisplayFrame(rect);
+        return rect.top;
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
new file mode 100644
index 0000000..d7ae4592
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -0,0 +1,37 @@
+/*
+ * 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.accessibilityservice.cts.utils;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class EventCapturingTouchListener implements View.OnTouchListener {
+
+    public final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        assertTrue(events.offer(MotionEvent.obtain(motionEvent)));
+        return true;
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
new file mode 100644
index 0000000..076a6da
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -0,0 +1,144 @@
+/*
+ * 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.accessibilityservice.cts.utils;
+
+import android.accessibilityservice.AccessibilityService.GestureResultCallback;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.InstrumentedAccessibilityService;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.view.ViewConfiguration;
+
+import java.util.concurrent.CompletableFuture;
+
+public class GestureUtils {
+
+    private GestureUtils() {}
+
+    public static CompletableFuture<Void> dispatchGesture(
+            InstrumentedAccessibilityService service,
+            GestureDescription gesture) {
+        CompletableFuture<Void> result = new CompletableFuture<>();
+        GestureResultCallback callback = new GestureResultCallback() {
+            @Override
+            public void onCompleted(GestureDescription gestureDescription) {
+                result.complete(null);
+            }
+
+            @Override
+            public void onCancelled(GestureDescription gestureDescription) {
+                result.cancel(false);
+            }
+        };
+        service.runOnServiceSync(() -> {
+            if (!service.dispatchGesture(gesture, callback, null)) {
+                result.completeExceptionally(new IllegalStateException());
+            }
+        });
+        return result;
+    }
+
+    public static StrokeDescription pointerDown(PointF point) {
+        return new StrokeDescription(path(point), 0, ViewConfiguration.getTapTimeout(), true);
+    }
+
+    public static StrokeDescription pointerUp(StrokeDescription lastStroke) {
+        return lastStroke.continueStroke(path(lastPointOf(lastStroke)),
+                endTimeOf(lastStroke), ViewConfiguration.getTapTimeout(), false);
+    }
+
+    public static PointF lastPointOf(StrokeDescription stroke) {
+        float[] p = stroke.getPath().approximate(0.3f);
+        return new PointF(p[p.length - 2], p[p.length - 1]);
+    }
+
+    public static StrokeDescription click(PointF point) {
+        return new StrokeDescription(path(point), 0, ViewConfiguration.getTapTimeout());
+    }
+
+    public static StrokeDescription longClick(PointF point) {
+        return new StrokeDescription(path(point), 0,
+                ViewConfiguration.getLongPressTimeout() * 3 / 2);
+    }
+
+    public static StrokeDescription swipe(PointF from, PointF to) {
+        return swipe(from, to, ViewConfiguration.getTapTimeout());
+    }
+
+    public static StrokeDescription swipe(PointF from, PointF to, long duration) {
+        return new StrokeDescription(path(from, to), 0, duration);
+    }
+
+    public static StrokeDescription drag(StrokeDescription from, PointF to) {
+        return from.continueStroke(
+                path(lastPointOf(from), to),
+                endTimeOf(from), ViewConfiguration.getTapTimeout(), true);
+    }
+
+    public static Path path(PointF first, PointF... rest) {
+        Path path = new Path();
+        path.moveTo(first.x, first.y);
+        for (PointF point : rest) {
+            path.lineTo(point.x, point.y);
+        }
+        return path;
+    }
+
+    public static StrokeDescription startingAt(long timeMs, StrokeDescription prototype) {
+        return new StrokeDescription(
+                prototype.getPath(), timeMs, prototype.getDuration(), prototype.willContinue());
+    }
+
+    public static long endTimeOf(StrokeDescription stroke) {
+        return stroke.getStartTime() + stroke.getDuration();
+    }
+
+    public static float distance(PointF a, PointF b) {
+        if (a == null) throw new NullPointerException();
+        if (b == null) throw new NullPointerException();
+        return (float) Math.hypot(a.x - b.x, a.y - b.y);
+    }
+
+    public static PointF add(PointF a, float x, float y) {
+        return new PointF(a.x + x, a.y + y);
+    }
+
+    public static PointF add(PointF a, PointF b) {
+        return add(a, b.x, b.y);
+    }
+
+    public static PointF diff(PointF a, PointF b) {
+        return add(a, -b.x, -b.y);
+    }
+
+    public static PointF negate(PointF p) {
+        return times(-1, p);
+    }
+
+    public static PointF times(float mult, PointF p) {
+        return new PointF(p.x * mult, p.y * mult);
+    }
+
+    public static float length(PointF p) {
+        return (float) Math.hypot(p.x, p.y);
+    }
+
+    public static PointF ceil(PointF p) {
+        return new PointF((float) Math.ceil(p.x), (float) Math.ceil(p.y));
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/RunOnMainUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/RunOnMainUtils.java
new file mode 100644
index 0000000..10c4862
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/RunOnMainUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.accessibilityservice.cts.utils;
+
+import android.app.Instrumentation;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Utilities to return values from {@link Instrumentation#runOnMainSync()}
+ */
+public class RunOnMainUtils {
+    /**
+     * Execute a callable on main and return its value
+     */
+    public static <T extends Object> T getOnMain(
+            Instrumentation instrumentation, Callable<T> callable) {
+        AtomicReference<T> returnValue = new AtomicReference<>(null);
+        AtomicReference<Throwable> throwable = new AtomicReference<>(null);
+        instrumentation.runOnMainSync(() -> {
+            try {
+                returnValue.set(callable.call());
+            } catch (Throwable e) {
+                throwable.set(e);
+            }
+        });
+        if (throwable.get() != null) {
+            throw new RuntimeException(throwable.get());
+        }
+        return returnValue.get();
+    }
+}
diff --git a/tests/accessibilityservice/test-apps/Android.mk b/tests/accessibilityservice/test-apps/Android.mk
new file mode 100644
index 0000000..c9afcf1
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/Android.mk
@@ -0,0 +1,23 @@
+# 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)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk b/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk
new file mode 100644
index 0000000..a0c1d97
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk
@@ -0,0 +1,30 @@
+# 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 := optional
+
+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 := CtsAccessibilityWidgetProvider
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
new file mode 100644
index 0000000..4a06f03
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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="foo.bar.baz">
+
+    <application>
+        <receiver android:name="foo.bar.baz.MyAppWidgetProvider" >
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/appwidget_info" />
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png b/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png
new file mode 100644
index 0000000..4ba97a5
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png
Binary files differ
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</Button>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml
new file mode 100644
index 0000000..b29e8da
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- App widget provider -->
+    <dimen name="min_appwidget_size">40dp</dimen>
+    <dimen name="min_resize_appwidget_size">60dp</dimen>
+    <integer name="update_period_millis">86400000</integer>
+    <integer name="resize_mode">3</integer>
+    <integer name="widget_category">3</integer>
+    <item type="id" name="auto_advance_view_id"/>
+</resources>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml
new file mode 100644
index 0000000..283f543
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/min_appwidget_size"
+    android:minHeight="@dimen/min_appwidget_size"
+    android:minResizeWidth="@dimen/min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/update_period_millis"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen"
+    android:initialLayout="@layout/initial_layout"
+    android:previewImage="@drawable/android_icon"
+    android:autoAdvanceViewId="@id/auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java b/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java
new file mode 100644
index 0000000..e30b554
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java
@@ -0,0 +1,22 @@
+/*
+ * 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 foo.bar.baz;
+
+import android.appwidget.AppWidgetProvider;
+
+public class MyAppWidgetProvider extends AppWidgetProvider {}
diff --git a/tests/admin/Android.mk b/tests/admin/Android.mk
index 24fdda3..bd5346d 100644
--- a/tests/admin/Android.mk
+++ b/tests/admin/Android.mk
@@ -23,6 +23,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner mockito-target-minus-junit4
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsAdminTestCases
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index a0233d7..02c5e3f 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for the CTS device admin tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/admin/app/Android.mk b/tests/admin/app/Android.mk
index 22b5c83..947fb7d 100644
--- a/tests/admin/app/Android.mk
+++ b/tests/admin/app/Android.mk
@@ -22,7 +22,7 @@
 
 LOCAL_JAVA_LIBRARIES := guava
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 4b9e5fa..9751d05 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -26,12 +26,20 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
+import android.os.Process;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -218,7 +226,7 @@
             return;
         }
         try {
-            mDevicePolicyManager.removeUser(mComponent, null);
+            mDevicePolicyManager.removeUser(mComponent, Process.myUserHandle());
             fail("did not throw expected SecurityException");
         } catch (SecurityException e) {
             assertDeviceOwnerMessage(e.getMessage());
@@ -746,8 +754,7 @@
     }
 
     private void assertProfileOwnerMessage(String message) {
-        assertTrue("message is: "+ message,
-                message.contains("does not own the profile"));
+        assertTrue("message is: "+ message, message.contains("does not own the profile"));
     }
 
     public void testSetDelegatedCertInstaller_failIfNotProfileOwner() {
@@ -901,4 +908,83 @@
             assertProfileOwnerMessage(e.getMessage());
         }
     }
+
+    public void testIsUsingUnifiedPassword_failIfNotProfileOwner() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testIsUsingUnifiedPassword_failIfNotProfileOwner");
+            return;
+        }
+        try {
+            mDevicePolicyManager.isUsingUnifiedPassword(mComponent);
+            fail("did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
+
+    public void testSetPasswordBlacklist_failIfNotDeviceOrProfileOwner() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testSetPasswordBlacklist_failIfNotDeviceOrProfileOwner");
+            return;
+        }
+        try {
+            mDevicePolicyManager.setPasswordBlacklist(mComponent, null, null);
+            fail("did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
+
+    public void testGetPasswordBlacklistName_failIfNotDeviceOrProfileOwner() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testGetPasswordBlacklistName_failIfNotDeviceOrProfileOwner");
+            return;
+        }
+        try {
+            mDevicePolicyManager.getPasswordBlacklistName(mComponent);
+            fail("did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
+
+    public void testGenerateKeyPair_failIfNotProfileOwner() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testGenerateKeyPair_failIfNotProfileOwner");
+            return;
+        }
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    "gen-should-fail",
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setKeySize(2048)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                    .build();
+
+            mDevicePolicyManager.generateKeyPair(mComponent, "RSA", spec, 0);
+            fail("did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
+
+    public void testSetKeyPairCertificate_failIfNotProfileOwner() throws CertificateException {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testSetKeyPairCertificate_failIfNotProfileOwner");
+            return;
+        }
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            Certificate cert  = cf.generateCertificate(
+                    new ByteArrayInputStream(TEST_CA_STRING1.getBytes()));
+            List<Certificate> certs = new ArrayList();
+            certs.add(cert);
+            mDevicePolicyManager.setKeyPairCertificate(mComponent, "set-should-fail", certs, true);
+            fail("did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
 }
diff --git a/tests/app/Android.mk b/tests/app/Android.mk
index 6bd42ef..e38ad2f 100644
--- a/tests/app/Android.mk
+++ b/tests/app/Android.mk
@@ -21,7 +21,10 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    org.apache.http.legacy \
+    android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
@@ -29,12 +32,12 @@
     ctstestserver \
     mockito-target-minus-junit4 \
     android-support-test \
-    platform-test-annotations
+    platform-test-annotations \
+    cts-amwm-util \
+    android-support-test
 
 LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, app2/src) \
-    $(call all-java-files-under, appSdk25/src) \
+    $(call all-java-files-under, src)
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 7986842..be79488 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-sdk android:minSdkVersion="11" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <application>
         <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 eb515b2..7aa3e20 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS App 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">
@@ -22,7 +23,8 @@
         <option name="test-file-name" value="CtsAppTestStubs.apk" />
         <option name="test-file-name" value="CtsAppTestStubsDifferentUid.apk" />
         <option name="test-file-name" value="CtsAppTestCases.apk" />
-        <option name="test-file-name" value="CtsAppTestSdk25.apk" />
+        <option name="test-file-name" value="CtsCantSaveState1.apk" />
+        <option name="test-file-name" value="CtsCantSaveState2.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.cts" />
diff --git a/tests/app/CantSaveState1/Android.mk b/tests/app/CantSaveState1/Android.mk
new file mode 100644
index 0000000..72c762d
--- /dev/null
+++ b/tests/app/CantSaveState1/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsCantSaveState1
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/CantSaveState1/AndroidManifest.xml b/tests/app/CantSaveState1/AndroidManifest.xml
new file mode 100644
index 0000000..fadcaeb
--- /dev/null
+++ b/tests/app/CantSaveState1/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.test.cantsavestate1">
+    <application android:label="Can't Save 1" android:cantSaveState="true">
+        <activity android:name="CantSave1Activity">
+            <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/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml b/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml
new file mode 100644
index 0000000..c5bf657
--- /dev/null
+++ b/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml
@@ -0,0 +1,31 @@
+<?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:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="This app #1 can't save its state"
+    />
+
+</LinearLayout>
diff --git a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
new file mode 100644
index 0000000..fb678cb
--- /dev/null
+++ b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.test.cantsavestate1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave1Activity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.cant_save_1_activity);
+        getWindow().getDecorView().requestFocus();
+    }
+}
diff --git a/tests/app/CantSaveState2/Android.mk b/tests/app/CantSaveState2/Android.mk
new file mode 100644
index 0000000..4023063
--- /dev/null
+++ b/tests/app/CantSaveState2/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsCantSaveState2
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/CantSaveState2/AndroidManifest.xml b/tests/app/CantSaveState2/AndroidManifest.xml
new file mode 100644
index 0000000..8f4f01d
--- /dev/null
+++ b/tests/app/CantSaveState2/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.test.cantsavestate2">
+    <application android:label="Can't Save 2" android:cantSaveState="true">
+        <activity android:name="CantSave2Activity">
+            <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/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml b/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml
new file mode 100644
index 0000000..c5b8e3d
--- /dev/null
+++ b/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml
@@ -0,0 +1,31 @@
+<?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:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="This app #2 can't save its state"
+    />
+
+</LinearLayout>
diff --git a/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java b/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
new file mode 100644
index 0000000..420ac93
--- /dev/null
+++ b/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.test.cantsavestate2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave2Activity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.cant_save_2_activity);
+        getWindow().getDecorView().requestFocus();
+    }
+}
diff --git a/tests/app/app/Android.mk b/tests/app/app/Android.mk
index bbafe4a..d157c41 100644
--- a/tests/app/app/Android.mk
+++ b/tests/app/app/Android.mk
@@ -23,15 +23,20 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+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 \
-    android-support-v4 \
-    legacy-android-test
+    android-support-v4
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
               src/android/app/stubs/ISecondary.aidl
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 7db0717..6093384 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -104,6 +104,8 @@
 
         <service android:name="android.app.stubs.MockService" />
 
+        <service android:name="android.app.stubs.NullService" />
+
         <activity android:name="android.app.stubs.SearchManagerStubActivity"
                 android:label="SearchManagerStubActivity">
             <intent-filter>
@@ -326,45 +328,6 @@
         <activity android:name="android.app.stubs.ToolbarActivity"
                   android:theme="@android:style/Theme.Material.Light.NoActionBar" />
 
-        <activity android:name="android.app.stubs.MaxAspectRatioActivity"
-                  android:label="MaxAspectRatioActivity"
-                  android:resizeableActivity="false"
-                  android:maxAspectRatio="1.0">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.app.stubs.MetaDataMaxAspectRatioActivity"
-                  android:label="MetaDataMaxAspectRatioActivity"
-                  android:resizeableActivity="false">
-            <meta-data android:name="android.max_aspect" android:value="1.0" />
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.app.stubs.MaxAspectRatioResizeableActivity"
-                  android:label="MaxAspectRatioResizeableActivity"
-                  android:resizeableActivity="true"
-                  android:maxAspectRatio="1.0">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.app.stubs.MaxAspectRatioUnsetActivity"
-                  android:label="MaxAspectRatioUnsetActivity"
-                  android:resizeableActivity="false">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
         <service
             android:name="android.app.stubs.LiveWallpaper"
             android:icon="@drawable/robot"
diff --git a/tests/app/app/src/android/app/stubs/DialogStubActivity.java b/tests/app/app/src/android/app/stubs/DialogStubActivity.java
index 03e6010..bb8d85b 100644
--- a/tests/app/app/src/android/app/stubs/DialogStubActivity.java
+++ b/tests/app/app/src/android/app/stubs/DialogStubActivity.java
@@ -30,7 +30,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.rule.ActivityTestRule;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -317,11 +317,10 @@
     private static final String TEST_DIALOG_NUMBER_EXTRA = "testDialogNumber";
 
     public static <T extends Activity> T startDialogActivity(
-            ActivityInstrumentationTestCase2<T> testCase, int dialogNumber) {
+            ActivityTestRule<T> rule, int dialogNumber) {
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.putExtra(TEST_DIALOG_NUMBER_EXTRA, dialogNumber);
-        testCase.setActivityIntent(intent);
-        return testCase.getActivity();
+        return rule.launchActivity(intent);
     }
 
     @Override
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java
deleted file mode 100644
index 4a04861..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +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.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java
deleted file mode 100644
index da1c605..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java
+++ /dev/null
@@ -1,23 +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.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioResizeableActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java
deleted file mode 100644
index 06e4994..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java
+++ /dev/null
@@ -1,23 +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.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioUnsetActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java b/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java
deleted file mode 100644
index 2e989de..0000000
--- a/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +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.app.stubs;
-
-import android.app.Activity;
-
-public class MetaDataMaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/NullService.java b/tests/app/app/src/android/app/stubs/NullService.java
new file mode 100644
index 0000000..6a1c618
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NullService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.app.stubs;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * A Service that always returns null from onBind(Intent).
+ */
+public class NullService extends Service {
+    private static final String TAG = "NullService";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind() returning null");
+        return null;
+    }
+
+}
diff --git a/tests/app/app2/Android.mk b/tests/app/app2/Android.mk
index 2689c30..304f79e 100644
--- a/tests/app/app2/Android.mk
+++ b/tests/app/app2/Android.mk
@@ -22,8 +22,7 @@
     compatibility-device-util \
 
 LOCAL_SRC_FILES := \
-    ../app/src/android/app/stubs/LocalService.java \
-    $(call all-java-files-under, src) \
+    ../app/src/android/app/stubs/LocalService.java
 
 LOCAL_SDK_VERSION := current
 
diff --git a/tests/app/app2/AndroidManifest.xml b/tests/app/app2/AndroidManifest.xml
index 8c30996..0926251 100644
--- a/tests/app/app2/AndroidManifest.xml
+++ b/tests/app/app2/AndroidManifest.xml
@@ -19,6 +19,8 @@
     <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"
diff --git a/tests/app/app2/src/com/android/app2/AlertWindowService.java b/tests/app/app2/src/com/android/app2/AlertWindowService.java
deleted file mode 100644
index a514e8a..0000000
--- a/tests/app/app2/src/com/android/app2/AlertWindowService.java
+++ /dev/null
@@ -1,145 +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.app2;
-
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Point;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import java.util.LinkedList;
-
-import static android.graphics.Color.BLUE;
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-/** Service for creating and managing alert windows. */
-public class AlertWindowService extends Service {
-
-    private static final String TAG = "AlertWindowService";
-    private static final boolean DEBUG = false;
-
-    public static final int MSG_ADD_ALERT_WINDOW = 1;
-    public static final int MSG_REMOVE_ALERT_WINDOW = 2;
-    public static final int MSG_REMOVE_ALL_ALERT_WINDOWS = 3;
-
-    public static String NOTIFICATION_MESSENGER_EXTRA =
-            "com.android.app2.AlertWindowService.NOTIFICATION_MESSENGER_EXTRA";
-    public static final int MSG_ON_ALERT_WINDOW_ADDED = 4;
-    public static final int MSG_ON_ALERT_WINDOW_REMOVED = 5;
-
-    private LinkedList<View> mAlertWindows = new LinkedList<>();
-
-    private Messenger mOutgoingMessenger = null;
-    private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
-
-    private class IncomingHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ADD_ALERT_WINDOW:
-                    addAlertWindow();
-                    break;
-                case MSG_REMOVE_ALERT_WINDOW:
-                    removeAlertWindow();
-                    break;
-                case MSG_REMOVE_ALL_ALERT_WINDOWS:
-                    removeAllAlertWindows();
-                    break;
-                default:
-                    super.handleMessage(msg);
-            }
-        }
-    }
-
-    private void addAlertWindow() {
-        final Point size = new Point();
-        final WindowManager wm = getSystemService(WindowManager.class);
-        wm.getDefaultDisplay().getSize(size);
-
-        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                TYPE_APPLICATION_OVERLAY,
-                FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
-        params.width = size.x / 3;
-        params.height = size.y / 3;
-        params.gravity = TOP | LEFT;
-
-        final TextView view = new TextView(this);
-        view.setText("AlertWindowService" + mAlertWindows.size());
-        view.setBackgroundColor(BLUE);
-        wm.addView(view, params);
-        mAlertWindows.add(view);
-
-        if (DEBUG) Log.e(TAG, "addAlertWindow " + mAlertWindows.size());
-        if (mOutgoingMessenger != null) {
-            try {
-                mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_ADDED));
-            } catch (RemoteException e) {
-
-            }
-        }
-    }
-
-    private void removeAlertWindow() {
-        if (mAlertWindows.size() == 0) {
-            return;
-        }
-        final WindowManager wm = getSystemService(WindowManager.class);
-        wm.removeView(mAlertWindows.pop());
-
-        if (DEBUG) Log.e(TAG, "removeAlertWindow " + mAlertWindows.size());
-        if (mOutgoingMessenger != null) {
-            try {
-                mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_REMOVED));
-            } catch (RemoteException e) {
-
-            }
-        }
-    }
-
-    private void removeAllAlertWindows() {
-        while (mAlertWindows.size() > 0) {
-            removeAlertWindow();
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (DEBUG) Log.e(TAG, "onBind");
-        mOutgoingMessenger = intent.getParcelableExtra(NOTIFICATION_MESSENGER_EXTRA);
-        return mIncomingMessenger.getBinder();
-    }
-
-    @Override
-    public boolean onUnbind(Intent intent) {
-        if (DEBUG) Log.e(TAG, "onUnbind");
-        removeAllAlertWindows();
-        return super.onUnbind(intent);
-    }
-}
diff --git a/tests/app/appSdk25/Android.mk b/tests/app/appSdk25/Android.mk
deleted file mode 100644
index 36c6d07..0000000
--- a/tests/app/appSdk25/Android.mk
+++ /dev/null
@@ -1,35 +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)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util \
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := 25
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsAppTestSdk25
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/appSdk25/AndroidManifest.xml b/tests/app/appSdk25/AndroidManifest.xml
deleted file mode 100755
index f564f51..0000000
--- a/tests/app/appSdk25/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +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.appSdk25">
-
-    <application android:label="CtsAppTestSdk25">
-        <activity android:name=".Sdk25MaxAspectRatioActivity"
-                  android:label="Sdk25MaxAspectRatioActivity"
-                  android:resizeableActivity="false"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
-
diff --git a/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java b/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java
deleted file mode 100644
index 7fa25e0..0000000
--- a/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +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.appSdk25;
-
-import android.app.Activity;
-
-public class Sdk25MaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/src/android/app/backup/cts/BackupManagerTest.java b/tests/app/src/android/app/backup/cts/BackupManagerTest.java
deleted file mode 100644
index 510e8d1..0000000
--- a/tests/app/src/android/app/backup/cts/BackupManagerTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.backup.cts;
-
-import android.app.backup.BackupManager;
-import android.app.backup.RestoreObserver;
-import android.test.AndroidTestCase;
-
-public class BackupManagerTest extends AndroidTestCase {
-
-    public void testBackupManager() throws Exception {
-        // Check that these don't crash as if they were called in an app...
-        BackupManager backupManager = new BackupManager(mContext);
-        backupManager.dataChanged();
-        BackupManager.dataChanged("android.app.stubs");
-
-        // Backup isn't expected to work in this test but check for obvious bugs...
-        int result = backupManager.requestRestore(new RestoreObserver() {});
-        assertTrue(result != 0);
-    }
-}
diff --git a/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java b/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
index 601dafd..4192883 100644
--- a/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
+++ b/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
@@ -56,15 +56,15 @@
             return;
         }
         // Open activity's options menu
-        mActivity.openOptionsMenu();
+        getInstrumentation().runOnMainSync(() -> mActivity.openOptionsMenu());
         mActivity.waitForMenuToBeOpen();
 
         // Request keyboard shortcuts
-        mActivity.requestShowKeyboardShortcuts();
+        getInstrumentation().runOnMainSync(() -> mActivity.requestShowKeyboardShortcuts());
         mActivity.waitForKeyboardShortcutsToBeRequested();
 
         // Close the shortcuts helper
-        mActivity.dismissKeyboardShortcutsHelper();
+        getInstrumentation().runOnMainSync(() -> mActivity.dismissKeyboardShortcutsHelper());
 
         // THEN the activity's onProvideKeyboardShortcuts should have been
         // triggered to get app specific shortcuts
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 7c1ed0d..b5fb968 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -17,6 +17,7 @@
 package android.app.cts;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -27,6 +28,7 @@
 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.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -34,11 +36,20 @@
 import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.server.am.WindowManagerState;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiSelector;
 import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.compatibility.common.util.SystemUtil;
 
 public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
+    private static final String TAG = ActivityManagerProcessStateTest.class.getName();
+
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
     private static final int WAIT_TIME = 2000;
     // A secondary test activity from another APK.
@@ -50,6 +61,12 @@
     public static String ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT =
             "com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
 
+    // APKs for testing heavy weight app interactions.
+    static final String CANT_SAVE_STATE_1_PACKAGE_NAME = "com.android.test.cantsavestate1";
+    static final String CANT_SAVE_STATE_2_PACKAGE_NAME = "com.android.test.cantsavestate2";
+
+    private static final int TEMP_WHITELIST_DURATION_MS = 2000;
+
     private Context mContext;
     private Instrumentation mInstrumentation;
     private Intent mServiceIntent;
@@ -70,11 +87,75 @@
         mAllProcesses[1] = mService2Intent;
         mContext.stopService(mServiceIntent);
         mContext.stopService(mService2Intent);
+        removeTestAppFromWhitelists();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    private void removeTestAppFromWhitelists() throws Exception {
+        executeShellCmd("cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
+        executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
+    }
+
+    private String executeShellCmd(String cmd) throws Exception {
+        final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
+        return result;
+    }
+
+    private boolean isScreenInteractive() {
+        final PowerManager powerManager =
+                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        return powerManager.isInteractive();
+    }
+
+    private boolean isKeyguardLocked() {
+        final KeyguardManager keyguardManager =
+                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        return keyguardManager.isKeyguardLocked();
+    }
+
+    private void waitForAppFocus(String waitForApp, long waitTime) {
+        long waitUntil = SystemClock.elapsedRealtime() + waitTime;
+        while (true) {
+            WindowManagerState wms = new WindowManagerState();
+            wms.computeState();
+            String appName = wms.getFocusedApp();
+            if (appName != null) {
+                ComponentName comp = ComponentName.unflattenFromString(appName);
+                if (waitForApp.equals(comp.getPackageName())) {
+                    break;
+                }
+            }
+            if (SystemClock.elapsedRealtime() > waitUntil) {
+                throw new IllegalStateException("Timed out waiting for focus on app "
+                        + waitForApp + ", last was " + appName);
+            }
+            Log.i(TAG, "Waiting for app focus, current: " + appName);
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+        };
+    }
+
+    private void startActivityAndWaitForShow(final Intent intent) throws Exception {
+        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> {
+                    try {
+                        mContext.startActivity(intent);
+                    } catch (Exception e) {
+                        fail("Cannot start activity: " + intent);
+                    }
+                }, (AccessibilityEvent event) -> event.getEventType()
+                        == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                , WAIT_TIME);
+    }
+
+    private void maybeClick(UiDevice device, UiSelector sel) {
+        try { device.findObject(sel).click(); } catch (Throwable ignored) { }
+    }
+
+    private void maybeClick(UiDevice device, BySelector sel) {
+        try { device.findObject(sel).click(); } catch (Throwable ignored) { }
     }
 
     /**
@@ -82,14 +163,17 @@
      */
     public void testUidImportanceListener() throws Exception {
         final Parcel data = Parcel.obtain();
-        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent);
-        ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent);
+        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
+                WAIT_TIME);
+        ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
+                WAIT_TIME);
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
         ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
                 SIMPLE_PACKAGE_NAME, 0);
-        UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+                WAIT_TIME);
 
         String cmd = "pm revoke " + STUB_PACKAGE_NAME + " "
                 + Manifest.permission.PACKAGE_USAGE_STATS;
@@ -119,20 +203,21 @@
         am.addOnUidImportanceListener(uidForegroundListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
 
-        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
         am.addOnUidImportanceListener(uidGoneListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+                WAIT_TIME);
 
         try {
             // First kill the processes to start out in a stable state.
-            conn.bind(WAIT_TIME);
-            conn2.bind(WAIT_TIME);
+            conn.bind();
+            conn2.bind();
             IBinder service1 = conn.getServiceIBinder();
             IBinder service2 = conn2.getServiceIBinder();
-            conn.unbind(WAIT_TIME);
-            conn2.unbind(WAIT_TIME);
+            conn.unbind();
+            conn2.unbind();
             try {
                 service1.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
             } catch (RemoteException e) {
@@ -145,38 +230,38 @@
 
             // Wait for uid's processes to go away.
             uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // And wait for the uid report to be gone.
-            uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
 
             // Now bind and see if we get told about the uid coming in to the foreground.
-            conn.bind(WAIT_TIME);
+            conn.bind();
             uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // Also make sure the uid state reports are as expected.  Wait for active because
             // there may be some intermediate states as the process comes up.
-            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
 
             // Pull out the service IBinder for a kludy hack...
             IBinder service = conn.getServiceIBinder();
 
             // Now unbind and see if we get told about it going to the background.
-            conn.unbind(WAIT_TIME);
+            conn.unbind();
             uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // Now kill the process and see if we are told about it being gone.
             try {
@@ -186,74 +271,74 @@
             }
 
             uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+            uidWatcher.expect(WatchUidRunner.CMD_GONE, null);
 
             // Now we are going to try different combinations of binding to two processes to
             // see if they are correctly combined together for the app.
 
             // Bring up both services.
-            conn.bind(WAIT_TIME);
-            conn2.bind(WAIT_TIME);
+            conn.bind();
+            conn2.bind();
             uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // Also make sure the uid state reports are as expected.
-            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
 
             // Bring down one service, app state should remain foreground.
-            conn2.unbind(WAIT_TIME);
+            conn2.unbind();
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // Bring down other service, app state should now be cached.  (If the processes both
             // actually get killed immediately, this is also not a correctly behaving system.)
-            conn.unbind(WAIT_TIME);
+            conn.unbind();
             uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // Bring up one service, this should be sufficient to become foreground.
-            conn2.bind(WAIT_TIME);
+            conn2.bind();
             uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
 
             // Bring up other service, should remain foreground.
-            conn.bind(WAIT_TIME);
+            conn.bind();
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // Bring down one service, should remain foreground.
-            conn.unbind(WAIT_TIME);
+            conn.unbind();
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // And bringing down other service should put us back to cached.
-            conn2.unbind(WAIT_TIME);
+            conn2.unbind();
             uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             data.recycle();
 
@@ -273,7 +358,8 @@
         Intent serviceIntent = new Intent();
         serviceIntent.setClassName(SIMPLE_PACKAGE_NAME,
                 SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
-        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent);
+        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent,
+                WAIT_TIME);
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
@@ -290,20 +376,22 @@
         ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
                 SIMPLE_PACKAGE_NAME, 0);
 
-        UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+                WAIT_TIME);
         am.addOnUidImportanceListener(uidForegroundListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
-        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
         am.addOnUidImportanceListener(uidGoneListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY);
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+                WAIT_TIME);
 
         // First kill the process to start out in a stable state.
         mContext.stopService(serviceIntent);
-        conn.bind(WAIT_TIME);
+        conn.bind();
         IBinder service = conn.getServiceIBinder();
-        conn.unbind(WAIT_TIME);
+        conn.unbind();
         try {
             service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
         } catch (RemoteException e) {
@@ -312,19 +400,19 @@
 
         // Wait for uid's process to go away.
         uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
-                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
         assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
                 am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
         // And wait for the uid report to be gone.
-        uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+        uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
 
         cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
         result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
         // This is a side-effect of the app op command.
-        uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
-        uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE", WAIT_TIME);
+        uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+        uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE");
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -350,37 +438,38 @@
             }
 
             // Put app on temporary whitelist to see if this allows the service start.
-            cmd = "cmd deviceidle tempwhitelist -d 2000 " + SIMPLE_PACKAGE_NAME;
+            cmd = String.format("cmd deviceidle tempwhitelist -d %d %s",
+                    TEMP_WHITELIST_DURATION_MS, SIMPLE_PACKAGE_NAME);
             result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
             // Try starting the service now that the app is whitelisted...  should work!
             mContext.startService(serviceIntent);
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
             // Also make sure the uid state reports are as expected.
-            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // Good, now stop the service and give enough time to get off the temp whitelist.
             mContext.stopService(serviceIntent);
-            conn.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
 
-            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
-            Thread.sleep(3000);
+            executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
 
             // Going off the temp whitelist causes a spurious proc state report...  that's
             // not ideal, but okay.
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // We don't want to wait for the uid to actually go idle, we can force it now.
             cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
             result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
-            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // Now that we should be off the temp whitelist, make sure we again can't start.
             failed = false;
@@ -399,17 +488,17 @@
 
             // Try starting the service now that the app is whitelisted...  should work!
             mContext.startService(serviceIntent);
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // Okay, bring down the service.
             mContext.stopService(serviceIntent);
-            conn.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
 
-            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
         } finally {
             mContext.stopService(serviceIntent);
@@ -435,8 +524,10 @@
      */
     public void testBackgroundCheckStopsService() throws Exception {
         final Parcel data = Parcel.obtain();
-        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent);
-        ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent);
+        ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
+                WAIT_TIME);
+        ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
+                WAIT_TIME);
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
@@ -453,24 +544,26 @@
         ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
                 SIMPLE_PACKAGE_NAME, 0);
 
-        UidImportanceListener uidServiceListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidServiceListener = new UidImportanceListener(appInfo.uid,
+                WAIT_TIME);
         am.addOnUidImportanceListener(uidServiceListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
-        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+        UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
         am.addOnUidImportanceListener(uidGoneListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+                WAIT_TIME);
 
         // First kill the process to start out in a stable state.
         mContext.stopService(mServiceIntent);
         mContext.stopService(mService2Intent);
-        conn.bind(WAIT_TIME);
-        conn2.bind(WAIT_TIME);
+        conn.bind();
+        conn2.bind();
         IBinder service = conn.getServiceIBinder();
         IBinder service2 = conn2.getServiceIBinder();
-        conn.unbind(WAIT_TIME);
-        conn2.unbind(WAIT_TIME);
+        conn.unbind();
+        conn2.unbind();
         try {
             service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
         } catch (RemoteException e) {
@@ -483,7 +576,7 @@
 
         // Wait for uid's process to go away.
         uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
-                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
         assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
                 am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
@@ -494,8 +587,8 @@
         result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
 
         // This is a side-effect of the app op command.
-        uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
-        uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE", WAIT_TIME);
+        uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+        uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_NONEXISTENT);
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -521,43 +614,43 @@
             }
 
             // First poke the process into the foreground, so we can avoid background check.
-            conn2.bind(WAIT_TIME);
-            conn2.waitForConnect(WAIT_TIME);
+            conn2.bind();
+            conn2.waitForConnect();
 
             // Wait for process state to reflect running service.
             uidServiceListener.waitForValue(
                     ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // Also make sure the uid state reports are as expected.
-            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
 
-            conn2.unbind(WAIT_TIME);
+            conn2.unbind();
 
             // Wait for process to recover back down to being cached.
             uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
-            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // Try starting the service now that the app is waiting to idle...  should work!
             mContext.startService(mServiceIntent);
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
-            uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // And also start the second service.
             conn2.startMonitoring();
             mContext.startService(mService2Intent);
-            conn2.waitForConnect(WAIT_TIME);
+            conn2.waitForConnect();
 
             // Force app to go idle now
             cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -565,24 +658,24 @@
 
             // Wait for services to be stopped by system.
             uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
             assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
                     am.getPackageImportance(SIMPLE_PACKAGE_NAME));
 
             // And service should be stopped by system, so just make sure it is disconnected.
-            conn.waitForDisconnect(WAIT_TIME);
-            conn2.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
+            conn2.waitForDisconnect();
 
-            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
             // There may be a transient 'SVC' proc state here.
-            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
         } finally {
             mContext.stopService(mServiceIntent);
             mContext.stopService(mService2Intent);
-            conn.cleanup(WAIT_TIME);
-            conn2.cleanup(WAIT_TIME);
+            conn.cleanup();
+            conn2.cleanup();
 
             uidWatcher.finish();
 
@@ -609,16 +702,17 @@
                 SIMPLE_PACKAGE_NAME + SIMPLE_RECEIVER_START_SERVICE);
 
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
         final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
-                mServiceIntent);
+                mServiceIntent, WAIT_TIME);
+        final WatchUidRunner uidWatcher = controller.getUidWatcher();
 
         try {
             // First kill the process to start out in a stable state.
-            controller.ensureProcessGone(WAIT_TIME);
+            controller.ensureProcessGone();
 
             // Do initial setup.
-            controller.denyBackgroundOp(WAIT_TIME);
+            controller.denyBackgroundOp();
             controller.makeUidIdle();
             controller.removeFromWhitelist();
 
@@ -635,18 +729,18 @@
             }
 
             // Track the uid proc state changes from the broadcast (but not service execution)
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "RCVR", WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
 
             // Put app on temporary whitelist to see if this allows the service start.
-            controller.tempWhitelist(2000);
+            controller.tempWhitelist(TEMP_WHITELIST_DURATION_MS);
 
             // Being on the whitelist means the uid is now active.
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
 
             // Try starting the service now that the app is whitelisted...  should work!
             br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
@@ -654,34 +748,34 @@
             if (brCode != Activity.RESULT_FIRST_USER) {
                 fail("Failed starting service, result=" + brCode);
             }
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
             // Also make sure the uid state reports are as expected.
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
             // We are going to wait until 'SVC', because we may see an intermediate 'RCVR'
             // proc state depending on timing.
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // Good, now stop the service and give enough time to get off the temp whitelist.
             mContext.stopService(mServiceIntent);
-            conn.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
 
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
-            Thread.sleep(3000);
+            controller.removeFromTempWhitelist();
 
             // Going off the temp whitelist causes a spurious proc state report...  that's
             // not ideal, but okay.
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // We don't want to wait for the uid to actually go idle, we can force it now.
             controller.makeUidIdle();
 
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
 
             // Make sure the process is gone so we start over fresh.
-            controller.ensureProcessGone(WAIT_TIME);
+            controller.ensureProcessGone();
 
             // Now that we should be off the temp whitelist, make sure we again can't start.
             br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
@@ -691,13 +785,13 @@
             }
 
             // Track the uid proc state changes from the broadcast (but not service execution)
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
             // There could be a transient 'cached' state here before 'uncached' if uid state
             // changes are dispatched before receiver is started.
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "RCVR", WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // Now put app on whitelist, should allow service to run.
             controller.addToWhitelist();
@@ -708,18 +802,18 @@
             if (brCode != Activity.RESULT_FIRST_USER) {
                 fail("Failed starting service, result=" + brCode);
             }
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
             // Also make sure the uid state reports are as expected.
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // Okay, bring down the service.
             mContext.stopService(mServiceIntent);
-            conn.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
 
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
         } finally {
             mContext.stopService(mServiceIntent);
@@ -736,18 +830,20 @@
         final Intent activityIntent = new Intent();
         activityIntent.setClassName(SIMPLE_PACKAGE_NAME,
                 SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_START_SERVICE);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
         final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
-                mServiceIntent);
+                mServiceIntent, WAIT_TIME);
+        final WatchUidRunner uidWatcher = controller.getUidWatcher();
 
         try {
             // First kill the process to start out in a stable state.
-            controller.ensureProcessGone(WAIT_TIME);
+            controller.ensureProcessGone();
 
             // Do initial setup.
-            controller.denyBackgroundOp(WAIT_TIME);
+            controller.denyBackgroundOp();
             controller.makeUidIdle();
             controller.removeFromWhitelist();
 
@@ -764,37 +860,36 @@
             if (brCode != Activity.RESULT_FIRST_USER) {
                 fail("Failed starting service, result=" + brCode);
             }
-            conn.waitForConnect(WAIT_TIME);
+            conn.waitForConnect();
 
             final String expectedActivityState = (isScreenInteractive() && !isKeyguardLocked())
-                    ? "TOP" : "TPSL";
+                    ? WatchUidRunner.STATE_TOP : WatchUidRunner.STATE_TOP_SLEEPING;
             // Also make sure the uid state reports are as expected.
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE,
-                    expectedActivityState, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, expectedActivityState);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // Okay, bring down the service.
             mContext.stopService(mServiceIntent);
-            conn.waitForDisconnect(WAIT_TIME);
+            conn.waitForDisconnect();
 
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // App isn't yet idle, so we should be able to start the service again.
             mContext.startService(mServiceIntent);
-            conn.waitForConnect(WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+            conn.waitForConnect();
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
 
             // And now fast-forward to the app going idle, service should be stopped.
             controller.makeUidIdle();
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+            uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
 
-            conn.waitForDisconnect(WAIT_TIME);
-            controller.getUidWatcher().waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
-            controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+            conn.waitForDisconnect();
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
 
             // No longer should be able to start service.
             boolean failed = false;
@@ -814,15 +909,271 @@
         }
     }
 
-    private boolean isScreenInteractive() {
-        final PowerManager powerManager =
-                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        return powerManager.isInteractive();
+    /**
+     * Test that a single "can't save state" app has the proper process management
+     * semantics.
+     */
+    public void testCantSaveStateLaunchAndBackground() throws Exception {
+        final Intent activityIntent = new Intent();
+        activityIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+        activityIntent.setAction(Intent.ACTION_MAIN);
+        activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Intent homeIntent = new Intent();
+        homeIntent.setAction(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+        String cmd = "pm grant " + STUB_PACKAGE_NAME + " "
+                + Manifest.permission.PACKAGE_USAGE_STATS;
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        // We don't want to wait for the uid to actually go idle, we can force it now.
+        cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
+                CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
+
+        // This test is also using UidImportanceListener to make sure the correct
+        // heavy-weight state is reported there.
+        UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+                WAIT_TIME);
+        am.addOnUidImportanceListener(uidForegroundListener,
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+        UidImportanceListener uidBackgroundListener = new UidImportanceListener(appInfo.uid,
+                WAIT_TIME);
+        am.addOnUidImportanceListener(uidBackgroundListener,
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE-1);
+
+        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+                WAIT_TIME);
+
+        try {
+            // Start the heavy-weight app, should launch like a normal app.
+            mContext.startActivity(activityIntent);
+
+            // Wait for process state to reflect running activity.
+            uidForegroundListener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+            assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+            // Also make sure the uid state reports are as expected.
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Now go to home, leaving the app.  It should be put in the heavy weight state.
+            mContext.startActivity(homeIntent);
+
+            // 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,
+                    am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+            // While in background, should go in to normal idle state.
+            // Force app to go idle now
+            cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+            // Switch back to heavy-weight app to see if it correctly returns to foreground.
+            mContext.startActivity(activityIntent);
+
+            // Wait for process state to reflect running activity.
+            uidForegroundListener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+            assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+            // Also make sure the uid state reports are as expected.
+            uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
+
+            // Exit activity, check to see if we are now cached.
+            getInstrumentation().getUiAutomation().performGlobalAction(
+                    AccessibilityService.GLOBAL_ACTION_BACK);
+
+            // Wait for process to become cached
+            uidBackgroundListener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+            assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                    am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+            uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+            // While in background, should go in to normal idle state.
+            // Force app to go idle now
+            cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+        } finally {
+            uidWatcher.finish();
+
+            am.removeOnUidImportanceListener(uidForegroundListener);
+            am.removeOnUidImportanceListener(uidBackgroundListener);
+        }
     }
 
-    private boolean isKeyguardLocked() {
-        final KeyguardManager keyguardManager =
-                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        return keyguardManager.isKeyguardLocked();
+    /**
+     * Test that switching between two "can't save state" apps is handled properly.
+     */
+    public void testCantSaveStateLaunchAndSwitch() throws Exception {
+        final Intent activity1Intent = new Intent();
+        activity1Intent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+        activity1Intent.setAction(Intent.ACTION_MAIN);
+        activity1Intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activity1Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Intent activity2Intent = new Intent();
+        activity2Intent.setPackage(CANT_SAVE_STATE_2_PACKAGE_NAME);
+        activity2Intent.setAction(Intent.ACTION_MAIN);
+        activity2Intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activity2Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Intent homeIntent = new Intent();
+        homeIntent.setAction(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+
+        String cmd = "pm grant " + STUB_PACKAGE_NAME + " "
+                + Manifest.permission.PACKAGE_USAGE_STATS;
+        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        // We don't want to wait for the uid to actually go idle, we can force it now.
+        cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
+        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(getInstrumentation(), app1Info.uid,
+                WAIT_TIME);
+
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                CANT_SAVE_STATE_2_PACKAGE_NAME, 0);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(getInstrumentation(), app2Info.uid,
+                WAIT_TIME);
+
+        try {
+            // Start the first heavy-weight app, should launch like a normal app.
+            mContext.startActivity(activity1Intent);
+
+            // Make sure the uid state reports are as expected.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Now go to home, leaving the app.  It should be put in the heavy weight state.
+            mContext.startActivity(homeIntent);
+
+            // Wait for process to go down to background heavy-weight.
+            uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+            // Start the second heavy-weight app, should ask us what to do with the two apps
+            startActivityAndWaitForShow(activity2Intent);
+
+            // First, let's try returning to the original app.
+            maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
+            device.waitForIdle();
+
+            // App should now be back in foreground.
+            uid1Watcher.expect(WatchUidRunner.CMD_UNCACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Return to home.
+            mContext.startActivity(homeIntent);
+            uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+            // Again try starting second heavy-weight app to get prompt.
+            startActivityAndWaitForShow(activity2Intent);
+
+            // Now we'll switch to the new app.
+            maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
+            device.waitForIdle();
+
+            // The original app should now become cached.
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+            // And the new app should start.
+            uid2Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Make sure the original app is idle for cleanliness
+            cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            uid1Watcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+            // Return to home.
+            mContext.startActivity(homeIntent);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+            // Try starting the first heavy weight app, but return to the existing second.
+            startActivityAndWaitForShow(activity1Intent);
+            maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
+            device.waitForIdle();
+            uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Return to home.
+            mContext.startActivity(homeIntent);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+            uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+            // Again start the first heavy weight app, this time actually switching to it
+            startActivityAndWaitForShow(activity1Intent);
+            maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
+            device.waitForIdle();
+
+            // The second app should now become cached.
+            uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+            // And the first app should start.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // Exit activity, check to see if we are now cached.
+            waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
+            getInstrumentation().getUiAutomation().performGlobalAction(
+                    AccessibilityService.GLOBAL_ACTION_BACK);
+            uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+            // Make both apps idle for cleanliness.
+            cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
+            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+        } finally {
+            uid2Watcher.finish();
+            uid1Watcher.finish();
+        }
     }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 31cb632..be30de1 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -38,12 +38,16 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
 import android.platform.test.annotations.RestrictedBuildTest;
+import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
+import android.util.Log;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
 public class ActivityManagerTest extends InstrumentationTestCase {
+    private static final String TAG = ActivityManagerTest.class.getSimpleName();
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
     private static final int WAITFOR_MSEC = 5000;
     private static final String SERVICE_NAME = "android.app.stubs.MockService";
@@ -312,6 +316,31 @@
         assertTrue(destroyed);
     }
 
+    private void executeAndLogShellCommand(String cmd) throws IOException {
+        final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
+        final String output = uiDevice.executeShellCommand(cmd);
+        Log.d(TAG, "executed[" + cmd + "]; output[" + output.trim() + "]");
+    }
+
+    private void setForcedAppStandby(String packageName, boolean enabled) throws IOException {
+        final StringBuilder cmdBuilder = new StringBuilder("appops set ")
+                .append(packageName)
+                .append(" RUN_ANY_IN_BACKGROUND ")
+                .append(enabled ? "ignore" : "allow");
+        executeAndLogShellCommand(cmdBuilder.toString());
+    }
+
+    public void testIsBackgroundRestricted() throws IOException {
+        // This instrumentation runs in the target package's uid.
+        final Context targetContext = mInstrumentation.getTargetContext();
+        final String targetPackage = targetContext.getPackageName();
+        final ActivityManager am = targetContext.getSystemService(ActivityManager.class);
+        setForcedAppStandby(targetPackage, true);
+        assertTrue(am.isBackgroundRestricted());
+        setForcedAppStandby(targetPackage, false);
+        assertFalse(am.isBackgroundRestricted());
+    }
+
     public void testGetMemoryInfo() {
         ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
         mActivityManager.getMemoryInfo(outInfo);
@@ -369,7 +398,6 @@
         final RunningAppProcessInfo ra = new RunningAppProcessInfo();
         ActivityManager.getMyMemoryState(ra);
 
-        assertEquals(mContext.getApplicationInfo().processName, ra.processName);
         assertEquals(android.os.Process.myUid(), ra.uid);
 
         // When an instrumentation test is running, the importance is high.
diff --git a/tests/app/src/android/app/cts/AlertDialogTest.java b/tests/app/src/android/app/cts/AlertDialogTest.java
index b633d46..8eb215b 100644
--- a/tests/app/src/android/app/cts/AlertDialogTest.java
+++ b/tests/app/src/android/app/cts/AlertDialogTest.java
@@ -20,41 +20,55 @@
 import android.app.Instrumentation;
 import android.app.stubs.DialogStubActivity;
 import android.content.DialogInterface;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.view.KeyEvent;
 import android.widget.Button;
 
 import com.android.compatibility.common.util.PollingCheck;
 
 import android.app.stubs.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
 /*
  * Test AlertDialog
  */
 @SmallTest
-public class AlertDialogTest extends ActivityInstrumentationTestCase2<DialogStubActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AlertDialogTest {
     private Instrumentation mInstrumentation;
     private DialogStubActivity mActivity;
     private Button mPositiveButton;
     private Button mNegativeButton;
     private Button mNeutralButton;
 
-    public AlertDialogTest() {
-        super("android.app.stubs", DialogStubActivity.class);
-    }
+    @Rule
+    public ActivityTestRule<DialogStubActivity> mActivityRule =
+            new ActivityTestRule<>(DialogStubActivity.class, true, false);
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInstrumentation = getInstrumentation();
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
     protected void startDialogActivity(int dialogNumber) {
-        mActivity = DialogStubActivity.startDialogActivity(this, dialogNumber);
+        mActivity = DialogStubActivity.startDialogActivity(mActivityRule, dialogNumber);
 
         PollingCheck.waitFor(() -> mActivity.getDialog().isShowing());
     }
 
+    @Test
     public void testAlertDialog() throws Throwable {
         doTestAlertDialog(DialogStubActivity.TEST_ALERTDIALOG);
     }
@@ -92,6 +106,7 @@
         assertTrue(mActivity.isNeutralButtonClicked);
     }
 
+    @Test
     public void testAlertDialogDeprecatedAPI() throws Throwable {
         doTestAlertDialog(DialogStubActivity.TEST_ALERTDIALOG_DEPRECATED);
     }
@@ -132,29 +147,34 @@
         assertEquals(DialogInterface.BUTTON_NEGATIVE, DialogStubActivity.buttonIndex);
     }
 
+    @Test
     public void testAlertDialogAPIWithMessageDeprecated() throws Throwable {
         testAlertDialogAPIWithMessage(true);
     }
 
+    @Test
     public void testAlertDialogAPIWithMessageNotDeprecated() throws Throwable {
         testAlertDialogAPIWithMessage(false);
     }
 
     private void performClick(final Button button) throws Throwable {
-        runTestOnUiThread(() -> button.performClick());
+        mActivityRule.runOnUiThread(() -> button.performClick());
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testCustomAlertDialog() {
         startDialogActivity(DialogStubActivity.TEST_CUSTOM_ALERTDIALOG);
         assertTrue(mActivity.getDialog().isShowing());
     }
 
+    @Test
     public void testCustomAlertDialogView() {
         startDialogActivity(DialogStubActivity.TEST_CUSTOM_ALERTDIALOG_VIEW);
         assertTrue(mActivity.getDialog().isShowing());
     }
 
+    @Test
     public void testCallback() {
         startDialogActivity(DialogStubActivity.TEST_ALERTDIALOG_CALLBACK);
         assertTrue(mActivity.onCreateCalled);
@@ -165,11 +185,13 @@
         assertTrue(mActivity.onKeyUpCalled);
     }
 
+    @Test
     public void testAlertDialogTheme() throws Exception {
         startDialogActivity(DialogStubActivity.TEST_ALERTDIALOG_THEME);
         assertTrue(mActivity.getDialog().isShowing());
     }
 
+    @Test
     public void testAlertDialogCancelable() throws Exception {
         startDialogActivity(DialogStubActivity.TEST_ALERTDIALOG_CANCELABLE);
         assertTrue(mActivity.getDialog().isShowing());
@@ -179,6 +201,7 @@
         assertTrue(mActivity.onCancelCalled);
     }
 
+    @Test
     public void testAlertDialogNotCancelable() throws Exception {
         startDialogActivity(DialogStubActivity.TEST_ALERTDIALOG_NOT_CANCELABLE);
         assertTrue(mActivity.getDialog().isShowing());
@@ -187,11 +210,13 @@
         assertFalse(mActivity.onCancelCalled);
     }
 
+    @Test
     public void testAlertDialogIconDrawable() {
         startDialogActivity(DialogStubActivity.TEST_ALERT_DIALOG_ICON_DRAWABLE);
         assertTrue(mActivity.getDialog().isShowing());
     }
 
+    @Test
     public void testAlertDialogIconAttribute() {
         startDialogActivity(DialogStubActivity.TEST_ALERT_DIALOG_ICON_ATTRIBUTE);
         assertTrue(mActivity.getDialog().isShowing());
diff --git a/tests/app/src/android/app/cts/AlertWindowsTests.java b/tests/app/src/android/app/cts/AlertWindowsTests.java
deleted file mode 100644
index b6b4ce4..0000000
--- a/tests/app/src/android/app/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.cts;
-
-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.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-
-import static com.android.app2.AlertWindowService.MSG_ADD_ALERT_WINDOW;
-import static com.android.app2.AlertWindowService.MSG_ON_ALERT_WINDOW_ADDED;
-import static com.android.app2.AlertWindowService.MSG_ON_ALERT_WINDOW_REMOVED;
-import static com.android.app2.AlertWindowService.MSG_REMOVE_ALERT_WINDOW;
-import static com.android.app2.AlertWindowService.MSG_REMOVE_ALL_ALERT_WINDOWS;
-import static com.android.app2.AlertWindowService.NOTIFICATION_MESSENGER_EXTRA;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-
-import com.android.app2.AlertWindowService;
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-/**
- * Build: mmma -j32 cts/tests/app
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsAppTestCases android.app.cts.AlertWindowsTests
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AlertWindowsTests {
-
-    private static final String TAG = "AlertWindowsTests";
-
-    private static final boolean DEBUG = false;
-    private static final long WAIT_TIME_MS = 2 * 1000;
-
-    private static final String SDK25_PACKAGE_NAME = "com.android.appSdk25";
-
-    private Messenger mService;
-    private String mServicePackageName;
-    private int mServiceUid;
-
-    private PackageManager mPm;
-
-    private ActivityManager mAm;
-    private ActivityManager mAm25; // ActivityManager created for an SDK 25 app context.
-
-    private final Messenger mMessenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));
-    private final Object mAddedLock = new Object();
-    private final Object mRemoveLock = new Object();
-
-    @Before
-    public void setUp() throws Exception {
-        if (DEBUG) Log.e(TAG, "setUp");
-        final Context context = InstrumentationRegistry.getTargetContext();
-
-        mPm = context.getPackageManager();
-
-        mAm = context.getSystemService(ActivityManager.class);
-        mAm25 = context.createPackageContext(SDK25_PACKAGE_NAME, 0)
-                .getSystemService(ActivityManager.class);
-
-        final Intent intent = new Intent();
-        intent.setClassName(AlertWindowService.class.getPackage().getName(),
-                AlertWindowService.class.getName());
-        intent.putExtra(NOTIFICATION_MESSENGER_EXTRA, mMessenger);
-        // Needs to be both BIND_NOT_FOREGROUND and BIND_ALLOW_OOM_MANAGEMENT to avoid the binding
-        // to this instrumentation test from increasing its importance.
-        context.bindService(intent, mConnection,
-                BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_ALLOW_OOM_MANAGEMENT);
-        synchronized (mConnection) {
-            // Wait for alert window service to be connection before processing.
-            mConnection.wait(WAIT_TIME_MS);
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (DEBUG) Log.e(TAG, "tearDown");
-        if (mService != null) {
-            mService.send(Message.obtain(null, MSG_REMOVE_ALL_ALERT_WINDOWS));
-        }
-        final Context context = InstrumentationRegistry.getTargetContext();
-        context.unbindService(mConnection);
-        mAm = null;
-    }
-
-    @Test
-    public void testAlertWindowOomAdj() throws Exception {
-        // Alert windows are always hidden when running in VR.
-        if (isRunningInVR()) {
-            return;
-        }
-        setAlertWindowPermission(true /* allow */);
-
-
-        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
-        // TODO AM.getUidImportance() sometimes return a different value from what
-        // getPackageImportance() returns... b/37950472
-        // assertUidImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
-        addAlertWindow();
-        // Process importance should be increased to visible when the service has an alert window.
-        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
-        addAlertWindow();
-        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
-        setAlertWindowPermission(false /* allow */);
-        // Process importance should no longer be visible since its alert windows are not allowed to
-        // be visible.
-        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
-        setAlertWindowPermission(true /* allow */);
-        // They can show again so importance should be visible again.
-        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
-        removeAlertWindow();
-        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
-        removeAlertWindow();
-        // Process importance should no longer be visible when the service no longer as alert
-        // windows.
-        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-    }
-
-    private void addAlertWindow() throws Exception {
-        mService.send(Message.obtain(null, MSG_ADD_ALERT_WINDOW));
-        synchronized (mAddedLock) {
-            // Wait for window addition confirmation before proceeding.
-            mAddedLock.wait(WAIT_TIME_MS);
-        }
-    }
-
-    private void removeAlertWindow() throws Exception {
-        mService.send(Message.obtain(null, MSG_REMOVE_ALERT_WINDOW));
-        synchronized (mRemoveLock) {
-            // Wait for window removal confirmation before proceeding.
-            mRemoveLock.wait(WAIT_TIME_MS);
-        }
-    }
-
-    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);
-    }
-
-    private void assertImportance(Function<ActivityManager, Integer> apiCaller,
-            int expectedForO, int expectedForPreO) throws Exception {
-        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.apply(mAm);
-        } while (actual != expectedForO && (SystemClock.uptimeMillis() < TIMEOUT));
-
-        assertEquals(expectedForO, actual);
-
-        // Check the result for pre-O apps.
-        assertEquals(expectedForPreO, (int) apiCaller.apply(mAm25));
-    }
-
-    /**
-     * Make sure {@link ActivityManager#getPackageImportance} returns the expected value.
-     */
-    private void assertPackageImportance(int expectedForO, int expectedForPreO) throws Exception {
-        assertImportance(am -> am.getPackageImportance(mServicePackageName),
-                expectedForO, expectedForPreO);
-    }
-
-    /**
-     * Make sure {@link ActivityManager#getUidImportance(int)} returns the expected value.
-     */
-    private void assertUidImportance(int expectedForO, int expectedForPreO) throws Exception {
-        assertImportance(am -> am.getUidImportance(mServiceUid),
-                expectedForO, expectedForPreO);
-    }
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) Log.e(TAG, "onServiceConnected");
-            mService = new Messenger(service);
-            mServicePackageName = name.getPackageName();
-            try {
-                mServiceUid = mPm.getPackageUid(mServicePackageName, 0);
-            } catch (NameNotFoundException e) {
-                throw new RuntimeException("getPackageUid() failed.", e);
-            }
-            synchronized (mConnection) {
-                notifyAll();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) Log.e(TAG, "onServiceDisconnected");
-            mService = null;
-            mServicePackageName = null;
-            mServiceUid = 0;
-        }
-    };
-
-    private class IncomingHandler extends Handler {
-
-        IncomingHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ON_ALERT_WINDOW_ADDED:
-                    synchronized (mAddedLock) {
-                        if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_ADDED");
-                        mAddedLock.notifyAll();
-                    }
-                    break;
-                case MSG_ON_ALERT_WINDOW_REMOVED:
-                    synchronized (mRemoveLock) {
-                        if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_REMOVED");
-                        mRemoveLock.notifyAll();
-                    }
-                    break;
-                default:
-                    super.handleMessage(msg);
-            }
-        }
-    }
-
-    private boolean isRunningInVR() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
-             == Configuration.UI_MODE_TYPE_VR_HEADSET) {
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/tests/app/src/android/app/cts/AspectRatioTests.java b/tests/app/src/android/app/cts/AspectRatioTests.java
deleted file mode 100644
index fa6154e..0000000
--- a/tests/app/src/android/app/cts/AspectRatioTests.java
+++ /dev/null
@@ -1,256 +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.app.cts;
-
-import android.app.stubs.MetaDataMaxAspectRatioActivity;
-import com.android.appSdk25.Sdk25MaxAspectRatioActivity;
-
-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 android.app.Activity;
-import android.app.stubs.MaxAspectRatioActivity;
-import android.app.stubs.MaxAspectRatioResizeableActivity;
-import android.app.stubs.MaxAspectRatioUnsetActivity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Point;
-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.View;
-import android.view.WindowManager;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import static android.content.Context.WINDOW_SERVICE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static org.junit.Assert.fail;
-
-/**
- * Build: mmma -j32 cts/tests/app
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsAppTestCases android.app.cts.AspectRatioTests
- */
-@RunWith(AndroidJUnit4.class)
-public class AspectRatioTests {
-    private static final String TAG = "AspectRatioTests";
-
-    // The max. aspect ratio the test activities are using.
-    private static final float MAX_ASPECT_RATIO = 1.0f;
-
-    // Max supported aspect ratio for pre-O apps.
-    private static final float MAX_PRE_O_ASPECT_RATIO = 1.86f;
-
-    // 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;
-
-    @Rule
-    public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
-            new ActivityTestRule<>(MaxAspectRatioActivity.class,
-                    false /* initialTouchMode */, false /* launchActivity */);
-
-    @Rule
-    public ActivityTestRule<MaxAspectRatioResizeableActivity> mMaxAspectRatioResizeableActivity =
-            new ActivityTestRule<>(MaxAspectRatioResizeableActivity.class,
-                    false /* initialTouchMode */, false /* launchActivity */);
-
-    @Rule
-    public ActivityTestRule<MetaDataMaxAspectRatioActivity> mMetaDataMaxAspectRatioActivity =
-        new ActivityTestRule<>(MetaDataMaxAspectRatioActivity.class,
-            false /* initialTouchMode */, false /* launchActivity */);
-
-    @Rule
-    public ActivityTestRule<MaxAspectRatioUnsetActivity> mMaxAspectRatioUnsetActivity =
-            new ActivityTestRule<>(MaxAspectRatioUnsetActivity.class,
-                    false /* initialTouchMode */, false /* launchActivity */);
-
-    // TODO: Can't use this to start an activity in a different process...sigh.
-    @Rule
-    public ActivityTestRule<Sdk25MaxAspectRatioActivity> mSdk25MaxAspectRatioActivity =
-            new ActivityTestRule<>(Sdk25MaxAspectRatioActivity.class, "com.android.appSdk25",
-                    268435456, false /* initialTouchMode */, false /* launchActivity */);
-
-    private interface AssertAspectRatioCallback {
-        void assertAspectRatio(float actual);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        finishActivity(mMaxAspectRatioActivity);
-        finishActivity(mMaxAspectRatioResizeableActivity);
-        finishActivity(mSdk25MaxAspectRatioActivity);
-        finishActivity(mMaxAspectRatioUnsetActivity);
-        finishActivity(mMetaDataMaxAspectRatioActivity);
-    }
-
-    @Test
-    @Presubmit
-    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);
-
-        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;
-
-        if (deviceAspectRatio < expectedMinAspectRatio) {
-            fail("deviceAspectRatio=" + deviceAspectRatio
-                    + " is less than expectedMinAspectRatio=" + expectedMinAspectRatio);
-        }
-    }
-
-    @Test
-    @Presubmit
-    public void testMaxAspectRatio() throws Exception {
-        runTest(launchActivity(mMaxAspectRatioActivity),
-                actual -> {
-                    if (MAX_ASPECT_RATIO >= actual) return;
-                    fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
-                });
-    }
-
-    @Test
-    @Presubmit
-    public void testMetaDataMaxAspectRatio() throws Exception {
-        runTest(launchActivity(mMetaDataMaxAspectRatioActivity),
-            actual -> {
-                if (MAX_ASPECT_RATIO >= actual) return;
-                fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
-            });
-    }
-
-    @Test
-    // TODO: Currently 10% flaky so not part of pre-submit for now
-    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);
-
-        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);
-                });
-    }
-
-    @Test
-    @Presubmit
-    public void testMaxAspectRatioUnsetActivity() throws Exception {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final float expected = getAspectRatio(context);
-
-        // Since this activity didn't set an aspect ratio, its aspect ratio shouldn't be less than
-        // the device's
-        runTest(launchActivity(mMaxAspectRatioUnsetActivity),
-                actual -> {
-                    if (aspectRatioEqual(expected, actual) || expected < actual) return;
-                    fail("actual=" + actual + " is less than expected=" + expected);
-                });
-    }
-
-    @Test
-    // TODO(b/35810513): Can't use rule to start an activity in a different process. Need a
-    // different way to make this test happen...host side? Sigh...
-    @Ignore
-    public void testMaxAspectRatioPreOActivity() throws Exception {
-        runTest(launchActivity(mSdk25MaxAspectRatioActivity),
-                actual -> {
-                    if (MAX_PRE_O_ASPECT_RATIO >= actual) return;
-                    fail("actual=" + actual + " is greater than expected=" + MAX_PRE_O_ASPECT_RATIO);
-                });
-    }
-
-    private void runTest(Activity activity, AssertAspectRatioCallback callback) {
-        callback.assertAspectRatio(getAspectRatio(activity));
-
-        // 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
-        // this activity if changing the orientation will cause a relaunch?
-//        activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-//        waitForIdle();
-//        callback.assertAspectRatio(getAspectRatio(activity));
-//
-//        activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
-//        waitForIdle();
-//        callback.assertAspectRatio(getAspectRatio(activity));
-    }
-
-    private float getAspectRatio(Context context) {
-        final Display display =
-                ((WindowManager) context.getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
-        final Point size = new Point();
-        display.getSize(size);
-        final float longSide = Math.max(size.x, size.y);
-        final float shortSide = Math.min(size.x, size.y);
-        return longSide / shortSide;
-    }
-
-    private Activity launchActivity(ActivityTestRule activityRule) {
-        final Activity activity = activityRule.launchActivity(null);
-        waitForIdle();
-        return activity;
-    }
-
-    private void finishActivity(ActivityTestRule activityRule) {
-        final Activity activity = activityRule.getActivity();
-        if (activity != null) {
-            activity.finish();
-        }
-    }
-
-    private void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
-    private static boolean aspectRatioEqual(float a, float b) {
-        // Aspect ratios are considered equal if they ware within to significant digits.
-        float diff = Math.abs(a - b);
-        return diff < 0.01f;
-    }
-}
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index c16101f..b2749c7 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -38,8 +38,11 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -53,12 +56,25 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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 java.lang.ref.WeakReference;
 
-public class DialogTest extends ActivityInstrumentationTestCase2<DialogStubActivity> {
+@RunWith(AndroidJUnit4.class)
+public class DialogTest {
 
     protected static final long SLEEP_TIME = 200;
-    private static final String STUB_ACTIVITY_PACKAGE = "android.app.stubs";
     private static final long TEST_TIMEOUT = 1000L;
 
     /**
@@ -76,23 +92,23 @@
     private Context mContext;
     private DialogStubActivity mActivity;
 
+    @Rule
+    public ActivityTestRule<DialogStubActivity> mActivityRule =
+            new ActivityTestRule<>(DialogStubActivity.class, false, false);
 
-    public DialogTest() {
-        super(STUB_ACTIVITY_PACKAGE, DialogStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInstrumentation = getInstrumentation();
+    @Before
+    public void setup() throws Throwable {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getContext();
+        mInstrumentation.waitForIdleSync();
     }
 
     private void startDialogActivity(int dialogNumber) {
-        mActivity = DialogStubActivity.startDialogActivity(this, dialogNumber);
+        mActivity = DialogStubActivity.startDialogActivity(mActivityRule, dialogNumber);
     }
 
     @UiThreadTest
+    @Test
     public void testConstructor() {
         new Dialog(mContext);
         Dialog d = new Dialog(mContext, 0);
@@ -110,6 +126,7 @@
         assertTextAppearanceStyle(ta);
     }
 
+    @Test
     public void testConstructor_protectedCancellable() {
         startDialogActivity(DialogStubActivity.TEST_PROTECTED_CANCELABLE);
         mActivity.onCancelListenerCalled = false;
@@ -117,6 +134,7 @@
         assertTrue(mActivity.onCancelListenerCalled);
     }
 
+    @Test
     public void testConstructor_protectedNotCancellable() {
         startDialogActivity(DialogStubActivity.TEST_PROTECTED_NOT_CANCELABLE);
         mActivity.onCancelListenerCalled = false;
@@ -124,6 +142,22 @@
         assertFalse(mActivity.onCancelListenerCalled);
     }
 
+    @Test
+    public void testConstructor_protectedCancellableEsc() {
+        startDialogActivity(DialogStubActivity.TEST_PROTECTED_CANCELABLE);
+        mActivity.onCancelListenerCalled = false;
+        sendKeys(KeyEvent.KEYCODE_ESCAPE);
+        assertTrue(mActivity.onCancelListenerCalled);
+    }
+
+    @Test
+    public void testConstructor_protectedNotCancellableEsc() {
+        startDialogActivity(DialogStubActivity.TEST_PROTECTED_NOT_CANCELABLE);
+        mActivity.onCancelListenerCalled = false;
+        sendKeys(KeyEvent.KEYCODE_ESCAPE);
+        assertFalse(mActivity.onCancelListenerCalled);
+    }
+
     private void assertTextAppearanceStyle(TypedArray ta) {
         final int defValue = -1;
         // get Theme and assert
@@ -141,11 +175,12 @@
         assertEquals(expectedTa.getColor(R.styleable.TextAppearance_textColorHighlight, defValue),
                 ta.getColor(R.styleable.TextAppearance_textColorHighlight, defValue));
         assertEquals(expectedTa.getDimension(R.styleable.TextAppearance_textSize, defValue),
-                ta.getDimension(R.styleable.TextAppearance_textSize, defValue));
+                ta.getDimension(R.styleable.TextAppearance_textSize, defValue), Float.MIN_VALUE);
         assertEquals(expectedTa.getInt(R.styleable.TextAppearance_textStyle, defValue),
                 ta.getInt(R.styleable.TextAppearance_textStyle, defValue));
     }
 
+    @Test
     public void testOnStartCreateStop(){
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -158,6 +193,20 @@
         assertTrue(d.isOnStopCalled);
     }
 
+    @Test
+    public void testOnStartCreateStopEsc(){
+        startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
+        final TestDialog d = (TestDialog) mActivity.getDialog();
+
+        assertTrue(d.isOnStartCalled);
+        assertTrue(d.isOnCreateCalled);
+
+        assertFalse(d.isOnStopCalled);
+        sendKeys(KeyEvent.KEYCODE_ESCAPE);
+        assertTrue(d.isOnStopCalled);
+    }
+
+    @Test
     public void testAccessOwnerActivity() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         Dialog d = mActivity.getDialog();
@@ -173,7 +222,7 @@
             // expected
         }
 
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 Dialog dialog = new Dialog(mContext);
                 assertNull(dialog.getOwnerActivity());
@@ -182,12 +231,13 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testShow() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
         final View decor = d.getWindow().getDecorView();
 
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.hide();
             }
@@ -197,7 +247,7 @@
         assertEquals(View.GONE, decor.getVisibility());
         assertTrue(d.isShowing());
 
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.show();
             }
@@ -210,6 +260,7 @@
         assertFalse(d.isShowing());
     }
 
+    @Test
     public void testOnSaveInstanceState() throws InterruptedException {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -228,11 +279,13 @@
         TestDialog.onRestoreInstanceStateObserver.await();
     }
 
+    @Test
     public void testGetCurrentFocus() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
         assertNull(d.getCurrentFocus());
-        runTestOnUiThread(new Runnable() {
+
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.takeKeyEvents(true);
                 d.setContentView(R.layout.alert_dialog_text_entry);
@@ -241,17 +294,18 @@
         mInstrumentation.waitForIdleSync();
 
         sendKeys(KeyEvent.KEYCODE_0);
-        // When mWindow is not null getCUrrentFocus is the view in dialog
+        // When mWindow is not null getCurrentFocus is the view in dialog
         assertEquals(d.getWindow().getCurrentFocus(), d.getCurrentFocus());
     }
 
+    @Test
     public void testSetContentView() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
         assertNotNull(d);
 
         // set content view to a four elements layout
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.setContentView(R.layout.alert_dialog_text_entry);
             }
@@ -267,7 +321,7 @@
         final LayoutInflater inflate1 = d.getLayoutInflater();
 
         // set content view to a two elements layout
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.setContentView(inflate1.inflate(R.layout.alert_dialog_text_entry_2, null));
             }
@@ -284,7 +338,7 @@
         final LayoutInflater inflate2 = mActivity.getLayoutInflater();
 
         // set content view to a four elements layout
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.setContentView(inflate2.inflate(R.layout.alert_dialog_text_entry, null), lp);
             }
@@ -303,7 +357,7 @@
         lp2.width = ViewGroup.LayoutParams.WRAP_CONTENT;
 
         // add a check box view
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.addContentView(inflate3.inflate(R.layout.checkbox_layout, null), lp2);
             }
@@ -318,6 +372,41 @@
         assertNotNull(d.findViewById(R.id.password_edit));
     }
 
+    @Test
+    public void testRequireViewById() throws Throwable {
+        startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
+        final Dialog d = mActivity.getDialog();
+        assertNotNull(d);
+
+        // set content view to a four elements layout
+        mActivityRule.runOnUiThread(new Runnable() {
+            public void run() {
+                d.setContentView(R.layout.alert_dialog_text_entry);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // check if four elements are right there
+        assertNotNull(d.requireViewById(R.id.username_view));
+        assertNotNull(d.requireViewById(R.id.username_edit));
+        assertNotNull(d.requireViewById(R.id.password_view));
+        assertNotNull(d.requireViewById(R.id.password_edit));
+        try {
+            d.requireViewById(R.id.check_box); // not present
+            fail("should not get here, check_box should not be found");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+        try {
+            d.requireViewById(View.NO_ID); // invalid
+            fail("should not get here, NO_ID should not be found");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+
+    @Test
     public void testSetTitle() {
         final String expectedTitle = "Test Dialog Without theme";
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
@@ -335,6 +424,7 @@
                 (String) d.getWindow().getAttributes().getTitle());
     }
 
+    @Test
     public void testOnKeyDownKeyUp() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -354,36 +444,50 @@
         assertTrue(d.onKeyDownReturn);
     }
 
-     public void testOnKeyMultiple() {
-         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-         final TestDialog d = (TestDialog) mActivity.getDialog();
+    @Test
+    public void testOnKeyMultiple() {
+        startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
+        final TestDialog d = (TestDialog) mActivity.getDialog();
 
-         assertNull(d.keyMultipleEvent);
-         d.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_UNKNOWN));
-         assertTrue(d.isOnKeyMultipleCalled);
-         assertFalse(d.onKeyMultipleReturn);
-         assertEquals(KeyEvent.KEYCODE_UNKNOWN, d.keyMultipleEvent.getKeyCode());
-         assertEquals(KeyEvent.ACTION_MULTIPLE, d.keyMultipleEvent.getAction());
-     }
+        assertNull(d.keyMultipleEvent);
+        d.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_UNKNOWN));
+        assertTrue(d.isOnKeyMultipleCalled);
+        assertFalse(d.onKeyMultipleReturn);
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, d.keyMultipleEvent.getKeyCode());
+        assertEquals(KeyEvent.ACTION_MULTIPLE, d.keyMultipleEvent.getAction());
+    }
 
+    private MotionEvent sendTouchEvent(long downTime, int action, float x, float y) {
+        long eventTime = downTime;
+        if (action != MotionEvent.ACTION_DOWN) {
+            eventTime += 1;
+        }
+        MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        mInstrumentation.getUiAutomation().injectInputEvent(event, true);
+        mInstrumentation.waitForIdleSync();
+        return event;
+    }
+
+    @Test
     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;
 
         assertNull(d.onTouchEvent);
         assertNull(d.touchEvent);
         assertFalse(d.isOnTouchEventCalled);
 
-        // Send a touch event outside the activity.  The event will be ignored
+        // Send a touch event outside the dialog window.  Expect the event to be ignored
         // because closeOnTouchOutside is false.
         d.setCanceledOnTouchOutside(false);
 
         long now = SystemClock.uptimeMillis();
-        MotionEvent touchMotionEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
-                containingRect.left + 1, containingRect.top + 1, 0);
-        mInstrumentation.sendPointerSync(touchMotionEvent);
+        MotionEvent touchMotionEvent = sendTouchEvent(now, MotionEvent.ACTION_DOWN, x, y);
 
         new PollingCheck(TEST_TIMEOUT) {
             protected boolean check() {
@@ -392,43 +496,41 @@
         }.run();
 
         assertMotionEventEquals(touchMotionEvent, d.touchEvent);
-
         assertTrue(d.isOnTouchEventCalled);
         assertMotionEventEquals(touchMotionEvent, d.onTouchEvent);
         d.isOnTouchEventCalled = false;
         assertTrue(d.isShowing());
+        touchMotionEvent.recycle();
+        // Send ACTION_UP to keep the event stream consistent
+        sendTouchEvent(now, MotionEvent.ACTION_UP, x, y).recycle();
 
-        // Watch activities cover the entire screen, so there is no way to touch outside.
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            // Send a touch event outside the activity.  This time the dialog will be dismissed
-            // because closeOnTouchOutside is true.
-            d.setCanceledOnTouchOutside(true);
-
-            touchMotionEvent = MotionEvent.obtain(now, now + 1, MotionEvent.ACTION_DOWN,
-                    containingRect.left + 1, containingRect.top + 1, 0);
-            mInstrumentation.sendPointerSync(touchMotionEvent);
-
-            new PollingCheck(TEST_TIMEOUT) {
-                protected boolean check() {
-                    return d.dispatchTouchEventResult;
-                }
-            }.run();
-
-            assertMotionEventEquals(touchMotionEvent, d.touchEvent);
-
-            assertTrue(d.isOnTouchEventCalled);
-            assertMotionEventEquals(touchMotionEvent, d.onTouchEvent);
-            assertFalse(d.isShowing());
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            // Watch activities cover the entire screen, so there is no way to touch outside.
+            return;
         }
+
+        // Send a touch event outside the dialog window. Expect the dialog to be dismissed
+        // because closeOnTouchOutside is true.
+
+        d.setCanceledOnTouchOutside(true);
+        now = SystemClock.uptimeMillis();
+        touchMotionEvent = sendTouchEvent(now, MotionEvent.ACTION_DOWN, x, y);
+
+        new PollingCheck(TEST_TIMEOUT) {
+            protected boolean check() {
+                return d.dispatchTouchEventResult;
+            }
+        }.run();
+
+        assertMotionEventEquals(touchMotionEvent, d.touchEvent);
+        assertTrue(d.isOnTouchEventCalled);
+        assertMotionEventEquals(touchMotionEvent, d.onTouchEvent);
+        assertFalse(d.isShowing());
+        touchMotionEvent.recycle();
+        sendTouchEvent(now, MotionEvent.ACTION_UP, x, y).recycle();
     }
 
-    private int getStatusBarHeight() {
-        final Rect rect = new Rect();
-        Window window = mActivity.getWindow();
-        window.getDecorView().getWindowVisibleDisplayFrame(rect);
-        return rect.top;
-    }
-
+    @Test
     public void testTrackballEvent() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -448,16 +550,18 @@
     }
 
     private void assertMotionEventEquals(final MotionEvent expected, final MotionEvent actual) {
+        assertNotNull(actual);
         assertEquals(expected.getDownTime(), actual.getDownTime());
         assertEquals(expected.getEventTime(), actual.getEventTime());
         assertEquals(expected.getAction(), actual.getAction());
         assertEquals(expected.getMetaState(), actual.getMetaState());
-        assertEquals(expected.getSize(), actual.getSize());
+        assertEquals(expected.getSize(), actual.getSize(), Float.MIN_VALUE);
         // As MotionEvent doc says the value of X and Y coordinate may have
         // a fraction for input devices that are sub-pixel precise,
         // so we won't assert them here.
     }
 
+    @Test
     public void testOnWindowAttributesChanged() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -467,7 +571,7 @@
 
         final WindowManager.LayoutParams lp = d.getWindow().getAttributes();
         lp.setTitle("test OnWindowAttributesChanged");
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.getWindow().setAttributes(lp);
             }
@@ -478,6 +582,7 @@
         assertSame(lp, d.getWindow().getAttributes());
     }
 
+    @Test
     public void testOnContentChanged() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -485,7 +590,7 @@
 
         assertFalse(d.isOnContentChangedCalled);
 
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.setContentView(R.layout.alert_dialog_text_entry);
             }
@@ -495,6 +600,7 @@
         assertTrue(d.isOnContentChangedCalled);
     }
 
+    @Test
     public void testOnWindowFocusChanged() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -502,7 +608,7 @@
         d.isOnWindowFocusChangedCalled = false;
 
         // show a new dialog, the new dialog get focus
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.showDialog(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
             }
@@ -517,6 +623,7 @@
         }.run();
     }
 
+    @Test
     public void testDispatchKeyEvent() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -565,6 +672,7 @@
      * 5. onContextMenuClosed is called whenever the context menu is being closed (either by
      * the user canceling the menu with the back/menu button, or when an item is selected).
      */
+    @Test
     public void testContextMenu() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -573,7 +681,7 @@
         parent.addView(v);
         assertFalse(v.isShowContextMenuCalled);
         // Register for context menu and open it
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.addContentView(parent, new LinearLayout.LayoutParams(
                         ViewGroup.LayoutParams.MATCH_PARENT,
@@ -598,14 +706,14 @@
         v.isShowContextMenuCalled = false;
         d.isOnCreateContextMenuCalled = false;
         // Unregister for context menu, and try to open it
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.unregisterForContextMenu(v);
             }
         });
         mInstrumentation.waitForIdleSync();
 
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.openContextMenu(v);
             }
@@ -616,7 +724,7 @@
         assertFalse(d.isOnCreateContextMenuCalled);
 
         // Register for context menu and open it again
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.registerForContextMenu(v);
                 d.openContextMenu(v);
@@ -638,9 +746,11 @@
         assertFalse(d.isOnContextMenuClosedCalled);
     }
 
+    @Test
     public void testOnSearchRequested() {
     }
 
+    @Test
     public void testTakeKeyEvents() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
@@ -660,7 +770,7 @@
     }
 
     private void takeKeyEvents(final Dialog d, final boolean get) throws Throwable {
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.takeKeyEvents(get);
             }
@@ -668,15 +778,17 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testRequestWindowFeature() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         // called requestWindowFeature at TestDialog onCreate method
         assertTrue(((TestDialog) mActivity.getDialog()).isRequestWindowFeature);
     }
 
+    @Test
     public void testSetFeatureDrawableResource() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.getDialog().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
                         R.drawable.robot);
@@ -685,9 +797,10 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testSetFeatureDrawableUri() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.getDialog().setFeatureDrawableUri(Window.FEATURE_LEFT_ICON,
                         Uri.parse("http://www.google.com"));
@@ -696,9 +809,10 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testSetFeatureDrawable() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.getDialog().setFeatureDrawable(Window.FEATURE_LEFT_ICON, 
                         new MockDrawable());
@@ -707,9 +821,10 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testSetFeatureDrawableAlpha() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.getDialog().setFeatureDrawableAlpha(Window.FEATURE_LEFT_ICON, 0);
             }
@@ -717,13 +832,15 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
     public void testGetLayoutInflater() {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
         assertEquals(d.getWindow().getLayoutInflater(), d.getLayoutInflater());
     }
 
-    public void testSetCancelable_true() {
+    @Test
+    public void testSetCancellable_true() {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
 
@@ -733,6 +850,7 @@
         assertFalse(d.isShowing());
     }
 
+    @Test
     public void testSetCancellable_false() {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
@@ -743,17 +861,41 @@
         assertTrue(d.isShowing());
     }
 
+    @Test
+    public void testSetCancellableEsc_true() {
+        startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
+        final Dialog d = mActivity.getDialog();
+
+        d.setCancelable(true);
+        assertTrue(d.isShowing());
+        sendKeys(KeyEvent.KEYCODE_ESCAPE);
+        assertFalse(d.isShowing());
+    }
+
+    @Test
+    public void testSetCancellableEsc_false() {
+        startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
+        final Dialog d = mActivity.getDialog();
+
+        d.setCancelable(false);
+        assertTrue(d.isShowing());
+        sendKeys(KeyEvent.KEYCODE_ESCAPE);
+        assertTrue(d.isShowing());
+    }
+
     /*
      * Test point
      * 1. Cancel the dialog.
      * 2. Set a listener to be invoked when the dialog is canceled.
      */
+    @Test
     public void testCancel_listener() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
 
         assertTrue(d.isShowing());
         mOnCancelListenerCalled = false;
+
         d.setOnCancelListener(new OnCancelListener() {
             public void onCancel(DialogInterface dialog) {
                 mOnCancelListenerCalled = true;
@@ -765,6 +907,7 @@
         assertTrue(mOnCancelListenerCalled);
     }
 
+    @Test
     public void testCancel_noListener() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
@@ -778,6 +921,7 @@
         assertFalse(mOnCancelListenerCalled);
     }
 
+    @Test
     public void testSetCancelMessage() throws Exception {
         mCalledCallback = false;
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
@@ -805,6 +949,7 @@
      * 1. Set a listener to be invoked when the dialog is dismissed.
      * 2. set onDismissListener to null, it will not changed flag after dialog dismissed.
      */
+    @Test
     public void testSetOnDismissListener_listener() throws Throwable {
         mCalledCallback = false;
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
@@ -823,6 +968,7 @@
         assertFalse(d.isShowing());
     }
 
+    @Test
     public void testSetOnDismissListener_noListener() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
         final Dialog d = mActivity.getDialog();
@@ -834,6 +980,7 @@
         assertFalse(d.isShowing());
     }
 
+    @Test
     public void testSetDismissMessage() throws Throwable {
         mCalledCallback = false;
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
@@ -857,7 +1004,7 @@
     }
 
     private void dialogDismiss(final Dialog d) throws Throwable {
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.dismiss();
             }
@@ -866,7 +1013,7 @@
     }
 
     private void dialogCancel(final Dialog d) throws Throwable {
-        runTestOnUiThread(new Runnable() {
+        mActivityRule.runOnUiThread(new Runnable() {
             public void run() {
                 d.cancel();
             }
@@ -874,6 +1021,11 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    private void sendKeys(int keyCode) {
+        mInstrumentation.sendKeyDownUpSync(keyCode);
+        mInstrumentation.waitForIdleSync();
+    }
+
     private static class MockDismissCancelHandler extends Handler {
         private WeakReference<DialogInterface> mDialog;
 
@@ -928,10 +1080,6 @@
             mOnCreateContextMenuListener = l;
         }
 
-        public OnCreateContextMenuListener getOnCreateContextMenuListener() {
-            return mOnCreateContextMenuListener;
-        }
-
         @Override
         public boolean showContextMenu() {
             isShowContextMenuCalled = true;
diff --git a/tests/app/src/android/app/cts/NewDocumentTest.java b/tests/app/src/android/app/cts/NewDocumentTest.java
index 52d8df9..9c966dd 100644
--- a/tests/app/src/android/app/cts/NewDocumentTest.java
+++ b/tests/app/src/android/app/cts/NewDocumentTest.java
@@ -35,6 +35,7 @@
         final Intent intent = new Intent();
         intent.setClass(getContext(), NewDocumentTestActivity.class);
         intent.setData(TEST_URI);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         try (final Receiver receiver = new Receiver(NewDocumentTestActivity.NOTIFY_RESUME)) {
             getContext().startActivity(intent);
diff --git a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
index 162815f..7931a1f 100644
--- a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -37,10 +37,21 @@
         NotificationChannelGroup group =  new NotificationChannelGroup("1", "one");
         assertEquals("1", group.getId());
         assertEquals("one", group.getName());
+        assertFalse(group.isBlocked());
+        assertNull(group.getDescription());
+        assertEquals(0, group.getChannels().size());
+    }
+
+    public void testIsBlocked() {
+        NotificationChannelGroup group =  new NotificationChannelGroup("1", "one");
+        group.setBlocked(true);
+        assertTrue(group.isBlocked());
     }
 
     public void testWriteToParcel() {
         NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+        group.setBlocked(true);
+        group.setDescription("bananas!");
         Parcel parcel = Parcel.obtain();
         group.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -51,8 +62,12 @@
 
     public void testClone() {
         NotificationChannelGroup group =  new NotificationChannelGroup("1", "one");
+        group.setBlocked(true);
+        group.setDescription("bananas");
         NotificationChannelGroup cloned = group.clone();
         assertEquals("1", cloned.getId());
         assertEquals("one", cloned.getName());
+        assertTrue(cloned.isBlocked());
+        assertEquals("bananas", cloned.getDescription());
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index b6077ca..070b2eb 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -16,11 +16,13 @@
 
 package android.app.cts;
 
+import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.UiAutomation;
 import android.app.stubs.R;
 import android.content.Context;
 import android.content.Intent;
@@ -29,13 +31,20 @@
 import android.media.AudioAttributes;
 import android.media.session.MediaSession;
 import android.net.Uri;
-import android.os.Bundle;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
 import android.provider.Settings;
 import android.provider.Telephony.Threads;
 import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import junit.framework.Assert;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -80,6 +89,63 @@
             }
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
+
+        List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
+        // Delete all groups.
+        for (NotificationChannelGroup ncg : groups) {
+            mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
+        }
+    }
+
+    public void testOnlyPostPCanToggleAlarmsAndMediaTest() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+            // Post-P can toggle alarms and media
+            // toggle on alarms and media:
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+                            | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, 0, 0));
+            NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
+            assertTrue((policy.priorityCategories
+                    & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) != 0);
+            assertTrue((policy.priorityCategories
+                    & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0);
+
+            // toggle off alarms and media
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+            policy = mNotificationManager.getNotificationPolicy();
+            assertTrue((policy.priorityCategories
+                    & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0);
+            assertTrue((policy.priorityCategories &
+                    NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0);
+        } else {
+            // 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_SYSTEM_OTHER;
+
+            // attempt to toggle off alarms and media:
+            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_SYSTEM_OTHER);
+
+            // attempt to toggle on alarms and media:
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+                            | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, 0, 0));
+            policy = mNotificationManager.getNotificationPolicy();
+            assertEquals(alarmBit, policy.priorityCategories
+                    & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
+            assertEquals(mediaBit, policy.priorityCategories
+                    & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER);
+        }
     }
 
     public void testCreateChannelGroup() throws Exception {
@@ -88,18 +154,43 @@
                 new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
         channel.setGroup(ncg.getId());
         mNotificationManager.createNotificationChannelGroup(ncg);
+        final NotificationChannel ungrouped =
+                new NotificationChannel(mId + "!", "name", NotificationManager.IMPORTANCE_DEFAULT);
         try {
             mNotificationManager.createNotificationChannel(channel);
+            mNotificationManager.createNotificationChannel(ungrouped);
 
             List<NotificationChannelGroup> ncgs =
                     mNotificationManager.getNotificationChannelGroups();
             assertEquals(1, ncgs.size());
-            assertEquals(ncg, ncgs.get(0));
+            assertEquals(ncg.getName(), ncgs.get(0).getName());
+            assertEquals(ncg.getDescription(), ncgs.get(0).getDescription());
+            assertEquals(channel.getId(), ncgs.get(0).getChannels().get(0).getId());
         } finally {
             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
         }
     }
 
+    public void testGetChannelGroup() 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(ncg.getId());
+
+        mNotificationManager.createNotificationChannelGroup(ncg);
+        mNotificationManager.createNotificationChannelGroup(ncg2);
+        mNotificationManager.createNotificationChannel(channel);
+
+        NotificationChannelGroup actual =
+                mNotificationManager.getNotificationChannelGroup(ncg.getId());
+        assertEquals(ncg.getId(), actual.getId());
+        assertEquals(ncg.getName(), actual.getName());
+        assertEquals(ncg.getDescription(), actual.getDescription());
+        assertEquals(channel.getId(), actual.getChannels().get(0).getId());
+    }
+
     public void testDeleteChannelGroup() throws Exception {
         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
         final NotificationChannel channel =
@@ -151,6 +242,46 @@
                 mNotificationManager.getNotificationChannel(mId).getImportance());
     }
 
+    public void testCreateChannel_addToGroup() throws Exception {
+        String oldGroup = null;
+        String newGroup = "new group";
+        mNotificationManager.createNotificationChannelGroup(
+                new NotificationChannelGroup(newGroup, newGroup));
+
+        NotificationChannel channel =
+                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setGroup(oldGroup);
+        mNotificationManager.createNotificationChannel(channel);
+
+        channel.setGroup(newGroup);
+        mNotificationManager.createNotificationChannel(channel);
+
+        final NotificationChannel updatedChannel =
+                mNotificationManager.getNotificationChannel(mId);
+        assertEquals("Failed to add non-grouped channel to a group on update ",
+                newGroup, updatedChannel.getGroup());
+    }
+
+    public void testCreateChannel_cannotChangeGroup() throws Exception {
+        String oldGroup = "old group";
+        String newGroup = "new group";
+        mNotificationManager.createNotificationChannelGroup(
+                new NotificationChannelGroup(oldGroup, oldGroup));
+        mNotificationManager.createNotificationChannelGroup(
+                new NotificationChannelGroup(newGroup, newGroup));
+
+        NotificationChannel channel =
+                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setGroup(oldGroup);
+        mNotificationManager.createNotificationChannel(channel);
+        channel.setGroup(newGroup);
+        mNotificationManager.createNotificationChannel(channel);
+        final NotificationChannel updatedChannel =
+                mNotificationManager.getNotificationChannel(mId);
+        assertEquals("Channels should not be allowed to change groups",
+                oldGroup, updatedChannel.getGroup());
+    }
+
     public void testCreateSameChannelDoesNotUpdate() throws Exception {
         final NotificationChannel channel =
                 new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
@@ -328,6 +459,54 @@
         }
     }
 
+    public void testNotify_blockedChannel() throws Exception {
+        mNotificationManager.cancelAll();
+
+        NotificationChannel channel =
+                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_NONE);
+        mNotificationManager.createNotificationChannel(channel);
+
+        int id = 1;
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.black)
+                        .setWhen(System.currentTimeMillis())
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
+            fail("found unexpected notification id=" + id);
+        }
+    }
+
+    public void testNotify_blockedChannelGroup() throws Exception {
+        mNotificationManager.cancelAll();
+
+        NotificationChannelGroup group = new NotificationChannelGroup(mId, "group name");
+        group.setBlocked(true);
+        mNotificationManager.createNotificationChannelGroup(group);
+        NotificationChannel channel =
+                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setGroup(mId);
+        mNotificationManager.createNotificationChannel(channel);
+
+        int id = 1;
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.black)
+                        .setWhen(System.currentTimeMillis())
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
+            fail("found unexpected notification id=" + id);
+        }
+    }
+
     public void testCancel() throws Exception {
         final int id = 9;
         sendNotification(id, R.drawable.black);
@@ -683,6 +862,7 @@
             found = false;
             final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
             for (StatusBarNotification sbn : sbns) {
+                Log.d(TAG, "Found " + sbn.getKey());
                 if (sbn.getId() == id) {
                     found = true;
                     break;
@@ -741,4 +921,32 @@
         assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
         assertEquals(expected.getGroup(), actual.getGroup());
     }
+
+    private void toggleNotificationPolicyAccess(String packageName,
+            Instrumentation instrumentation, boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+
+        // Get permission to change dnd policy
+        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();
+        }
+
+        NotificationManager nm = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        Assert.assertEquals("Notification Policy Access Grant is " +
+                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+                nm.isNotificationPolicyAccessGranted());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 25c25e3..82150c6 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -17,6 +17,7 @@
 package android.app.cts;
 
 import android.app.Notification;
+import android.app.Notification.Action.Builder;
 import android.app.Notification.MessagingStyle.Message;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -24,12 +25,18 @@
 import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
 import android.test.AndroidTestCase;
 import android.widget.RemoteViews;
+import java.util.function.Consumer;
 
-import org.mockito.internal.matchers.Not;
+import java.util.ArrayList;
 
 public class NotificationTest extends AndroidTestCase {
     private static final String TEXT_RESULT_KEY = "text";
@@ -236,6 +243,61 @@
         assertEquals(true, mAction.getAllowGeneratedReplies());
     }
 
+    public void testNotification_addPerson() {
+        String name = "name";
+        String key = "key";
+        String uri = "name:name";
+        Notification.Person person = new Notification.Person()
+                .setName(name)
+                .setIcon(Icon.createWithResource(mContext, 1))
+                .setKey(key)
+                .setUri(uri);
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
+                .setSmallIcon(1)
+                .setContentTitle(CONTENT_TITLE)
+                .addPerson(person)
+                .build();
+
+        ArrayList<Notification.Person> restoredPeople = mNotification.extras.getParcelableArrayList(
+                Notification.EXTRA_PEOPLE_LIST);
+        assertNotNull(restoredPeople);
+        Notification.Person restoredPerson = restoredPeople.get(0);
+        assertNotNull(restoredPerson);
+        assertNotNull(restoredPerson.getIcon());
+        assertEquals(name, restoredPerson.getName());
+        assertEquals(key, restoredPerson.getKey());
+        assertEquals(uri, restoredPerson.getUri());
+    }
+
+    public void testNotification_MessagingStyle_people() {
+        String name = "name";
+        String key = "key";
+        String uri = "name:name";
+        Notification.Person user = new Notification.Person()
+                .setName(name)
+                .setIcon(Icon.createWithResource(mContext, 1))
+                .setKey(key)
+                .setUri(uri);
+        Notification.Person participant = new Notification.Person().setName("sender");
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(user)
+                .addMessage("text", 0, participant)
+                .addMessage(new Message("text 2", 0, participant));
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
+                .setSmallIcon(1)
+                .setStyle(messagingStyle)
+                .build();
+
+        Notification.Person restoredPerson = mNotification.extras.getParcelable(
+                Notification.EXTRA_MESSAGING_PERSON);
+        assertNotNull(restoredPerson);
+        assertNotNull(restoredPerson.getIcon());
+        assertEquals(name, restoredPerson.getName());
+        assertEquals(key, restoredPerson.getKey());
+        assertEquals(uri, restoredPerson.getUri());
+        assertNotNull(mNotification.extras.getParcelableArray(Notification.EXTRA_MESSAGES));
+    }
+
+
     public void testMessagingStyle_historicMessages() {
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
@@ -252,6 +314,68 @@
                 mNotification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES));
     }
 
+    public void testMessagingStyle_isGroupConversation() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle("test conversation title");
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
+    public void testMessagingStyle_isGroupConversation_noConversationTitle() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle(null);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
+    public void testMessagingStyle_isGroupConversation_withConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(false)
+                .setConversationTitle("test conversation title");
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertFalse(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
+    public void testMessagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle(null);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertFalse(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
     public void testToString() {
         mNotification = new Notification();
         assertNotNull(mNotification.toString());
@@ -294,6 +418,36 @@
         assertTrue(a.getDataOnlyRemoteInputs()[0].isDataOnly());
     }
 
+    public void testAction_builder_hasDefault() {
+        Notification.Action action = makeNotificationAction(null);
+        assertEquals(Notification.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
+    }
+
+    public void testAction_builder_setSemanticAction() {
+        Notification.Action action = makeNotificationAction(
+                builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY));
+        assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
+    }
+
+    public void testAction_parcel() {
+        Notification.Action action = writeAndReadParcelable(
+                makeNotificationAction(builder -> {
+                    builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_ARCHIVE);
+                    builder.setAllowGeneratedReplies(true);
+                }));
+
+        assertEquals(Notification.Action.SEMANTIC_ACTION_ARCHIVE, action.getSemanticAction());
+        assertTrue(action.getAllowGeneratedReplies());
+    }
+
+    public void testAction_clone() {
+        Notification.Action action = makeNotificationAction(
+                builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_DELETE));
+        assertEquals(
+                Notification.Action.SEMANTIC_ACTION_DELETE,
+                action.clone().getSemanticAction());
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
@@ -320,4 +474,29 @@
     private static Notification.Action.Builder newActionBuilder() {
         return new Notification.Action.Builder(0, "title", null);
     }
+
+    /**
+     * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel
+     * method before reading it out again to check that it was sent properly.
+     */
+    private static <T extends Parcelable> T writeAndReadParcelable(T original) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(original, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.readParcelable(/* classLoader */ null);
+    }
+
+    /**
+     * Creates a Notification.Action by mocking initial dependencies and then applying
+     * transformations if they're defined.
+     */
+    private Notification.Action makeNotificationAction(
+            @Nullable Consumer<Builder> transformation) {
+        Notification.Action.Builder actionBuilder =
+            new Notification.Action.Builder(null, "Test Title", null);
+        if (transformation != null) {
+            transformation.accept(actionBuilder);
+        }
+        return actionBuilder.build();
+    }
 }
diff --git a/tests/app/src/android/app/cts/PipActivityTest.java b/tests/app/src/android/app/cts/PipActivityTest.java
index bceb748..214de59 100644
--- a/tests/app/src/android/app/cts/PipActivityTest.java
+++ b/tests/app/src/android/app/cts/PipActivityTest.java
@@ -57,14 +57,8 @@
                         }
                     });
                 } else {
-                    boolean pipSupportDisabled = false;
-                    try {
-                        pipSupportDisabled = !mActivity.enterPictureInPictureMode(
-                                new PictureInPictureParams.Builder().build());
-                    } catch (IllegalStateException e) {
-                        pipSupportDisabled = true;
-                    }
-                    assertTrue(pipSupportDisabled);
+                    assertTrue(!mActivity.enterPictureInPictureMode(
+                            new PictureInPictureParams.Builder().build()));
 
                     // Entering PIP mode is not synchronous, so waiting for completion of all work
                     // on UI thread.
diff --git a/tests/app/src/android/app/cts/RemoteInputTest.java b/tests/app/src/android/app/cts/RemoteInputTest.java
index f53226b..b8e0fce 100644
--- a/tests/app/src/android/app/cts/RemoteInputTest.java
+++ b/tests/app/src/android/app/cts/RemoteInputTest.java
@@ -102,21 +102,12 @@
             throws Throwable {
         CharSequence charSequence = "value doesn't matter";
         Uri uri = Uri.parse("Some Uri");
-        RemoteInput input =
-                new RemoteInput.Builder(RESULT_KEY)
-                .setAllowDataType(MIME_TYPE, true)
-                .build();
+        RemoteInput input = newTextAndDataRemoteInput();
         Intent intent = new Intent();
 
-        Map<String, Uri> dataResults = new HashMap<>();
-        dataResults.put(MIME_TYPE, uri);
-        RemoteInput.addDataResultToIntent(input, intent, dataResults);
-
-        Bundle textResults = new Bundle();
-        textResults.putCharSequence(input.getResultKey(), charSequence);
-        RemoteInput[] arr = new RemoteInput[1];
-        arr[0] = input;
-        RemoteInput.addResultsToIntent(arr, intent, textResults);
+        addDataResultsToIntent(input, intent, uri);
+        addTextResultsToIntent(input, intent, charSequence);
+        RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_FREE_FORM_INPUT);
 
         verifyIntentHasTextResults(intent, charSequence);
         verifyIntentHasDataResults(intent, uri);
@@ -126,24 +117,69 @@
             throws Throwable {
         CharSequence charSequence = "value doesn't matter";
         Uri uri = Uri.parse("Some Uri");
-        RemoteInput input =
-                new RemoteInput.Builder(RESULT_KEY)
-                .setAllowDataType(MIME_TYPE, true)
-                .build();
+        RemoteInput input = newTextAndDataRemoteInput();
         Intent intent = new Intent();
 
+        addTextResultsToIntent(input, intent, charSequence);
+        addDataResultsToIntent(input, intent, uri);
+        RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
+
+        verifyIntentHasTextResults(intent, charSequence);
+        verifyIntentHasDataResults(intent, uri);
+    }
+
+    public void testGetResultsSource_emptyIntent() {
+        Intent intent = new Intent();
+
+        assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT, RemoteInput.getResultsSource(intent));
+    }
+
+    public void testGetResultsSource_addDataAndTextResults() {
+        CharSequence charSequence = "value doesn't matter";
+        Uri uri = Uri.parse("Some Uri");
+        RemoteInput input = newTextAndDataRemoteInput();
+        Intent intent = new Intent();
+
+        addTextResultsToIntent(input, intent, charSequence);
+        addDataResultsToIntent(input, intent, uri);
+
+        assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT, RemoteInput.getResultsSource(intent));
+    }
+
+    public void testGetResultsSource_setSource() {
+        Intent intent = new Intent();
+
+        RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
+
+        assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(intent));
+    }
+
+    public void testGetResultsSource_setSourceAndAddDataAndTextResults() {
+        CharSequence charSequence = "value doesn't matter";
+        Uri uri = Uri.parse("Some Uri");
+        RemoteInput input = newTextAndDataRemoteInput();
+        Intent intent = new Intent();
+
+        RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
+        addTextResultsToIntent(input, intent, charSequence);
+        addDataResultsToIntent(input, intent, uri);
+
+        assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(intent));
+    }
+
+    private static void addTextResultsToIntent(RemoteInput input, Intent intent,
+            CharSequence charSequence) {
         Bundle textResults = new Bundle();
         textResults.putCharSequence(input.getResultKey(), charSequence);
         RemoteInput[] arr = new RemoteInput[1];
         arr[0] = input;
         RemoteInput.addResultsToIntent(arr, intent, textResults);
+    }
 
+    private static void addDataResultsToIntent(RemoteInput input, Intent intent, Uri uri) {
         Map<String, Uri> dataResults = new HashMap<>();
         dataResults.put(MIME_TYPE, uri);
         RemoteInput.addDataResultToIntent(input, intent, dataResults);
-
-        verifyIntentHasTextResults(intent, charSequence);
-        verifyIntentHasDataResults(intent, uri);
     }
 
     private static void verifyIntentHasTextResults(Intent intent, CharSequence expected) {
@@ -181,5 +217,12 @@
             .setAllowDataType(MIME_TYPE, true)
             .build();
     }
+
+    private static RemoteInput newTextAndDataRemoteInput() {
+        return new RemoteInput.Builder(RESULT_KEY)
+            .setAllowFreeFormInput(true)
+            .setAllowDataType(MIME_TYPE, true)
+            .build();
+    }
 }
 
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 5f8202d..d660040 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -26,6 +26,7 @@
 import android.app.stubs.LocalForegroundService;
 import android.app.stubs.LocalGrantedService;
 import android.app.stubs.LocalService;
+import android.app.stubs.NullService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +37,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
@@ -85,6 +87,41 @@
         }
     }
 
+    private static class NullServiceConnection implements ServiceConnection {
+        boolean mNullBinding = false;
+
+        @Override public void onServiceConnected(ComponentName name, IBinder service) {}
+        @Override public void onServiceDisconnected(ComponentName name) {}
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            synchronized (this) {
+                mNullBinding = true;
+                this.notifyAll();
+            }
+        }
+
+        public void waitForNullBinding(final long timeout) {
+            long now = SystemClock.uptimeMillis();
+            final long end = now + timeout;
+            synchronized (this) {
+                while (!mNullBinding && (now < end)) {
+                    try {
+                        this.wait(end - now);
+                    } catch (InterruptedException e) {
+                    }
+                    now = SystemClock.uptimeMillis();
+                }
+            }
+        }
+
+        public boolean nullBindingReceived() {
+            synchronized (this) {
+                return mNullBinding;
+            }
+        }
+    }
+
     private class TestConnection implements ServiceConnection {
         private final boolean mExpectDisconnect;
         private final boolean mSetReporter;
@@ -831,4 +868,28 @@
             // expected
         }
     }
+
+    /**
+     * Verify that when the requested service's onBind() returns null,
+     * the connection's onNullBinding() method is invoked.
+     */
+    @MediumTest
+    public void testNullServiceBinder() throws Exception {
+        Intent intent = new Intent(mContext, NullService.class);
+        intent.setAction("testNullServiceBinder");
+        NullServiceConnection conn1 = new NullServiceConnection();
+        NullServiceConnection conn2 = new NullServiceConnection();
+        try {
+            assertTrue(mContext.bindService(intent, conn1, Context.BIND_AUTO_CREATE));
+            conn1.waitForNullBinding(DELAY);
+            assertTrue(conn1.nullBindingReceived());
+
+            assertTrue(mContext.bindService(intent, conn2, Context.BIND_AUTO_CREATE));
+            conn2.waitForNullBinding(DELAY);
+            assertTrue(conn2.nullBindingReceived());
+        } finally {
+            mContext.unbindService(conn1);
+            mContext.unbindService(conn2);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 53b1bf2..fb31878 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -125,6 +125,7 @@
             assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_AR);
 
             assertFalse("Devices supporting external cameras must have a representative camera " +
                     "connected for testing",
@@ -142,6 +143,7 @@
         boolean fullCamera = false;
         boolean manualSensor = false;
         boolean manualPostProcessing = false;
+        boolean motionTracking = false;
         boolean raw = false;
         CameraCharacteristics[] cameraChars = new CameraCharacteristics[cameraIds.length];
         for (String cameraId : cameraIds) {
@@ -163,6 +165,9 @@
                     case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW:
                         raw = true;
                         break;
+                  case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING:
+                        motionTracking = true;
+                        break;
                     default:
                         // Capabilities don't have a matching system feature
                         break;
@@ -174,6 +179,7 @@
         assertFeature(manualPostProcessing,
                 PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
         assertFeature(raw, PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+        assertFeature(motionTracking, PackageManager.FEATURE_CAMERA_AR);
     }
 
     private void checkFrontCamera() {
diff --git a/tests/app/src/android/app/cts/TaskDescriptionTest.java b/tests/app/src/android/app/cts/TaskDescriptionTest.java
new file mode 100644
index 0000000..408930e
--- /dev/null
+++ b/tests/app/src/android/app/cts/TaskDescriptionTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.cts;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+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.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.TaskDescription;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.Activity;
+import android.app.stubs.MockActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+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
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class TaskDescriptionTest {
+    private static final String TEST_LABEL = "test-label";
+    private static final int TEST_NO_DATA = 0;
+    private static final int TEST_RES_DATA = 777;
+    private static final int TEST_COLOR = Color.BLACK;
+
+    @Rule
+    public ActivityTestRule<MockActivity> mTaskDescriptionActivity =
+        new ActivityTestRule<>(MockActivity.class,
+            false /* initialTouchMode */, false /* launchActivity */);
+
+    @Test
+    public void testBitmapConstructor() throws Exception {
+        final Activity activity = mTaskDescriptionActivity.launchActivity(null);
+        final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        bitmap.eraseColor(0);
+        activity.setTaskDescription(new TaskDescription(TEST_LABEL, bitmap, TEST_COLOR));
+        assertTaskDescription(activity, TEST_LABEL, TEST_NO_DATA);
+    }
+
+    @Test
+    public void testResourceConstructor() throws Exception {
+        final Activity activity = mTaskDescriptionActivity.launchActivity(null);
+        activity.setTaskDescription(new TaskDescription(TEST_LABEL, TEST_RES_DATA, TEST_COLOR));
+        assertTaskDescription(activity, TEST_LABEL, TEST_RES_DATA);
+    }
+
+    private void assertTaskDescription(Activity activity, String label, int resId) {
+        final ActivityManager am = (ActivityManager) activity.getSystemService(ACTIVITY_SERVICE);
+        List<RecentTaskInfo> recentsTasks = am.getRecentTasks(1 /* maxNum */, 0 /* flags */);
+        if (!recentsTasks.isEmpty()) {
+            final RecentTaskInfo info = recentsTasks.get(0);
+            if (activity.getTaskId() == info.id) {
+                final TaskDescription td = info.taskDescription;
+                assertNotNull(td);
+                if (resId == TEST_NO_DATA) {
+                    assertNotNull(td.getIcon());
+                    assertNotNull(td.getIconFilename());
+                } else {
+                    assertNull(td.getIconFilename());
+                    assertNull(td.getIcon());
+                }
+                assertEquals(resId, td.getIconResource());
+                assertEquals(label, td.getLabel());
+                return;
+            }
+        }
+        fail("Did not find activity (id=" + activity.getTaskId() + ") in recent tasks list");
+    }
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
index f5bb5a3..5a00932 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
@@ -32,6 +32,7 @@
 
     final Context mContext;
     final Intent mIntent;
+    final long mDefaultWaitTime;
     boolean mMonitoring;
     boolean mBound;
     IBinder mService;
@@ -47,8 +48,13 @@
     };
 
     public ServiceConnectionHandler(Context context, Intent intent) {
+        this(context, intent, 5*1000);
+    }
+
+    public ServiceConnectionHandler(Context context, Intent intent, long defaultWaitTime) {
         mContext = context;
         mIntent = intent;
+        mDefaultWaitTime = defaultWaitTime;
     }
 
     public void startMonitoring() {
@@ -64,6 +70,10 @@
         }
     }
 
+    public void waitForConnect() {
+        waitForConnect(mDefaultWaitTime);
+    }
+
     public void waitForConnect(long timeout) {
         final long endTime = SystemClock.uptimeMillis() + timeout;
 
@@ -85,6 +95,10 @@
         return mService;
     }
 
+    public void waitForDisconnect() {
+        waitForDisconnect(mDefaultWaitTime);
+    }
+
     public void waitForDisconnect(long timeout) {
         final long endTime = SystemClock.uptimeMillis() + timeout;
 
@@ -120,6 +134,10 @@
         }
     }
 
+    public void bind() {
+        bind(mDefaultWaitTime);
+    }
+
     public void bind(long timeout) {
         synchronized (this) {
             if (mBound) {
@@ -137,6 +155,10 @@
         }
     }
 
+    public void unbind() {
+        unbind(mDefaultWaitTime);
+    }
+
     public void unbind(long timeout) {
         synchronized (this) {
             if (!mBound) {
@@ -155,6 +177,10 @@
         }
     }
 
+    public void cleanup() {
+        cleanup(mDefaultWaitTime);
+    }
+
     public void cleanup(long timeout) {
         synchronized (this) {
             if (mBound) {
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
index 9cad7a3..e8008df 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
@@ -42,6 +42,7 @@
     final String mMyPackageName;
     final Intent[] mServiceIntents;
     final String mServicePackage;
+    final long mDefaultWaitTime;
 
     final ActivityManager mAm;
     final Parcel mData;
@@ -54,11 +55,18 @@
     public ServiceProcessController(Context context, Instrumentation instrumentation,
             String myPackageName, Intent[] serviceIntents)
             throws IOException, PackageManager.NameNotFoundException {
+        this(context, instrumentation, myPackageName, serviceIntents, 5*1000);
+    }
+
+    public ServiceProcessController(Context context, Instrumentation instrumentation,
+            String myPackageName, Intent[] serviceIntents, long defaultWaitTime)
+            throws IOException, PackageManager.NameNotFoundException {
         mContext = context;
         mInstrumentation = instrumentation;
         mMyPackageName = myPackageName;
         mServiceIntents = serviceIntents;
         mServicePackage = mServiceIntents[0].getComponent().getPackageName();
+        mDefaultWaitTime = defaultWaitTime;
         String cmd = "pm grant " + mMyPackageName + " " + Manifest.permission.PACKAGE_USAGE_STATS;
         String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
         /*
@@ -72,21 +80,26 @@
         mData = Parcel.obtain();
         mConnections = new ServiceConnectionHandler[serviceIntents.length];
         for (int i=0; i<serviceIntents.length; i++) {
-            mConnections[i] = new ServiceConnectionHandler(mContext, serviceIntents[i]);
+            mConnections[i] = new ServiceConnectionHandler(mContext, serviceIntents[i],
+                    mDefaultWaitTime);
         }
 
         ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
                 mServicePackage, 0);
         mUid = appInfo.uid;
 
-        mUidForegroundListener = new UidImportanceListener(appInfo.uid);
+        mUidForegroundListener = new UidImportanceListener(appInfo.uid, mDefaultWaitTime);
         mAm.addOnUidImportanceListener(mUidForegroundListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
-        mUidGoneListener = new UidImportanceListener(appInfo.uid);
+        mUidGoneListener = new UidImportanceListener(appInfo.uid, mDefaultWaitTime);
         mAm.addOnUidImportanceListener(mUidGoneListener,
                 ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY);
 
-        mUidWatcher = new WatchUidRunner(instrumentation, appInfo.uid);
+        mUidWatcher = new WatchUidRunner(instrumentation, appInfo.uid, mDefaultWaitTime);
+    }
+
+    public void denyBackgroundOp() throws IOException {
+        denyBackgroundOp(mDefaultWaitTime);
     }
 
     public void denyBackgroundOp(long timeout) throws IOException {
@@ -123,6 +136,11 @@
         String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
     }
 
+    public void removeFromTempWhitelist() throws IOException {
+        String cmd = "cmd deviceidle tempwhitelist -r " + mServicePackage;
+        SystemUtil.runShellCommand(mInstrumentation, cmd);
+    }
+
     public void cleanup() throws IOException {
         removeFromWhitelist();
         allowBackgroundOp();
@@ -152,6 +170,10 @@
         return mUidWatcher;
     }
 
+    public void ensureProcessGone() {
+        ensureProcessGone(mDefaultWaitTime);
+    }
+
     public void ensureProcessGone(long timeout) {
         for (int i=0; i<mConnections.length; i++) {
             mConnections[i].bind(timeout);
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java b/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
index 3eb402a..f015441 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
@@ -25,11 +25,17 @@
  */
 public final class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
     final int mUid;
+    final long mDefaultWaitTime;
 
     int mLastValue = -1;
 
     public UidImportanceListener(int uid) {
+        this(uid, 5*1000);
+    }
+
+    public UidImportanceListener(int uid, long defaultWaitTime) {
         mUid = uid;
+        mDefaultWaitTime = defaultWaitTime;
     }
 
     @Override
@@ -43,6 +49,10 @@
         }
     }
 
+    public int waitForValue(int minValue, int maxValue) {
+        return waitForValue(minValue, maxValue, mDefaultWaitTime);
+    }
+
     public int waitForValue(int minValue, int maxValue, long timeout) {
         final long endTime = SystemClock.uptimeMillis()+timeout;
 
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
index e2abf67..c077d32 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
@@ -39,6 +39,8 @@
  * bit CtsAppTestCases:ActivityManagerProcessStateTest
  */
 public class WatchUidRunner {
+    static final String TAG = "WatchUidRunner";
+
     public static final int CMD_PROCSTATE = 0;
     public static final int CMD_ACTIVE = 1;
     public static final int CMD_IDLE = 2;
@@ -46,6 +48,27 @@
     public static final int CMD_CACHED = 4;
     public static final int CMD_GONE = 5;
 
+    public static final String STATE_PERSISTENT = "PER";
+    public static final String STATE_PERSISTENT_UI = "PERU";
+    public static final String STATE_TOP = "TOP";
+    public static final String STATE_BOUND_FG_SERVICE = "BFGS";
+    public static final String STATE_FG_SERVICE = "FGS";
+    public static final String STATE_TOP_SLEEPING = "TPSL";
+    public static final String STATE_IMPORTANT_FG = "IMPF";
+    public static final String STATE_IMPORTANT_BG = "IMPB";
+    public static final String STATE_TRANSIENT_BG = "TRNB";
+    public static final String STATE_BACKUP = "BKUP";
+    public static final String STATE_HEAVY_WEIGHT = "HVY";
+    public static final String STATE_SERVICE = "SVC";
+    public static final String STATE_RECEIVER = "RCVR";
+    public static final String STATE_HOME = "HOME";
+    public static final String STATE_LAST = "LAST";
+    public static final String STATE_CACHED_ACTIVITY = "CAC";
+    public static final String STATE_CACHED_ACTIVITY_CLIENT = "CACC";
+    public static final String STATE_CACHED_RECENT = "CRE";
+    public static final String STATE_CACHED_EMPTY = "CEM";
+    public static final String STATE_NONEXISTENT = "NONE";
+
     static final String[] COMMAND_TO_STRING = new String[] {
             "procstate", "active", "idle", "uncached", "cached", "gone"
     };
@@ -53,6 +76,7 @@
     final Instrumentation mInstrumentation;
     final int mUid;
     final String mUidStr;
+    final long mDefaultWaitTime;
     final Pattern mSpaceSplitter;
     final ParcelFileDescriptor mReadFd;
     final FileInputStream mReadStream;
@@ -68,9 +92,14 @@
     boolean mStopping;
 
     public WatchUidRunner(Instrumentation instrumentation, int uid) {
+        this(instrumentation, uid, 5*1000);
+    }
+
+    public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime) {
         mInstrumentation = instrumentation;
         mUid = uid;
         mUidStr = Integer.toString(uid);
+        mDefaultWaitTime = defaultWaitTime;
         mSpaceSplitter = Pattern.compile("\\s+");
         ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation().executeShellCommandRw(
                 "am watch-uids");
@@ -96,6 +125,10 @@
         }
     }
 
+    public void expect(int cmd, String procState) {
+        expect(cmd, procState, mDefaultWaitTime);
+    }
+
     public void expect(int cmd, String procState, long timeout) {
         long waitUntil = SystemClock.uptimeMillis() + timeout;
         String[] line = waitForNextLine(waitUntil);
@@ -109,6 +142,10 @@
         }
     }
 
+    public void waitFor(int cmd, String procState) {
+        waitFor(cmd, procState, mDefaultWaitTime);
+    }
+
     public void waitFor(int cmd, String procState, long timeout) {
         long waitUntil = SystemClock.uptimeMillis() + timeout;
         while (true) {
@@ -120,11 +157,11 @@
                 if (line.length >= 3 && procState.equals(line[2])) {
                     return;
                 } else {
-                    Log.d("XXXX", "Skipping because procstate not " + procState + ": "
+                    Log.d(TAG, "Skipping because procstate not " + procState + ": "
                             + Arrays.toString(line));
                 }
             } else {
-                Log.d("XXXX", "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
+                Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
                         + Arrays.toString(line));
             }
         }
@@ -170,14 +207,14 @@
             try {
                 while ((line = readNextLine()) != null) {
                     if (line.length < 2) {
-                        Log.d("XXXXX", "Skipping: " + mLastReadLine);
+                        Log.d(TAG, "Skipping too short: " + mLastReadLine);
                         continue;
                     }
                     if (!line[0].equals(mUidStr)) {
-                        Log.d("XXXXX", "Skipping: " + mLastReadLine);
+                        Log.d(TAG, "Skipping ignored uid: " + mLastReadLine);
                         continue;
                     }
-                    Log.d("XXXXX", "Enqueueing: " + mLastReadLine);
+                    Log.d(TAG, "Enqueueing: " + mLastReadLine);
                     synchronized (mPendingLines) {
                         if (mStopping) {
                             return;
@@ -187,7 +224,7 @@
                     }
                 }
             } catch (IOException e) {
-                Log.w("WatchUidRunner", "Failed reading", e);
+                Log.w(TAG, "Failed reading", e);
             }
         }
 
diff --git a/tests/aslr/AndroidTest.xml b/tests/aslr/AndroidTest.xml
index 8b9c893..87c5dfb 100644
--- a/tests/aslr/AndroidTest.xml
+++ b/tests/aslr/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Aslr Malloc test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index b8ab1f9..a16086b 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -18,6 +18,9 @@
     package="android.autofillservice.cts" >
 
     <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" />
 
     <application>
 
@@ -32,18 +35,26 @@
             </intent-filter>
         </activity>
         <activity android:name=".PreFilledLoginActivity" />
-        <activity android:name=".WelcomeActivity"/>
+        <activity android:name=".LoginWithStringsActivity" />
+        <activity android:name=".WelcomeActivity" android:taskAffinity=".WelcomeActivity"/>
         <activity android:name=".ViewAttributesTestActivity" />
         <activity android:name=".AuthenticationActivity" />
         <activity android:name=".ManualAuthenticationActivity" />
-        <activity android:name=".CheckoutActivity"/>
+        <activity android:name=".CheckoutActivity" android:taskAffinity=".CheckoutActivity"/>
         <activity android:name=".InitializedCheckoutActivity" />
         <activity android:name=".DatePickerCalendarActivity" />
         <activity android:name=".DatePickerSpinnerActivity" />
         <activity android:name=".TimePickerClockActivity" />
         <activity android:name=".TimePickerSpinnerActivity" />
         <activity android:name=".FatActivity" />
-        <activity android:name=".VirtualContainerActivity"/>
+        <activity android:name=".VirtualContainerActivity">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it maks 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=".OptionalSaveActivity" />
         <activity android:name=".AllAutofillableViewsActivity" />
         <activity android:name=".GridActivity"/>
@@ -66,6 +77,18 @@
         <activity android:name=".WebViewActivity"/>
         <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 app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".MultiWindowLoginActivity" />
+        <activity android:name=".MultiWindowEmptyActivity"
+            android:taskAffinity="nobody.but.EmptyActivity"
+            android:exported="true" />
 
         <service
             android:name=".InstrumentedAutoFillService"
@@ -76,6 +99,18 @@
             </intent-filter>
         </service>
         <service
+            android:name=".InstrumentedAutoFillServiceCompatMode"
+            android:label="InstrumentedAutoFillServiceCompatMode"
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+            <meta-data
+                android:name="android.autofill"
+                android:resource="@xml/autofill_service_compat_mode_config">
+            </meta-data>
+        </service>
+        <service
             android:name=".NoOpAutofillService"
             android:label="NoOpAutofillService"
             android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
@@ -83,6 +118,14 @@
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
+        <!--  BadAutofillService does not declare the proper permission -->
+        <service
+            android:name=".BadAutofillService"
+            android:label="BadAutofillService">
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation
diff --git a/tests/autofillservice/AndroidTest.xml b/tests/autofillservice/AndroidTest.xml
index 73fffd2..e8cfc3c 100644
--- a/tests/autofillservice/AndroidTest.xml
+++ b/tests/autofillservice/AndroidTest.xml
@@ -15,7 +15,7 @@
 -->
 <configuration description="Config for AutoFill Framework CTS tests.">
   <option name="test-suite-tag" value="cts" />
-  <option name="config-descriptor:metadata" key="component" value="framework" />
+  <option name="config-descriptor:metadata" key="component" value="autofill" />
 
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
diff --git a/tests/autofillservice/res/layout/dialog_launcher_activity.xml b/tests/autofillservice/res/layout/dialog_launcher_activity.xml
new file mode 100644
index 0000000..62d6d9b
--- /dev/null
+++ b/tests/autofillservice/res/layout/dialog_launcher_activity.xml
@@ -0,0 +1,32 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:importantForAutofill="noExcludeDescendants"
+    android:orientation="vertical" >
+
+    <Button
+        android:id="@+id/launch_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Launch" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/empty.xml b/tests/autofillservice/res/layout/empty.xml
index 7687408..906e561 100644
--- a/tests/autofillservice/res/layout/empty.xml
+++ b/tests/autofillservice/res/layout/empty.xml
@@ -16,5 +16,6 @@
   -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/empty"
     android:layout_width="match_parent"
     android:layout_height="match_parent" />
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
index 6d43320..9b4a8b6 100644
--- a/tests/autofillservice/res/layout/fat_activity.xml
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -136,4 +136,10 @@
 
     </LinearLayout>
 
+    <View
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:autofillHints="importantAmI">
+    </View>
+
 </LinearLayout>
diff --git a/tests/autofillservice/res/layout/grid_activity.xml b/tests/autofillservice/res/layout/grid_activity.xml
index 2ad125c..7888720 100644
--- a/tests/autofillservice/res/layout/grid_activity.xml
+++ b/tests/autofillservice/res/layout/grid_activity.xml
@@ -24,6 +24,7 @@
     android:orientation="vertical" >
 
     <GridLayout
+        android:id="@+id/grid"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index e16d1c4..2a971b1 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -37,6 +37,9 @@
 
         <EditText
             android:id="@+id/username"
+            android:minEms="2"
+            android:maxEms="5"
+            android:maxLength="25"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
     </LinearLayout>
diff --git a/tests/autofillservice/res/layout/login_with_strings_activity.xml b/tests/autofillservice/res/layout/login_with_strings_activity.xml
new file mode 100644
index 0000000..2f90761
--- /dev/null
+++ b/tests/autofillservice/res/layout/login_with_strings_activity.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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="@+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/third_line_only.xml b/tests/autofillservice/res/layout/third_line_only.xml
new file mode 100644
index 0000000..0267c94
--- /dev/null
+++ b/tests/autofillservice/res/layout/third_line_only.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/third"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
diff --git a/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml b/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml
new file mode 100644
index 0000000..3a96389
--- /dev/null
+++ b/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  * Copyright (C) 2017 The Android Open Source Project
+  *
+  * Licensed under the Apache License, Version 2.0 (the "License");
+  * you may not use this file except in compliance with the License.
+  * You may obtain a copy of the License at
+  *
+  *      http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  -->
+
+<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/static_text"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="YO:"/>
+
+    <TextView android:id="@+id/first"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
+
+    <TextView android:id="@+id/second"
+        android:text="2ND, Y U NO HIDDEN?"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
+
+    <TextView android:id="@+id/third"
+        android:text="3RD, Y U NO HIDDEN?"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
+
+    <ImageView android:id="@+id/img"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/two_horizontal_text_fields.xml b/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
index 944f926..773afae 100644
--- a/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
+++ b/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
@@ -16,6 +16,7 @@
   -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parent"
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
diff --git a/tests/autofillservice/res/layout/virtual_container_activity.xml b/tests/autofillservice/res/layout/virtual_container_activity.xml
index 2596fbb..bc47819 100644
--- a/tests/autofillservice/res/layout/virtual_container_activity.xml
+++ b/tests/autofillservice/res/layout/virtual_container_activity.xml
@@ -15,8 +15,34 @@
  * limitations under the License.
 -->
 
-<android.autofillservice.cts.VirtualContainerView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/virtual_container_view"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-/>
+<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:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="URL" />
+
+        <EditText
+            android:id="@+id/my_url_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <android.autofillservice.cts.VirtualContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/virtual_container_view"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+    />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/webview_activity.xml b/tests/autofillservice/res/layout/webview_activity.xml
index a975186..8740273 100644
--- a/tests/autofillservice/res/layout/webview_activity.xml
+++ b/tests/autofillservice/res/layout/webview_activity.xml
@@ -14,8 +14,55 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
-<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/webview"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-/>
+
+<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/outsideContainer1"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Outside 1" />
+
+        <EditText
+            android:id="@+id/outside1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/outsideContainer2"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Outside 2" />
+
+        <EditText
+            android:id="@+id/outside2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <android.autofillservice.cts.MyWebView
+        android:id="@+id/webview"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+    />
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/welcome_activity.xml b/tests/autofillservice/res/layout/welcome_activity.xml
index 292aacd..a79752f 100644
--- a/tests/autofillservice/res/layout/welcome_activity.xml
+++ b/tests/autofillservice/res/layout/welcome_activity.xml
@@ -22,7 +22,7 @@
     android:orientation="vertical" >
 
     <TextView
-        android:id="@+id/output"
+        android:id="@+id/welcome"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="Welcome to the jungle!" />
diff --git a/tests/autofillservice/res/values/strings.xml b/tests/autofillservice/res/values/strings.xml
index 8720a71..785c7a5 100644
--- a/tests/autofillservice/res/values/strings.xml
+++ b/tests/autofillservice/res/values/strings.xml
@@ -26,4 +26,7 @@
         <item>never</item>
     </string-array>
 
+    <string name="username_string">Username</string>
+    <string name="password_string">Password</string>
+
 </resources>
\ No newline at end of file
diff --git a/tests/autofillservice/res/xml/autofill_service_compat_mode_config.xml b/tests/autofillservice/res/xml/autofill_service_compat_mode_config.xml
new file mode 100644
index 0000000..a340fc2
--- /dev/null
+++ b/tests/autofillservice/res/xml/autofill_service_compat_mode_config.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.
+-->
+<autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
+  <compatibility-package
+    android:name="android.autofillservice.cts"
+    android:urlBarResourceId="my_url_bar"
+    android:maxLongVersionCode="1000000000" />
+</autofill-service>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
index efc0b2c..1de07cc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
@@ -35,14 +35,14 @@
      * Run an action in the UI thread, and blocks caller until the action is finished.
      */
     public final void syncRunOnUiThread(Runnable action) {
-        syncRunOnUiThread(action, Helper.UI_TIMEOUT_MS);
+        syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
     }
 
     /**
      * Run an action in the UI thread, and blocks caller until the action is finished or it times
      * out.
      */
-    public final void syncRunOnUiThread(Runnable action, int timeoutMs) {
+    public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
         final CountDownLatch latch = new CountDownLatch(1);
         runOnUiThread(() -> {
             action.run();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
index ad7ffe9..154db78 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
@@ -62,15 +62,15 @@
 
         mDatePicker = (DatePicker) findViewById(R.id.date_picker);
 
-        mDatePicker.setOnDateChangedListener((v, y, m, d) -> {
-            updateOutputWithDate(y, m, d);
-        });
+        mDatePicker.setOnDateChangedListener((v, y, m, d) -> updateOutputWithDate(y, m, d));
 
         mOutput = (EditText) findViewById(R.id.output);
         mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> {
-            ok();
-        });
+        mOk.setOnClickListener((v) -> ok());
+    }
+
+    public DatePicker getDatePicker() {
+        return mDatePicker;
     }
 
     private void updateOutputWithDate(int year, int month, int day) {
@@ -116,18 +116,14 @@
      * Visits the {@code output} in the UiThread.
      */
     void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> {
-            v.visit(mOutput);
-        });
+        syncRunOnUiThread(() -> v.visit(mOutput));
     }
 
     /**
      * Sets the date in the {@link DatePicker}.
      */
     void setDate(int year, int month, int day) {
-        syncRunOnUiThread(() -> {
-            mDatePicker.updateDate(year, month, day);
-        });
+        syncRunOnUiThread(() -> mDatePicker.updateDate(year, month, day));
     }
 
     /**
@@ -135,9 +131,7 @@
      */
     void tapOk() throws Exception {
         mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> {
-            mOk.performClick();
-        });
+        syncRunOnUiThread(() -> mOk.performClick());
         boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
                 .that(called).isTrue();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
new file mode 100644
index 0000000..da684d6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.getContext;
+
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+
+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);
+
+    protected LoginActivity mActivity;
+    protected boolean mCanPassKeys;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+        mCanPassKeys = !Helper.isAutofillWindowFullScreen(getContext());
+    }
+
+    /**
+     * Requests focus on username and expect Window event happens.
+     */
+    protected void requestFocusOnUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+    /**
+     * Requests focus on username and expect no Window event happens.
+     */
+    protected void requestFocusOnUsernameNoWindowChange() {
+        try {
+            // TODO: define a small value in Timeout
+            mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                    Timeouts.UI_TIMEOUT.ms());
+        } catch (TimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException("Expect no window event when focusing to"
+                + " username, but event happened");
+    }
+
+    /**
+     * Requests focus on password and expect Window event happens.
+     */
+    protected void requestFocusOnPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus),
+                Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
index 848b3cc..a997590 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
@@ -62,15 +62,11 @@
 
         mTimePicker = (TimePicker) findViewById(R.id.time_picker);
 
-        mTimePicker.setOnTimeChangedListener((v, m, h) -> {
-            updateOutputWithTime(m, h);
-        });
+        mTimePicker.setOnTimeChangedListener((v, m, h) -> updateOutputWithTime(m, h));
 
         mOutput = (EditText) findViewById(R.id.output);
         mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> {
-            ok();
-        });
+        mOk.setOnClickListener((v) -> ok());
     }
 
     private void updateOutputWithTime(int hour, int minute) {
@@ -116,9 +112,7 @@
      * Visits the {@code output} in the UiThread.
      */
     void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> {
-            v.visit(mOutput);
-        });
+        syncRunOnUiThread(() -> v.visit(mOutput));
     }
 
     /**
@@ -136,9 +130,7 @@
      */
     void tapOk() throws Exception {
         mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> {
-            mOk.performClick();
-        });
+        syncRunOnUiThread(() -> mOk.performClick());
         boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
                 .that(called).isTrue();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
new file mode 100644
index 0000000..af713d3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
@@ -0,0 +1,54 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import java.util.regex.Pattern;
+
+/**
+ * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
+ */
+public final class AntiTrimmerTextWatcher implements TextWatcher {
+
+    /**
+     * Regex used to revert a String that was "anti-trimmed".
+     */
+    public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
+
+    private final EditText mView;
+
+    public AntiTrimmerTextWatcher(EditText view) {
+        mView = view;
+        mView.addTextChangedListener(this);
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        mView.removeTextChangedListener(this);
+        mView.setText("#" + s + "#");
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
index 45f7264..50526dd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
@@ -56,7 +56,7 @@
         sReplier.getNextFillRequest();
 
         // Select dataset
-        sUiBot.selectDataset("fill me");
+        mUiBot.selectDataset("fill me");
 
         // Assert results
         fillExpectation.assertAutoFilled();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
index 570de4e..62072c3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
@@ -50,6 +50,13 @@
     private static final String EXTRA_DATASET_ID = "dataset_id";
     private static final String EXTRA_RESPONSE_ID = "response_id";
 
+    /**
+     * When launched with this intent, it will pass it back to the
+     * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
+     */
+    private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
+
+
     private static final int MSG_WAIT_FOR_LATCH = 1;
 
     private static Bundle sData;
@@ -84,10 +91,15 @@
      */
     public static IntentSender createSender(Context context, int id,
             CannedDataset dataset) {
+        return createSender(context, id, dataset, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedDataset dataset, Bundle outClientState) {
         Preconditions.checkArgument(id > 0, "id must be positive");
         Preconditions.checkState(sDatasets.get(id) == null, "already have id");
         sDatasets.put(id, dataset);
-        return createSender(context, EXTRA_DATASET_ID, id);
+        return createSender(context, EXTRA_DATASET_ID, id, outClientState);
     }
 
     /**
@@ -95,15 +107,25 @@
      */
     public static IntentSender createSender(Context context, int id,
             CannedFillResponse response) {
+        return createSender(context, id, response, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedFillResponse response, Bundle outData) {
         Preconditions.checkArgument(id > 0, "id must be positive");
         Preconditions.checkState(sResponses.get(id) == null, "already have id");
         sResponses.put(id, response);
-        return createSender(context, EXTRA_RESPONSE_ID, id);
+        return createSender(context, EXTRA_RESPONSE_ID, id, outData);
     }
 
-    private static IntentSender createSender(Context context, String extraName, int id) {
+    private static IntentSender createSender(Context context, String extraName, int id,
+            Bundle outClientState) {
         final Intent intent = new Intent(context, AuthenticationActivity.class);
         intent.putExtra(extraName, id);
+        if (outClientState != null) {
+            Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
+            intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
+        }
         final PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, 0);
         sPendingIntents.add(pendingIntent);
         return pendingIntent.getIntentSender();
@@ -213,6 +235,13 @@
         // Pass on the auth result
         final Intent intent = new Intent();
         intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+
+        final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
+        if (outClientState != null) {
+            Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
+            intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
+        }
+
         final int resultCode;
         synchronized (sLock) {
             resultCode = sResultCode;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
new file mode 100644
index 0000000..f47bac5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
@@ -0,0 +1,1154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.regex.Pattern;
+
+public class AuthenticationTest extends AbstractLoginActivityTestCase {
+
+    @Test
+    public void testDatasetAuthTwoFields() throws Exception {
+        datasetAuthTwoFields(false);
+    }
+
+    @Test
+    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
+        datasetAuthTwoFields(true);
+    }
+
+    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth dataset");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Set up the authentication response client state
+        final Bundle authentionClientState = new Bundle();
+        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (AutofillValue) null)
+                        .setField(ID_PASSWORD, (AutofillValue) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(authentionClientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+
+        // Select a dataset from the new response
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("clientStateKey1");
+        assertThat(extraValue).isEqualTo("clientStateValue1");
+    }
+
+    @Test
+    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intent
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null)
+                        .setField(ID_PASSWORD, (String) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthTwoDatasets() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 1"))
+                        .setAuthentication(authentication1)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 2"))
+                        .setAuthentication(authentication2)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
+
+        mUiBot.selectDataset("Tap to auth dataset 1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthMixedSelectAuth() throws Exception {
+        datasetAuthMixedTest(true);
+    }
+
+    @Test
+    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
+        datasetAuthMixedTest(false);
+    }
+
+    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthNoFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("DS1"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE,THE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("DS2"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ZzBottom")
+                        .setField(ID_PASSWORD, "top")
+                        .setPresentation(createPresentation("DS3"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then type something to hide them.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert they're shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then filter for 2
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...up to 1
+        mActivity.onUsername((v) -> v.setText("du"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dud"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude,"));
+        mUiBot.assertDatasets("DS2");
+
+        // Now delete the char and assert 2 are shown again...
+        mActivity.onUsername((v) -> v.setText("dude"));
+        final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...and select it this time
+        mUiBot.selectDataset(picker, "DS1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthFilteringUsingRegex() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+
+        final Pattern min2Chars = Pattern.compile(".{2,}");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...now type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Delete the char and assert it's not shown again...
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...then type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
+        datasetAuthMixedFilteringTest(true);
+    }
+
+    @Test
+    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
+        datasetAuthMixedFilteringTest(false);
+    }
+
+    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        } else {
+            mActivity.expectAutoFill("dude", "sweet");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // Filter the auth dataset.
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Filter all.
+        mActivity.onUsername((v) -> v.setText("dw"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert the non-auth is shown again.
+        mActivity.onUsername((v) -> v.setText("d"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Delete again and assert all dataset are shown.
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // ...and select it this time
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedDataset dataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                        dataset)
+                : AuthenticationActivity.createSender(mContext, 1,
+                        dataset, newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(newClientState("CSI", "FromResponse"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        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);
+
+        // Assert client state on authentication activity.
+        assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromResponse" : "FromIntent";
+        assertClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    @Test
+    public void testFillResponseAuthBothFields() throws Exception {
+        fillResponseAuthBothFields(false);
+    }
+
+    @Test
+    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
+        fillResponseAuthBothFields(true);
+    }
+
+    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth response");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    public void testFillResponseAuthJustOneField() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is not show on 2nd field
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Disables autofill so it's not triggered again after the auth activity is finished
+        // (and current session is canceled) and the login activity is resumed.
+        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
+
+        // Autofill it.
+        final CountDownLatch latch = new CountDownLatch(1);
+        AuthenticationActivity.setResultCode(latch, RESULT_OK);
+
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+
+        // Cancel session...
+        mActivity.getAutofillManager().cancel();
+
+        // ...before finishing the Auth UI.
+        latch.countDown();
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(true);
+    }
+
+    @Test
+    public void testFillResponseAuthServiceHasNoData() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(false);
+    }
+
+    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final CannedFillResponse response = canSave
+                ? new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                        .build()
+                : CannedFillResponse.NO_RESPONSE;
+
+        final IntentSender authentication =
+                AuthenticationActivity.createSender(mContext, 1, response);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+
+        // Select the authentication dialog.
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    enum ClientStateLocation {
+        INTENT_ONLY,
+        FILL_RESPONSE_ONLY,
+        BOTH
+    }
+
+    private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedFillResponse.Builder authenticatedResponseBuilder =
+                new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dataset"))
+                        .build());
+
+        if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
+            authenticatedResponseBuilder.setExtras(newClientState("CSI", "FromAuthResponse"));
+        }
+
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                        authenticatedResponseBuilder.build())
+                : AuthenticationActivity.createSender(mContext, 1,
+                        authenticatedResponseBuilder.build(), newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(newClientState("CSI", "FromResponse"))
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth response");
+
+        // Tap dataset.
+        mUiBot.selectDataset("Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        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);
+
+        // Assert client state on authentication activity.
+        assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromAuthResponse" : "FromIntent";
+        assertClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    // TODO(on master): move to helper / reuse in other places
+    private void assertClientState(String where, Bundle data, String expectedKey,
+            String expectedValue) {
+        assertWithMessage("no client state on %s", where).that(data).isNotNull();
+        final String extraValue = data.getString(expectedKey);
+        assertWithMessage("invalid value for %s on %s", expectedKey, where)
+            .that(extraValue).isEqualTo(expectedValue);
+    }
+
+    // TODO(on master): move to helper / reuse in other places
+    private Bundle newClientState(String key, String value) {
+        final Bundle clientState = new Bundle();
+        clientState.putString(key, value);
+        return clientState;
+    }
+
+    @Test
+    public void testFillResponseFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index e72d85d..de20402 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -19,39 +19,62 @@
 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.runShellCommand;
 import static android.autofillservice.cts.Helper.setLoggingLevel;
 import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
-import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 
 import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
+import android.autofillservice.cts.common.SettingsStateKeeperRule;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 import android.widget.RemoteViews;
 
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.ClassRule;
 import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 /**
  * Base class for all other tests.
  */
 @RunWith(AndroidJUnit4.class)
-abstract class AutoFillServiceTestCase {
+// NOTE: @ClassRule requires it to be public
+public abstract class AutoFillServiceTestCase {
     private static final String TAG = "AutoFillServiceTestCase";
 
-    protected static UiBot sUiBot;
+    static final UiBot sDefaultUiBot = new UiBot();
 
     protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
 
+    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());
+        }
+
+        @Override
+        protected void finished(Description description) {
+            JUnitHelper.setCurrentTestName(null);
+        }
+    };
+
     @Rule
     public final RetryRule mRetryRule = new RetryRule(2);
 
@@ -62,8 +85,16 @@
     public final RequiredFeatureRule mRequiredFeatureRule =
             new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
 
-    protected final Context mContext;
+    @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.
@@ -71,8 +102,13 @@
     private String mLoggingLevel;
 
     protected AutoFillServiceTestCase() {
-        mContext = InstrumentationRegistry.getTargetContext();
+        this(sDefaultUiBot);
+    }
+
+    protected AutoFillServiceTestCase(UiBot uiBot) {
         mPackageName = mContext.getPackageName();
+        mUiBot = uiBot;
+        mUiBot.reset();
     }
 
     @BeforeClass
@@ -86,24 +122,9 @@
         runShellCommand("cmd statusbar collapse");
     }
 
-    @BeforeClass
-    public static void setUiBot() throws Exception {
-        if (!hasAutofillFeature()) return;
-
-        sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation());
-    }
-
-    @AfterClass
-    public static void resetSettings() {
-        if (!hasAutofillFeature()) return;
-
-        // Clean up only - no need to call disableService() because it doesn't need to fail if
-        // it's not reset.
-        runShellCommand("settings delete secure %s", AUTOFILL_SERVICE);
-    }
-
     @Before
-    public void reset() {
+    public void cleanupStaticState() {
+        Helper.preTestCleanup();
         sReplier.reset();
     }
 
@@ -122,6 +143,15 @@
         }
     }
 
+    /**
+     * Cleans up activities that might have been left over.
+     */
+    @Before
+    @After
+    public void finishActivities() {
+        WelcomeActivity.finishIt(mUiBot);
+    }
+
     @After
     public void resetVerboseLogging() {
         try {
@@ -131,24 +161,6 @@
         }
     }
 
-    // TODO: we shouldn't throw exceptions on @After / @AfterClass because if the test failed, these
-    // exceptions would mask the real cause. A better approach might be using a @Rule or some other
-    // visitor pattern.
-    @After
-    public void assertNothingIsPending() throws Throwable {
-        final MultipleExceptionsCatcher catcher = new MultipleExceptionsCatcher()
-            .run(() -> sReplier.assertNumberUnhandledFillRequests(0))
-            .run(() -> sReplier.assertNumberUnhandledSaveRequests(0));
-
-        final List<Exception> replierExceptions = sReplier.getExceptions();
-        if (replierExceptions != null) {
-            for (Exception e : replierExceptions) {
-                catcher.add(e);
-            }
-        }
-        catcher.throwIfAny();
-    }
-
     @After
     public void ignoreFurtherRequests() {
         InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index b966078..b70d932 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -33,13 +33,19 @@
 import android.widget.EditText;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 /**
  * Tests that the session finishes when the views and fragments go away
  */
 public class AutoFinishSessionTest extends AutoFillServiceTestCase {
+
+    private static final String ID_BUTTON = "button";
+
     @Rule
     public final AutofillActivityTestRule<FragmentContainerActivity> mActivityRule =
             new AutofillActivityTestRule<>(FragmentContainerActivity.class);
@@ -62,13 +68,12 @@
 
     // firstRemove and secondRemove run in the UI Thread; firstCheck doesn't
     private void removeViewsBaseTest(@NonNull Runnable firstRemove, @Nullable Runnable firstCheck,
-            @Nullable Runnable secondRemove, String... viewsToSave)
-            throws Exception {
+            @Nullable Runnable secondRemove, String... viewsToSave) throws Exception {
         enableService();
 
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, viewsToSave).build());
 
         // Trigger autofill
@@ -79,7 +84,7 @@
 
         sReplier.getNextFillRequest();
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // remove first set of views
         mActivity.syncRunOnUiThread(() -> {
@@ -99,7 +104,7 @@
         }
 
         // Save should be shows after all remove operations were executed
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         SaveRequest saveRequest = sReplier.getNextSaveRequest();
         for (String view : viewsToSave) {
@@ -110,11 +115,24 @@
 
     @Test
     public void removeBothViewsToFinishSession() throws Exception {
+        final AtomicReference<Exception> ref = new AtomicReference<>();
         removeViewsBaseTest(
                 () -> ((ViewGroup) mEditText1.getParent()).removeView(mEditText1),
-                () -> sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC),
+                () -> assertSaveNotShowing(ref),
                 () -> ((ViewGroup) mEditText2.getParent()).removeView(mEditText2),
                 "editText1", "editText2");
+        final Exception e = ref.get();
+        if (e != null) {
+            throw e;
+        }
+    }
+
+    private void assertSaveNotShowing(AtomicReference<Exception> ref) {
+        try {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        } catch (Exception e) {
+            ref.set(e);
+        }
     }
 
     @Test
@@ -185,7 +203,7 @@
 
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText1").build());
 
         // Trigger autofill
@@ -196,7 +214,7 @@
 
         sReplier.getNextFillRequest();
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         mActivity.syncRunOnUiThread(() -> {
             mEditText1.setText("editText1-filled");
@@ -212,26 +230,27 @@
             mActivity.syncRunOnUiThread(removeInBackGround);
         }
 
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Remove previously started activity from top
-        sUiBot.selectById("android.autofillservice.cts:id/button");
+        mUiBot.selectByRelativeId(ID_BUTTON);
         mActivity.waitUntilResumed();
 
         if (removeInForeGroup != null) {
-            sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
             mActivity.syncRunOnUiThread(removeInForeGroup);
         }
 
         // Save should be shows after all remove operations were executed
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertThat(findNodeByResourceId(saveRequest.structure, "editText1")
                 .getAutofillValue().getTextValue().toString()).isEqualTo("editText1-filled");
     }
 
+    @Ignore("temporarily disabled because UI is shown - b/73736562")
     @Test
     public void removeViewInBackground() throws Exception {
         activityToBackgroundShouldNotTriggerSave(
@@ -244,6 +263,7 @@
                 null);
     }
 
+    @Ignore("temporarily disabled because UI is shown - b/73736562")
     @Test
     public void hideViewInBackground() throws Exception {
         activityToBackgroundShouldNotTriggerSave(() -> {
@@ -255,12 +275,14 @@
                 null);
     }
 
+    @Ignore("temporarily disabled because UI is shown - b/73736562")
     @Test
     public void hideParentInBackground() throws Exception {
         activityToBackgroundShouldNotTriggerSave(() -> mParent.setVisibility(ViewGroup.INVISIBLE),
                 null);
     }
 
+    @Ignore("temporarily disabled because UI is shown - b/73736562")
     @Test
     public void removeParentInBackground() throws Exception {
         activityToBackgroundShouldNotTriggerSave(
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
index 88fd1e0..7e36593 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
@@ -19,8 +19,7 @@
 import android.support.test.rule.ActivityTestRule;
 
 /**
- * Custom {@link ActivityTestRule} that cleans up the autofill state before the activity is
- * launched.
+ * Custom {@link ActivityTestRule}.
  */
 public class AutofillActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
 
@@ -31,11 +30,4 @@
     public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
         super(activityClass, false, launchActivity);
     }
-
-    @Override
-    protected void beforeActivityLaunched() {
-        Helper.preTestCleanup();
-
-        super.beforeActivityLaunched();
-    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
index 92a19a3..8a873f3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -16,13 +16,15 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 
+import android.support.annotation.NonNull;
 import android.util.Log;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
+
 /**
  * Custom JUnit4 rule that improves autofill-related logging by:
  *
@@ -31,9 +33,10 @@
  *   <li>Call {@code dumpsys autofill} in case of failure.
  * </ol>
  */
-public class AutofillLoggingTestRule implements TestRule {
+public class AutofillLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
 
     private final String mTag;
+    private boolean mDumped;
 
     public AutofillLoggingTestRule(String tag) {
         mTag = tag;
@@ -52,8 +55,7 @@
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    final String dump = runShellCommand("dumpsys autofill");
-                    Log.e(mTag, "dump for " + description.getDisplayName() + ": \n" + dump, t);
+                    dump(description.getDisplayName(), t);
                     throw t;
                 } finally {
                     if (!levelBefore.equals("verbose")) {
@@ -64,4 +66,17 @@
         };
     }
 
+    @Override
+    public void dump(@NonNull String testName, @NonNull Throwable t) {
+        if (mDumped) {
+            Log.e(mTag, "dump(" + testName + "): already dumped");
+            return;
+        }
+        Log.e(mTag, "Dumping after exception on " + testName, t);
+        final String autofillDump = runShellCommand("dumpsys autofill");
+        Log.e(mTag, "autofill dump: \n" + autofillDump);
+        final String activityDump = runShellCommand("dumpsys activity top");
+        Log.e(mTag, "top activity dump: \n" + activityDump);
+        mDumped = true;
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
index c319730..9fcf334 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
@@ -188,7 +188,7 @@
         startAutoFill(mEditText);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
@@ -241,7 +241,7 @@
         startAutoFill(mCompoundButton);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
@@ -294,7 +294,7 @@
         startAutoFill(mSpinner);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
@@ -361,7 +361,7 @@
         startAutoFill(mEditText);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
@@ -430,7 +430,7 @@
         startAutoFill(mEditText);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
@@ -486,7 +486,7 @@
         startAutoFill(mEditText);
 
         // Autofill it.
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         if (expectAutoFill) {
             // Check the results.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
new file mode 100644
index 0000000..0f862f5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
@@ -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.
+ */
+package android.autofillservice.cts;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * An {@link AutofillService} implementation that does fails if called upon.
+ */
+public class BadAutofillService extends AutofillService {
+
+    private static final String TAG = "BadAutofillService";
+
+    static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
+            + "/." + BadAutofillService.class.getSimpleName();
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.e(TAG, "onFillRequest() should never be called");
+        throw new UnsupportedOperationException("onFillRequest() should never be called");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.e(TAG, "onSaveRequest() should never be called");
+        throw new UnsupportedOperationException("onSaveRequest() should never be called");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
new file mode 100644
index 0000000..f17f7dc
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.Transformation;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BatchUpdatesTest {
+
+    private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
+
+    @Test
+    public void testAddTransformation_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.transformChild(42, null));
+    }
+
+    @Test
+    public void testAddTransformation_invalidClass() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.transformChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testSetUpdateTemplate_null() {
+        assertThrows(NullPointerException.class, () ->  mBuilder.updateTemplate(null));
+    }
+
+    @Test
+    public void testEmptyObject() {
+        assertThrows(IllegalStateException.class, () ->  mBuilder.build());
+    }
+
+    @Test
+    public void testNoMoreChangesAfterBuild() {
+        assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.updateTemplate(mock(RemoteViews.class)));
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.transformChild(42, mock(InternalTransformation.class)));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index cf2cc6d..33aa025 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -27,8 +27,11 @@
 import android.service.autofill.Dataset;
 import android.service.autofill.FillCallback;
 import android.service.autofill.FillResponse;
+import android.service.autofill.Sanitizer;
 import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
 import android.service.autofill.Validator;
+import android.util.Pair;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.widget.RemoteViews;
@@ -39,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
+import java.util.regex.Pattern;
 
 /**
  * Helper class used to produce a {@link FillResponse} based on expected fields that should be
@@ -59,21 +63,30 @@
 
     private final ResponseType mResponseType;
     private final List<CannedDataset> mDatasets;
+    private final ArrayList<Pair<Sanitizer, AutofillId[]>> mSanitizers;
     private final String mFailureMessage;
     private final int mSaveType;
     private final Validator mValidator;
     private final String[] mRequiredSavableIds;
     private final String[] mOptionalSavableIds;
+    private final AutofillId[] mRequiredSavableAutofillIds;
     private final String mSaveDescription;
     private final CustomDescription mCustomDescription;
     private final Bundle mExtras;
     private final RemoteViews mPresentation;
+    private final RemoteViews mHeader;
+    private final RemoteViews mFooter;
     private final IntentSender mAuthentication;
     private final String[] mAuthenticationIds;
     private final String[] mIgnoredIds;
     private final int mNegativeActionStyle;
     private final IntentSender mNegativeActionListener;
-    private final int mFlags;
+    private final int mSaveInfoFlags;
+    private final int mFillResponseFlags;
+    private final AutofillId mSaveTriggerId;
+    private final long mDisableDuration;
+    private final AutofillId[] mFieldClassificationIds;
+    private final boolean mFieldClassificationIdsOverflow;
 
     private CannedFillResponse(Builder builder) {
         mResponseType = builder.mResponseType;
@@ -81,18 +94,27 @@
         mFailureMessage = builder.mFailureMessage;
         mValidator = builder.mValidator;
         mRequiredSavableIds = builder.mRequiredSavableIds;
+        mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
         mOptionalSavableIds = builder.mOptionalSavableIds;
         mSaveDescription = builder.mSaveDescription;
         mCustomDescription = builder.mCustomDescription;
         mSaveType = builder.mSaveType;
         mExtras = builder.mExtras;
         mPresentation = builder.mPresentation;
+        mHeader = builder.mHeader;
+        mFooter = builder.mFooter;
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
         mNegativeActionStyle = builder.mNegativeActionStyle;
         mNegativeActionListener = builder.mNegativeActionListener;
-        mFlags = builder.mFlags;
+        mSanitizers = builder.mSanitizers;
+        mSaveInfoFlags = builder.mSaveInfoFlags;
+        mFillResponseFlags = builder.mFillResponseFlags;
+        mSaveTriggerId = builder.mSaveTriggerId;
+        mDisableDuration = builder.mDisableDuration;
+        mFieldClassificationIds = builder.mFieldClassificationIds;
+        mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
     }
 
     /**
@@ -122,7 +144,8 @@
      * structure.
      */
     FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) {
-        final FillResponse.Builder builder = new FillResponse.Builder();
+        final FillResponse.Builder builder = new FillResponse.Builder()
+                .setFlags(mFillResponseFlags);
         if (mDatasets != null) {
             for (CannedDataset cannedDataset : mDatasets) {
                 final Dataset dataset = cannedDataset.asDataset(nodeResolver);
@@ -130,14 +153,18 @@
                 builder.addDataset(dataset);
             }
         }
-        if (mRequiredSavableIds != null) {
-            final SaveInfo.Builder saveInfo =
-                    mRequiredSavableIds == null || mRequiredSavableIds.length == 0
+        if (mRequiredSavableIds != null || mRequiredSavableAutofillIds != null) {
+            final SaveInfo.Builder saveInfo;
+            if (mRequiredSavableAutofillIds != null) {
+                saveInfo = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
+            } else {
+                saveInfo = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
                         ? new SaveInfo.Builder(mSaveType)
                             : new SaveInfo.Builder(mSaveType,
                                     getAutofillIds(nodeResolver, mRequiredSavableIds));
+            }
 
-            saveInfo.setFlags(mFlags);
+            saveInfo.setFlags(mSaveInfoFlags);
 
             if (mValidator != null) {
                 saveInfo.setValidator(mValidator);
@@ -153,6 +180,14 @@
             if (mCustomDescription != null) {
                 saveInfo.setCustomDescription(mCustomDescription);
             }
+
+            for (Pair<Sanitizer, AutofillId[]> sanitizer : mSanitizers) {
+                saveInfo.addSanitizer(sanitizer.first, sanitizer.second);
+            }
+
+            if (mSaveTriggerId != null) {
+                saveInfo.setTriggerId(mSaveTriggerId);
+            }
             builder.setSaveInfo(saveInfo.build());
         }
         if (mIgnoredIds != null) {
@@ -162,9 +197,29 @@
             builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
                     mAuthentication, mPresentation);
         }
-        return builder
-                .setClientState(mExtras)
-                .build();
+        if (mDisableDuration > 0) {
+            builder.disableAutofill(mDisableDuration);
+        }
+        if (mFieldClassificationIdsOverflow) {
+            final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
+            final AutofillId[] fieldIds = new AutofillId[length];
+            for (int i = 0; i < length; i++) {
+                fieldIds[i] = new AutofillId(i);
+            }
+            builder.setFieldClassificationIds(fieldIds);
+        } else if (mFieldClassificationIds != null) {
+            builder.setFieldClassificationIds(mFieldClassificationIds);
+        }
+        if (mExtras != null) {
+            builder.setClientState(mExtras);
+        }
+        if (mHeader != null) {
+            builder.setHeader(mHeader);
+        }
+        if (mFooter != null) {
+            builder.setFooter(mFooter);
+        }
+        return builder.build();
     }
 
     @Override
@@ -173,14 +228,23 @@
                 + ",datasets=" + mDatasets
                 + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
                 + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
-                + ", flags=" + mFlags
+                + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
+                + ", saveInfoFlags=" + mSaveInfoFlags
+                + ", fillResponseFlags=" + mFillResponseFlags
                 + ", failureMessage=" + mFailureMessage
                 + ", saveDescription=" + mSaveDescription
                 + ", mCustomDescription=" + mCustomDescription
                 + ", hasPresentation=" + (mPresentation != null)
+                + ", hasHeader=" + (mHeader != null)
+                + ", hasFooter=" + (mFooter != null)
                 + ", hasAuthentication=" + (mAuthentication != null)
                 + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
                 + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
+                + ", sanitizers =" + mSanitizers
+                + ", saveTriggerId=" + mSaveTriggerId
+                + ", disableDuration=" + mDisableDuration
+                + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
+                + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
                 + "]";
     }
 
@@ -192,22 +256,31 @@
 
     static class Builder {
         private final List<CannedDataset> mDatasets = new ArrayList<>();
+        private final ArrayList<Pair<Sanitizer, AutofillId[]>> mSanitizers = new ArrayList<>();
         private final ResponseType mResponseType;
         private String mFailureMessage;
         private Validator mValidator;
         private String[] mRequiredSavableIds;
         private String[] mOptionalSavableIds;
+        private AutofillId[] mRequiredSavableAutofillIds;
         private String mSaveDescription;
         public CustomDescription mCustomDescription;
         public int mSaveType = -1;
         private Bundle mExtras;
         private RemoteViews mPresentation;
+        private RemoteViews mFooter;
+        private RemoteViews mHeader;
         private IntentSender mAuthentication;
         private String[] mAuthenticationIds;
         private String[] mIgnoredIds;
         private int mNegativeActionStyle;
         private IntentSender mNegativeActionListener;
-        private int mFlags;
+        private int mSaveInfoFlags;
+        private int mFillResponseFlags;
+        private AutofillId mSaveTriggerId;
+        private long mDisableDuration;
+        private AutofillId[] mFieldClassificationIds;
+        private boolean mFieldClassificationIdsOverflow;
 
         public Builder(ResponseType type) {
             mResponseType = type;
@@ -232,16 +305,38 @@
         }
 
         /**
-         * Sets the required savable ids based on they {@code resourceId}.
+         * 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;
         }
 
-        public Builder setFlags(int flags) {
-            mFlags = flags;
+        /**
+         * 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;
+        }
+
+        public Builder setSaveInfoFlags(int flags) {
+            mSaveInfoFlags = flags;
+            return this;
+        }
+
+        public Builder setFillResponseFlags(int flags) {
+            mFillResponseFlags = flags;
             return this;
         }
 
@@ -312,6 +407,14 @@
             return this;
         }
 
+        /**
+         * Adds a save sanitizer.
+         */
+        public Builder addSanitizer(Sanitizer sanitizer, AutofillId... ids) {
+            mSanitizers.add(new Pair<>(sanitizer, ids));
+            return this;
+        }
+
         public CannedFillResponse build() {
             return new CannedFillResponse(this);
         }
@@ -324,6 +427,50 @@
             mFailureMessage = message;
             return this;
         }
+
+        /**
+         * Sets the view that explicitly triggers save.
+         */
+        public Builder setSaveTriggerId(AutofillId id) {
+            assertWithMessage("already set").that(mSaveTriggerId).isNull();
+            mSaveTriggerId = id;
+            return this;
+        }
+
+        public Builder disableAutofill(long duration) {
+            assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
+            mDisableDuration = duration;
+            return this;
+        }
+
+        /**
+         * Sets the ids used for field classification.
+         */
+        public Builder setFieldClassificationIds(AutofillId... ids) {
+            assertWithMessage("already set").that(mFieldClassificationIds).isNull();
+            mFieldClassificationIds = ids;
+            return this;
+        }
+
+        /**
+         * Forces the service to throw an exception when setting the fields classification ids.
+         */
+        public Builder setFieldClassificationIdsOverflow() {
+            mFieldClassificationIdsOverflow = true;
+            return this;
+        }
+
+        public Builder setHeader(RemoteViews header) {
+            assertWithMessage("already set").that(mHeader).isNull();
+            mHeader = header;
+            return this;
+        }
+
+        public Builder setFooter(RemoteViews footer) {
+            assertWithMessage("already set").that(mFooter).isNull();
+            mFooter = footer;
+            return this;
+        }
     }
 
     /**
@@ -343,14 +490,20 @@
      */
     static class CannedDataset {
         private final Map<String, AutofillValue> mFieldValues;
+        private final Map<AutofillId, AutofillValue> mFieldValuesById;
+        private final Map<AutofillId, RemoteViews> mFieldPresentationsById;
         private final Map<String, RemoteViews> mFieldPresentations;
+        private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
         private final RemoteViews mPresentation;
         private final IntentSender mAuthentication;
         private final String mId;
 
         private CannedDataset(Builder builder) {
             mFieldValues = builder.mFieldValues;
+            mFieldValuesById = builder.mFieldValuesById;
+            mFieldPresentationsById = builder.mFieldPresentationsById;
             mFieldPresentations = builder.mFieldPresentations;
+            mFieldFilters = builder.mFieldFilters;
             mPresentation = builder.mPresentation;
             mAuthentication = builder.mAuthentication;
             mId = builder.mId;
@@ -371,13 +524,36 @@
                     if (node == null) {
                         throw new AssertionError("No node with resource id " + id);
                     }
-                    final AutofillId autofillid = node.getAutofillId();
+                    final AutofillId autofillId = node.getAutofillId();
                     final AutofillValue value = entry.getValue();
                     final RemoteViews presentation = mFieldPresentations.get(id);
+                    final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
                     if (presentation != null) {
-                        builder.setValue(autofillid, value, presentation);
+                        if (filter == null) {
+                            builder.setValue(autofillId, value, presentation);
+                        } else {
+                            builder.setValue(autofillId, value, filter.second, presentation);
+                        }
                     } else {
-                        builder.setValue(autofillid, value);
+                        if (filter == null) {
+                            builder.setValue(autofillId, value);
+                        } else {
+                            builder.setValue(autofillId, value, filter.second);
+                        }
+                    }
+                }
+            }
+            if (mFieldValuesById != null) {
+                // NOTE: filter is not yet supported when calling methods that explicitly pass
+                // autofill id
+                for (Map.Entry<AutofillId, AutofillValue> entry : mFieldValuesById.entrySet()) {
+                    final AutofillId autofillId = entry.getKey();
+                    final AutofillValue value = entry.getValue();
+                    final RemoteViews presentation = mFieldPresentationsById.get(autofillId);
+                    if (presentation != null) {
+                        builder.setValue(autofillId, value, presentation);
+                    } else {
+                        builder.setValue(autofillId, value);
                     }
                 }
             }
@@ -389,13 +565,20 @@
         public String toString() {
             return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
                     + ", fieldPresentations=" + (mFieldPresentations)
+                    + ", fieldPresentationsById=" + (mFieldPresentationsById)
                     + ", hasAuthentication=" + (mAuthentication != null)
-                    + ", fieldValues=" + mFieldValues + "]";
+                    + ", fieldValues=" + mFieldValues
+                    + ", fieldValuesById=" + mFieldValuesById
+                    + ", fieldFilters=" + mFieldFilters + "]";
         }
 
         static class Builder {
             private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
+            private final Map<AutofillId, AutofillValue> mFieldValuesById = new HashMap<>();
             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
+            private final Map<AutofillId, RemoteViews> mFieldPresentationsById = new HashMap<>();
+            private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
+
             private RemoteViews mPresentation;
             private IntentSender mAuthentication;
             private String mId;
@@ -420,6 +603,21 @@
             }
 
             /**
+             * Sets the canned value of a text field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, Pattern filter) {
+                return setField(id, AutofillValue.forText(text), true, filter);
+            }
+
+            public Builder setUnfilterableField(String id, String text) {
+                return setField(id, AutofillValue.forText(text), false, null);
+            }
+
+            /**
              * Sets the canned value of a list field based on its its {@code id}.
              *
              * <p>The meaning of the id is defined by the object using the canned dataset.
@@ -465,6 +663,28 @@
             }
 
             /**
+             * Sets the canned value of a date field based on its {@code autofillId}.
+             */
+            public Builder setField(AutofillId autofillId, AutofillValue value) {
+                mFieldValuesById.put(autofillId, value);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a date field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, AutofillValue value, boolean filterable,
+                    Pattern filter) {
+                setField(id, value);
+                mFieldFilters.put(id, new Pair<>(filterable, filter));
+                return this;
+            }
+
+            /**
              * Sets the canned value of a field based on its {@code id}.
              *
              * <p>The meaning of the id is defined by the object using the canned dataset.
@@ -478,6 +698,29 @@
             }
 
             /**
+             * Sets the canned value of a date field based on its {@code autofillId}.
+             */
+            public Builder setField(AutofillId autofillId, String text, RemoteViews presentation) {
+                setField(autofillId, AutofillValue.forText(text));
+                mFieldPresentationsById.put(autofillId, presentation);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    Pattern filter) {
+                setField(id, text, presentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+                return this;
+            }
+
+            /**
              * Sets the view to present the response in the UI.
              */
             public Builder setPresentation(RemoteViews presentation) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java
new file mode 100644
index 0000000..d3a0404
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.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.autofillservice.cts;
+
+import org.mockito.ArgumentMatcher;
+
+final class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
+    private final CharSequence mExpected;
+
+    CharSequenceMatcher(CharSequence expected) {
+        mExpected = expected;
+    }
+
+    @Override
+    public boolean matches(CharSequence actual) {
+        return actual.toString().equals(mExpected.toString());
+    }
+
+    @Override
+    public String toString() {
+        return mExpected.toString();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
index e68483b..065edbc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
@@ -33,7 +33,6 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
 
 import java.util.regex.Pattern;
 
@@ -221,16 +220,26 @@
         verify(template, never()).setCharSequence(eq(0), any(), any());
     }
 
-    static class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
-        private final CharSequence mExpected;
+    @Test
+    public void testFieldsAreAppliedInOrder() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id1, Pattern.compile("a"), "A")
+                .addField(id3, Pattern.compile("c"), "C")
+                .addField(id2, Pattern.compile("b"), "B")
+                .build();
 
-        public CharSequenceMatcher(CharSequence expected) {
-            mExpected = expected;
-        }
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
 
-        @Override
-        public boolean matches(CharSequence actual) {
-            return actual.toString().equals(mExpected.toString());
-        }
+        when(finder.findByAutofillId(id1)).thenReturn("a");
+        when(finder.findByAutofillId(id2)).thenReturn("b");
+        when(finder.findByAutofillId(id3)).thenReturn("c");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("ACB")));
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
index 84be355..141b8d0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
@@ -21,6 +21,7 @@
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.util.Log;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
@@ -45,6 +46,7 @@
  * </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";
@@ -104,9 +106,18 @@
         mClearButton.setOnClickListener((v) -> resetFields());
     }
 
-    static void finishIt() {
+    @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);
         }
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 59b0b7c..a3ce455 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -44,13 +44,13 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.service.autofill.CharSequenceTransformation;
 import android.service.autofill.CustomDescription;
+import android.service.autofill.ImageTransformation;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
 import android.widget.ArrayAdapter;
 import android.widget.RemoteViews;
 import android.widget.Spinner;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -74,11 +74,6 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testAutofill() throws Exception {
         // Set service.
@@ -112,7 +107,7 @@
                 .inOrder();
 
         // Auto-fill it.
-        sUiBot.selectDataset("ACME CC");
+        mUiBot.selectDataset("ACME CC");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -152,7 +147,7 @@
         assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
 
         // Auto-fill it.
-        sUiBot.selectDataset("ACME CC");
+        mUiBot.selectDataset("ACME CC");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -195,7 +190,7 @@
                 .containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
 
         // Auto-fill it.
-        sUiBot.selectDataset("ACME CC");
+        mUiBot.selectDataset("ACME CC");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -235,7 +230,7 @@
         mActivity.onAddress((v) -> v.check(R.id.work_address));
         mActivity.onSaveCc((v) -> v.setChecked(false));
         mActivity.tapBuy();
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
         // Assert sanitization on save: everything should be available!
@@ -249,11 +244,20 @@
         assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
     }
 
+    @Test
+    public void testCustomizedSaveUi() throws Exception {
+        customizedSaveUi(false);
+    }
+
+    @Test
+    public void testCustomizedSaveUiWithContentDescription() throws Exception {
+        customizedSaveUi(true);
+    }
+
     /**
      * Tests that a spinner can be used on custom save descriptions.
      */
-    @Test
-    public void testCustomizedSaveUi() throws Exception {
+    private void customizedSaveUi(boolean withContentDescription) throws Exception {
         // Set service.
         enableService();
 
@@ -268,9 +272,18 @@
         final CharSequenceTransformation trans2 = new CharSequenceTransformation
                 .Builder(mActivity.getCcExpiration().getAutofillId(), Pattern.compile("(.*)"), "$1")
                 .build();
+        final ImageTransformation trans3 = (withContentDescription
+                ? new ImageTransformation.Builder(mActivity.getCcNumber().getAutofillId(),
+                        Pattern.compile("(.*)"), R.drawable.android,
+                        "One image is worth thousand words")
+                : new ImageTransformation.Builder(mActivity.getCcNumber().getAutofillId(),
+                        Pattern.compile("(.*)"), R.drawable.android))
+                .build();
+
         final CustomDescription customDescription = new CustomDescription.Builder(presentation)
                 .addChild(R.id.first, trans1)
                 .addChild(R.id.second, trans2)
+                .addChild(R.id.img, trans3)
                 .build();
 
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -291,10 +304,10 @@
         mActivity.tapBuy();
 
         // First make sure the UI is shown...
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
 
         // Then make sure it does have the custom views on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(packageName, "static_text"));
+        final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
         assertThat(staticText).isNotNull();
         assertThat(staticText.getText()).isEqualTo("YO:");
 
@@ -305,6 +318,15 @@
         final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
         assertThat(expiration).isNotNull();
         assertThat(expiration.getText()).isEqualTo("today");
+
+        final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
+        assertThat(image).isNotNull();
+        final String contentDescription = image.getContentDescription();
+        if (withContentDescription) {
+            assertThat(contentDescription).isEqualTo("One image is worth thousand words");
+        } else {
+            assertThat(contentDescription).isNull();
+        }
     }
 
     /**
@@ -353,9 +375,9 @@
         mActivity.tapBuy();
 
         // First make sure the UI is shown...
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
 
         // Then make sure it does not have the custom views on it...
-        assertThat(saveUi.findObject(By.res(packageName, "static_text"))).isNull();
+        assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
new file mode 100644
index 0000000..7ac759b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.AbstractDatePickerActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.AbstractDatePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.icu.text.SimpleDateFormat;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.DateValueSanitizer;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+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;
+
+public class CustomDescriptionDateTest extends AutoFillServiceTestCase {
+
+    @Rule
+    public final AutofillActivityTestRule<DatePickerSpinnerActivity> mActivityRule = new
+            AutofillActivityTestRule<DatePickerSpinnerActivity>(DatePickerSpinnerActivity.class);
+
+    private DatePickerSpinnerActivity mActivity;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testCustomSave() throws Exception {
+        // Set service.
+        enableService();
+
+        final AutofillId id = mActivity.getDatePicker().getAutofillId();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setCustomDescription(new CustomDescription
+                        .Builder(newTemplate(R.layout.two_horizontal_text_fields))
+                        .addChild(R.id.first,
+                                new DateTransformation(id, new SimpleDateFormat("MM/yyyy")))
+                        .addChild(R.id.second,
+                                new DateTransformation(id, new SimpleDateFormat("MM-yy")))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
+
+        // First, make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Then, make sure it does have the custom view on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        // Finally, assert the custom lines are shown
+        mUiBot.assertChild(saveUi, "first", (o) -> assertThat(o.getText()).isEqualTo("12/2010"));
+        mUiBot.assertChild(saveUi, "second", (o) -> assertThat(o.getText()).isEqualTo("12-10"));
+    }
+
+    @Test
+    public void testSaveSameValue_usingSanitization() throws Exception {
+        sanitizationTest(true);
+    }
+
+    @Test
+    public void testSaveSameValue_withoutSanitization() throws Exception {
+        sanitizationTest(false);
+    }
+
+    private void sanitizationTest(boolean withSanitization) throws Exception {
+        // Set service.
+        enableService();
+
+        final AutofillId id = mActivity.getDatePicker().getAutofillId();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+
+        // Set expectations.
+
+        // NOTE: ID_OUTPUT is used to trigger autofill, but it's value will be automatically
+        // changed, hence we need to set the expected value as the formated one. Ideally
+        // we shouldn't worry about that, but that would require creating a new activitiy with
+        // a custom edit text that uses date autofill values...
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("The end of the world"))
+                        .setField(ID_OUTPUT, "2012/11/25")
+                        .setField(ID_DATE_PICKER, cal.getTimeInMillis())
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER);
+
+        if (withSanitization) {
+            response.addSanitizer(new DateValueSanitizer(new SimpleDateFormat("MM/yyyy")), id);
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger autofill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The end of the world");
+
+        // Manually set same values as dataset.
+        mActivity.onOutput((v) -> v.setText("whatever"));
+        mActivity.setDate(2012, Calendar.DECEMBER, 25);
+        mActivity.tapOk();
+
+        // Verify save behavior.
+        if (withSanitization) {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        } else {
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+    }
+
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
index b7acfc6..c6a9716 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
@@ -18,16 +18,18 @@
 
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
 import static android.autofillservice.cts.Helper.getContext;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.service.autofill.BatchUpdates;
 import android.service.autofill.CharSequenceTransformation;
 import android.service.autofill.CustomDescription;
 import android.service.autofill.ImageTransformation;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.Validator;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.uiautomator.By;
@@ -36,7 +38,6 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -47,7 +48,7 @@
 public class CustomDescriptionTest extends AutoFillServiceTestCase {
     @Rule
     public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-        new AutofillActivityTestRule<>(LoginActivity.class);
+            new AutofillActivityTestRule<>(LoginActivity.class);
 
     private LoginActivity mActivity;
 
@@ -56,11 +57,6 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     /**
      * Base test
      *
@@ -96,22 +92,20 @@
             uiVerifier.run();
         }
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         sReplier.getNextSaveRequest();
-
-        assertNoDanglingSessions();
     }
 
     @Test
     public void validTransformation() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
             CharSequenceTransformation trans1 = new CharSequenceTransformation
                     .Builder(usernameId, Pattern.compile("(.*)"), "$1")
                     .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
                     .build();
+            @SuppressWarnings("deprecation")
             ImageTransformation trans2 = new ImageTransformation
                     .Builder(usernameId, Pattern.compile(".*"),
                     R.drawable.android).build();
@@ -120,31 +114,252 @@
                     .addChild(R.id.first, trans1)
                     .addChild(R.id.img, trans2)
                     .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown("usernm..wd"));
+        }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithOneTemplateUpdate() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"),
+                    R.drawable.android).build();
+            RemoteViews update = newTemplate(0); // layout id not really used
+            update.setViewVisibility(R.id.second, View.GONE);
+            Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(condition,
+                            new BatchUpdates.Builder().updateTemplate(update).build())
+                    .build();
+        }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithMultipleTemplateUpdates() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
+                    Pattern.compile("(.*)"), "$1")
+                            .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                            .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), R.drawable.android)
+                    .build();
+
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+
+            // Line 1 updates
+            RemoteViews update1 = newTemplate(666); // layout id not really used
+            update1.setContentDescription(R.id.first, "First am I"); // valid
+            RemoteViews update2 = newTemplate(0); // layout id not really used
+            update2.setViewVisibility(R.id.first, View.GONE); // invalid
+
+            // Line 2 updates
+            RemoteViews update3 = newTemplate(-666); // layout id not really used
+            update3.setTextViewText(R.id.second, "First of his second name"); // valid
+            RemoteViews update4 = newTemplate(0); // layout id not really used
+            update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update1).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update2).build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update3).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update4).build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong content description for line1")
+                        .that(line1.getContentDescription()).isEqualTo("First am I"),
+                (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
+                        .isEqualTo("First of his second name"),
+                null));
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_noConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
+    }
+
+    private enum BatchUpdatesConditionType {
+        NONE_PASS,
+        SECOND_PASS,
+        THIRD_PASS,
+        ALL_PASS
+    }
+
+    /**
+     * Tests a custom description that has 3 transformations, one applied directly and the other
+     * 2 in batch updates.
+     *
+     * @param conditionsType defines which batch updates conditions will pass.
+     */
+    private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
+            throws Exception {
+
+        final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+        final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+
+        final Visitor<UiObject2> line2Visitor;
+        if (line2Pass) {
+            line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                    .that(line2.getText()).isEqualTo("L2-u");
+        } else {
+            line2Visitor = null;
+        }
+
+        final Visitor<UiObject2> line3Visitor;
+        if (line3Pass) {
+            line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                    .that(line3.getText()).isEqualTo("L3-p");
+        } else {
+            line3Visitor = null;
+        }
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+            final RemoteViews line2Updates = newTemplate(666); // layout id not really used
+            line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(line2Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .updateTemplate(line2Updates)
+                            .build())
+                    .batchUpdate(line3Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.third, line3Transformation)
+                            .updateTemplate(line3Updates)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+    }
+
+    @Test
+    public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+        final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                .that(line2.getText()).isEqualTo("L2-u");
+        final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                .that(line3.getText()).isEqualTo("L3-p");
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.two_horizontal_text_fields);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.addView(R.id.parent, line3Presentation);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .updateTemplate(line3Updates)
+                            .transformChild(R.id.third, line3Transformation)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
     }
 
     @Test
     public void badImageTransformation() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
-            ImageTransformation trans = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"), 1)
-                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), 1).build();
 
             return new CustomDescription.Builder(presentation)
                     .addChild(R.id.img, trans)
                     .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown() );
+        }, () -> assertSaveUiWithCustomDescriptionIsShown());
     }
 
     @Test
     public void unusedImageTransformation() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
+            @SuppressWarnings("deprecation")
             ImageTransformation trans = new ImageTransformation
                     .Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
                     .build();
@@ -158,9 +373,9 @@
     @Test
     public void applyImageTransformationToTextView() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
+            @SuppressWarnings("deprecation")
             ImageTransformation trans = new ImageTransformation
                     .Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
                     .build();
@@ -174,8 +389,7 @@
     @Test
     public void failFirstFailAll() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
             CharSequenceTransformation trans = new CharSequenceTransformation
                     .Builder(usernameId, Pattern.compile("(.*)"), "$42")
@@ -191,8 +405,7 @@
     @Test
     public void failSecondFailAll() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
             CharSequenceTransformation trans = new CharSequenceTransformation
                     .Builder(usernameId, Pattern.compile("(.*)"), "$1")
@@ -208,8 +421,7 @@
     @Test
     public void applyCharSequenceTransformationToImageView() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                    R.layout.two_horizontal_text_fields);
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
 
             CharSequenceTransformation trans = new CharSequenceTransformation
                     .Builder(usernameId, Pattern.compile("(.*)"), "$1")
@@ -232,8 +444,7 @@
         final CharSequenceTransformation secondTrans = new CharSequenceTransformation
                 .Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
                 .build();
-        final RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
-                R.layout.two_horizontal_text_fields);
+        RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
         final CustomDescription customDescription = new CustomDescription.Builder(presentation)
                 .addChild(R.id.first, firstTrans)
                 .addChild(R.id.first, secondTrans)
@@ -256,7 +467,7 @@
         mActivity.tapLogin();
 
         final String expectedText = matchFirst ? "polo" : "POLO";
-        assertSaveUiWithCustomDescriptionIsShown(expectedText);
+        assertSaveUiIsShownWithTwoLines(expectedText);
     }
 
     @Test
@@ -269,18 +480,30 @@
         multipleTransformationsForSameFieldTest(false);
     }
 
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+
+    private UiObject2 assertSaveUiShowing() {
+        try {
+            return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private void assertSaveUiWithoutCustomDescriptionIsShown() {
         // First make sure the UI is shown...
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi = assertSaveUiShowing();
 
         // Then make sure it does not have the custom view on it.
-        assertWithMessage("found static_text on SaveUI (%s)", sUiBot.getChildrenAsText(saveUi))
+        assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
             .that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
     }
 
     private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
         // First make sure the UI is shown...
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi = assertSaveUiShowing();
 
         // Then make sure it does have the custom view on it...
         final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
@@ -290,10 +513,36 @@
         return saveUi;
     }
 
-    private void assertSaveUiWithCustomDescriptionIsShown(String expectedText) {
+    /**
+     * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
+     * invisible), but only {@code first} has text.
+     */
+    private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
+        return assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                (line2) -> assertWithMessage("Wrong text for child with id 'second'")
+                        .that(line2.getText()).isNull(),
+                null);
+    }
+
+    /**
+     * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
+     * invisible).
+     */
+    private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
+        assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                null, null);
+    }
+
+    private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
+            @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
         final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
-        assertWithMessage("didn't find '%s' on SaveUI (%s)", expectedText,
-                sUiBot.getChildrenAsText(saveUi))
-                        .that(saveUi.findObject(By.text(expectedText))).isNotNull();
+        mUiBot.assertChild(saveUi, "first", line1Visitor);
+        mUiBot.assertChild(saveUi, "second", line2Visitor);
+        mUiBot.assertChild(saveUi, "third", line3Visitor);
+        return saveUi;
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
new file mode 100644
index 0000000..2a50b6e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.autofillservice.cts;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Transformation;
+import android.service.autofill.Validator;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CustomDescriptionUnitTest {
+
+    private final CustomDescription.Builder mBuilder =
+            new CustomDescription.Builder(mock(RemoteViews.class));
+    private final BatchUpdates mValidUpdate =
+            new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
+    private final Validator mValidCondition = mock(InternalValidator.class);
+
+    @Test
+    public void testNullConstructor() {
+        assertThrows(NullPointerException.class, () ->  new CustomDescription.Builder(null));
+    }
+
+    @Test
+    public void testAddChild_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addChild(42, null));
+    }
+
+    @Test
+    public void testAddChild_invalidTransformation() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.addChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testBatchUpdate_nullCondition() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(null, mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_invalidCondition() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_nullUpdates() {
+        assertThrows(NullPointerException.class,
+                () ->  mBuilder.batchUpdate(mValidCondition, null));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index e8b183c..7396a44 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -15,6 +15,8 @@
  */
 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,7 +27,6 @@
 import android.support.test.uiautomator.UiObject2;
 import android.widget.RemoteViews;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -62,13 +63,18 @@
      */
     @Test
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
-        sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
+            runShellCommand("wm density 420");
             saveUiRestoredAfterTappingLinkTest(
                     PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
         } finally {
-            sUiBot.setScreenOrientation(UiBot.PORTRAIT);
-            cleanUpAfterScreenOrientationIsBackToPortrait();
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+            try {
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } finally {
+                runShellCommand("wm density reset");
+            }
         }
     }
 
@@ -166,18 +172,6 @@
             PostSaveLinkTappedAction action, boolean manualRequest) throws Exception;
 
     /**
-     * Tests scenarios when user taps a link in the custom description, then double-tap recents
-     * to go back to the original activity:
-     * the Save UI should have been canceled.
-     */
-    @Test
-    @Ignore("Test fail on some devices because Recents UI is not well defined: b/72044685")
-    public final void testTapLink_backToPreviousActivityByTappingRecents()
-            throws Exception {
-        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_RECENTS);
-    }
-
-    /**
      * Tests scenarios when user taps a link in the custom description, then re-launches the
      * original activity:
      * the Save UI should have been canceled.
@@ -198,6 +192,9 @@
         saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
     }
 
+    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
     @Test
     public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
             throws Exception {
@@ -207,10 +204,21 @@
     protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
             throws Exception;
 
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(true);
+    }
+
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(false);
+    }
+
+    protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
+
     enum PostSaveLinkTappedAction {
         TAP_BACK_BUTTON,
         ROTATE_THEN_TAP_BACK_BUTTON,
-        TAP_RECENTS,
         FINISH_ACTIVITY,
         LAUNCH_NEW_ACTIVITY,
         LAUNCH_PREVIOUS_ACTIVITY,
@@ -219,41 +227,59 @@
         TAP_YES_ON_SAVE_UI
     }
 
-    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception;
+    protected final void startActivityOnNewTask(Class<?> clazz) {
+        final Intent intent = new Intent(mContext, clazz);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
 
-    protected final void startActivity(Class<?> clazz) {
-        mContext.startActivity(new Intent(mContext, clazz));
+    protected RemoteViews newTemplate() {
+        final RemoteViews presentation = new RemoteViews(mPackageName,
+                R.layout.custom_description_with_link);
+        return presentation;
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(
+            Class<? extends Activity> activityClass) {
+        final Intent intent = new Intent(mContext, activityClass);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        return newCustomDescriptionBuilder(intent);
     }
 
     protected final CustomDescription newCustomDescription(
             Class<? extends Activity> activityClass) {
-        final Intent intent = new Intent(mContext, activityClass);
-        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-        return newCustomDescription(intent);
+        return newCustomDescriptionBuilder(activityClass).build();
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
+        final RemoteViews presentation = newTemplate();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
+        return new CustomDescription.Builder(presentation);
     }
 
     protected final CustomDescription newCustomDescription(Intent intent) {
-        final RemoteViews presentation = new RemoteViews(mPackageName,
-                R.layout.custom_description_with_link);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
-        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
-        return new CustomDescription.Builder(presentation).build();
+        return newCustomDescriptionBuilder(intent).build();
     }
 
-    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) {
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
+        return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
+    }
+
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
+            throws Exception {
         // First make sure the UI is shown...
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(saveType);
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
         // Then make sure it does have the custom view with link on it...
-        getLink(saveUi);
+        final UiObject2 link = getLink(saveUi);
+        assertThat(link.getText()).isEqualTo(expectedText);
         return saveUi;
     }
 
     protected final UiObject2 getLink(final UiObject2 container) {
-        final UiObject2 button = container.findObject(By.res(mPackageName, ID_LINK));
-        assertThat(button).isNotNull();
-        assertThat(button.getText()).isEqualTo("DON'T TAP ME!");
-        return button;
+        final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
+        assertThat(link).isNotNull();
+        return link;
     }
 
     protected final void tapSaveUiLink(UiObject2 saveUi) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
new file mode 100644
index 0000000..91595a2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+public class DatasetFilteringTest extends AbstractLoginActivityTestCase {
+
+    @Test
+    public void testFilter() 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());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFilter_usingKeyboard() throws Exception {
+        if (!mCanPassKeys) {
+            return;
+        }
+
+        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());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        runShellCommand("input keyevent KEYCODE_A");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        runShellCommand("input keyevent KEYCODE_A");
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        runShellCommand("input keyevent KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        runShellCommand("input keyevent KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        runShellCommand("input keyevent KEYCODE_A");
+        runShellCommand("input keyevent KEYCODE_A");
+        runShellCommand("input keyevent KEYCODE_A");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFilter_nullValuesAlwaysMatched() 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, (String) null)
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // One dataset start with 'aa' and one with null value always shown
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.assertDatasets(aa, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // With no filter text all datasets should be shown
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa' and one with null value always shown
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        mUiBot.assertDatasets(b);
+    }
+
+    @Test
+    public void testFilter_differentPrefixes() throws Exception {
+        final String a = "aaa";
+        final String b = "bra";
+        final String c = "cadabra";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, a)
+                        .setPresentation(createPresentation(a))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, b)
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, c)
+                        .setPresentation(createPresentation(c))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(a, b, c);
+
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(a);
+
+        mActivity.onUsername((v) -> v.setText("b"));
+        mUiBot.assertDatasets(b);
+
+        mActivity.onUsername((v) -> v.setText("c"));
+        mUiBot.assertDatasets(c);
+    }
+
+    @Test
+    public void testFilter_usingRegex() throws Exception {
+        // Dataset presentations.
+        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, "whatever", Pattern.compile("a|aa"))
+                        .setPresentation(createPresentation(aa))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever", createPresentation(ab),
+                                Pattern.compile("a|ab"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testFilter_disabledUsingNullRegex() throws Exception {
+        // Dataset presentations.
+        final String unfilterable = "Unfilterabled";
+        final String aOrW = "A or W";
+        final String w = "Wazzup";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                // This dataset has a value but filter is disabled
+                .addDataset(new CannedDataset.Builder()
+                        .setUnfilterableField(ID_USERNAME, "a am I")
+                        .setPresentation(createPresentation(unfilterable))
+                        .build())
+                // This dataset uses pattern to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever", createPresentation(aOrW),
+                                Pattern.compile("a|aw"))
+                        .build())
+                // This dataset uses value to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "wazzup")
+                        .setPresentation(createPresentation(w))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one dataset start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aOrW);
+
+        // No dataset starts with 'aa'
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.assertNoDatasets();
+
+        // Only one datasets start with 'a'
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(aOrW);
+
+        // With no filter text all datasets should be shown
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one datasets start with 'w'
+        mActivity.onUsername((v) -> v.setText("w"));
+        mUiBot.assertDatasets(w);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
new file mode 100644
index 0000000..90d4193
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.Dataset;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DatasetTest {
+
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
+    private final Pattern mFilter = Pattern.compile("whatever");
+
+    private final RemoteViews mPresentation = mock(RemoteViews.class);
+
+    @Test
+    public void testBuilder_nullPresentation() {
+        assertThrows(NullPointerException.class, () -> new Dataset.Builder(null));
+    }
+
+    @Test
+    public void testBuilder_setValueNullId() {
+        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+        assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
+    }
+
+    @Test
+    public void testBuilder_setValueWithoutPresentation() {
+        // Just assert that it builds without throwing an exception.
+        assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValueWithNullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (RemoteViews) null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithNullFilter() {
+        assertThat(new Dataset.Builder(mPresentation).setValue(mId, mValue, (Pattern) null).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentationNullFilter() {
+        assertThat(new Dataset.Builder().setValue(mId, mValue, null, mPresentation).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentationNullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithoutPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
+    }
+
+    @Test
+    public void testBuild_noValues() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        builder.setValue(mId, mValue, mPresentation);
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter, mPresentation));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
index d977ac6..c9afacc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
@@ -31,7 +31,6 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.icu.util.Calendar;
 
-import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -42,11 +41,6 @@
 
     protected abstract T getDatePickerActivity();
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testAutoFillAndSave() throws Exception {
         final T activity = getDatePickerActivity();
@@ -57,7 +51,7 @@
         // Set expectations.
         final Calendar cal = Calendar.getInstance();
         cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, 11);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
         cal.set(Calendar.DAY_OF_MONTH, 20);
 
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -68,7 +62,7 @@
                     .build())
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
                 .build());
-        activity.expectAutoFill("2012/11/20", 2012, 11, 20);
+        activity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
 
         // Trigger auto-fill.
         activity.onOutput((v) -> v.requestFocus());
@@ -79,16 +73,16 @@
         assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
 
         // Auto-fill it.
-        sUiBot.selectDataset("The end of the world");
+        mUiBot.selectDataset("The end of the world");
 
         // Check the results.
         activity.assertAutoFilled();
 
         // Trigger save.
-        activity.setDate(2010, 11, 12);
+        activity.setDate(2010, Calendar.DECEMBER, 12);
         activity.tapOk();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(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/DateTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
new file mode 100644
index 0000000..52016ed
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DateTransformationTest {
+
+    @Mock private ValueFinder mValueFinder;
+    @Mock private RemoteViews mTemplate;
+
+    private final AutofillId mFieldId = new AutofillId(42);
+
+    @Test
+    public void testConstructor_nullFieldId() {
+        assertThrows(NullPointerException.class,
+                () -> new DateTransformation(null, new SimpleDateFormat()));
+    }
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateTransformation(mFieldId, null));
+    }
+
+    @Test
+    public void testFieldNotFound() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testInvalidAutofillValueType() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forText("D'OH"));
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testValidAutofillValue() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId,
+                new SimpleDateFormat("MM/yyyy"));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+        cal.set(Calendar.DAY_OF_MONTH, 20);
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forDate(cal.getTimeInMillis()));
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate).setCharSequence(eq(0), any(),
+                argThat(new CharSequenceMatcher("12/2012")));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java
new file mode 100644
index 0000000..38386af
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.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 android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.service.autofill.DateValueSanitizer;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+
+@RunWith(AndroidJUnit4.class)
+public class DateValueSanitizerTest {
+
+    private static final String TAG = "DateValueSanitizerTest";
+
+    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM/yyyy");
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateValueSanitizer(null));
+    }
+
+    @Test
+    public void testSanitize_nullValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_invalidValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(AutofillValue.forText("D'OH!"))).isNull();
+    }
+
+    @Test
+    public void testSanitize_ok() throws Exception {
+        final Calendar inputCal = Calendar.getInstance();
+        inputCal.set(Calendar.YEAR, 2012);
+        inputCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        inputCal.set(Calendar.DAY_OF_MONTH, 20);
+        final long inputDate = inputCal.getTimeInMillis();
+        final AutofillValue inputValue = AutofillValue.forDate(inputDate);
+        Log.v(TAG, "Input date: " + inputDate + " >> " + new Date(inputDate));
+
+        final Calendar expectedCal = Calendar.getInstance();
+        expectedCal.clear(); // We just care for year and month...
+        expectedCal.set(Calendar.YEAR, 2012);
+        expectedCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        final long expectedDate = expectedCal.getTimeInMillis();
+        final AutofillValue expectedValue = AutofillValue.forDate(expectedDate);
+        Log.v(TAG, "Exected date: " + expectedDate + " >> " + new Date(expectedDate));
+
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(
+                mDateFormat);
+        final AutofillValue sanitizedValue = sanitizer.sanitize(inputValue);
+        final long sanitizedDate = sanitizedValue.getDateValue();
+        Log.v(TAG, "Sanitized date: " + sanitizedDate + " >> " + new Date(sanitizedDate));
+        assertThat(sanitizedDate).isEqualTo(expectedDate);
+        assertThat(sanitizedValue).isEqualTo(expectedValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
new file mode 100644
index 0000000..24cd5bf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
@@ -0,0 +1,146 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Activity that has buttons to launch dialogs that should then be autofillable.
+ */
+public class DialogLauncherActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "DialogLauncherActivity";
+
+    private FillExpectation mExpectation;
+    private LoginDialog mDialog;
+    Button mLaunchButton;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.dialog_launcher_activity);
+        mLaunchButton = findViewById(R.id.launch_button);
+        mDialog = new LoginDialog(this);
+        mLaunchButton.setOnClickListener((v) -> mDialog.show());
+    }
+
+    void onUsername(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
+    }
+
+    void launchDialog(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> mLaunchButton.performClick());
+        // TODO: should assert by id, but it's not working
+        uiBot.assertShownByText("Username");
+    }
+
+    void maximizeDialog() {
+        final WindowManager wm = getWindowManager();
+        final Display display = wm.getDefaultDisplay();
+        final DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
+        syncRunOnUiThread(
+                () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
+    }
+
+    void expectAutofill(String username, String password) {
+        assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
+                .isNotNull();
+        mExpectation = new FillExpectation(username, password);
+        mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
+        mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
+    }
+
+    void assertAutofilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.mCcUsernameWatcher != null) {
+            mExpectation.mCcUsernameWatcher.assertAutoFilled();
+        }
+        if (mExpectation.mCcPasswordWatcher != null) {
+            mExpectation.mCcPasswordWatcher.assertAutoFilled();
+        }
+    }
+
+    private final class FillExpectation {
+        private final OneTimeTextWatcher mCcUsernameWatcher;
+        private final OneTimeTextWatcher mCcPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            mCcUsernameWatcher = username == null ? null
+                    : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
+            mCcPasswordWatcher = password == null ? null
+                    : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
+        }
+
+        private FillExpectation(String username) {
+            this(username, null);
+        }
+    }
+
+    public final class LoginDialog extends AlertDialog {
+
+        private EditText mUsernameEditText;
+        private EditText mPasswordEditText;
+
+        public LoginDialog(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            setContentView(R.layout.login_activity);
+            mUsernameEditText = findViewById(R.id.username);
+            mPasswordEditText = findViewById(R.id.password);
+        }
+
+        // TODO(b/68816440): temporary hack to make sure tests pass - everything below should be
+        // removed
+        private IBinder mToken;
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            mToken = DialogLauncherActivity.this.getWindow().getAttributes().token;
+            Log.v(TAG, "onAttachedToWindow(): " + mToken);
+        }
+
+
+        @Override
+        public void onWindowAttributesChanged(LayoutParams params) {
+            Log.v(TAG, "onWindowAttributesChanged: p.token=" + params.token + "hack=" + mToken);
+            params.token = mToken;
+
+            super.onWindowAttributesChanged(params);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
new file mode 100644
index 0000000..fa5d8ac
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+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);
+
+    private DialogLauncherActivity mActivity;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testAutofill_noDatasets() throws Exception {
+        autofillNoDatasetsTest(false);
+    }
+
+    @Test
+    public void testAutofill_noDatasets_afterResizing() throws Exception {
+        autofillNoDatasetsTest(true);
+    }
+
+    private void autofillNoDatasetsTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Asserts results.
+        try {
+            mUiBot.assertNoDatasetsEver();
+            // Make sure nodes were properly generated.
+            assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+            assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+        } catch (AssertionError e) {
+            Helper.dumpStructure("D'OH!", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testAutofill_oneDataset() throws Exception {
+        autofillOneDatasetTest(false);
+    }
+
+    @Test
+    public void testAutofill_oneDataset_afterResizing() throws Exception {
+        autofillOneDatasetTest(true);
+    }
+
+    private void autofillOneDatasetTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        mActivity.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Asserts results.
+        mUiBot.selectDataset("The Dude");
+        mActivity.assertAutofilled();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
new file mode 100644
index 0000000..c6fc0d7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.service.autofill.FillResponse;
+import android.util.Log;
+
+import org.junit.Test;
+
+/**
+ * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
+ */
+public class DisableAutofillTest extends AutoFillServiceTestCase {
+
+    private static final String TAG = "DisableAutofillTest";
+
+    private SimpleSaveActivity startSimpleSaveActivity() throws Exception {
+        final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
+        return SimpleSaveActivity.getInstance();
+    }
+
+    private PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
+        final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
+        return PreSimpleSaveActivity.getInstance();
+    }
+
+    /**
+     * Defines what to do after the activity being tested is launched.
+     */
+    enum PostLaunchAction {
+        /**
+         * Used when the service disables autofill in the fill response for this activty. As such:
+         *
+         * <ol>
+         *   <li>There should be a fill request on {@code sReplier}.
+         *   <li>The first UI focus should generate a
+         *   {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
+         *   event.
+         *   <li>Subsequent UI focus should not trigger events.
+         * </ol>
+         */
+        ASSERT_DISABLING,
+
+        /**
+         * Used when the service already disabled autofill prior to launching activty. As such:
+         *
+         * <ol>
+         *   <li>There should be no fill request on {@code sReplier}.
+         *   <li>There should be no callback calls when UI is focused
+         * </ol>
+         */
+        ASSERT_DISABLED,
+
+        /**
+         * Used when autofill is enabled, so it tries to autofill the activity.
+         */
+        ASSERT_ENABLED_AND_AUTOFILL
+    }
+
+    private void launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(SimpleSaveActivity.ID_INPUT, "id")
+                            .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+
+        }
+
+        final SimpleSaveActivity activity = startSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+
+                // Make sure other fields are not triggered.
+                activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                // Make sure forced requests are ignored as well.
+                activity.getAutofillManager().requestAutofill(activity.mInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+                final SimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("id", "pass");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.unregisterCallback();
+            activity.finish();
+        }
+    }
+
+    private void launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+        }
+
+        final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                activity.getAutofillManager().requestAutofill(activity.mPreInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+                final PreSimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("yo");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.unregisterCallback();
+            activity.finish();
+        }
+    }
+
+    @Test
+    public void testDisableApp() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(
+                new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should be disabled too.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+    }
+
+    @Test
+    public void testDisableAppThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        SystemClock.sleep(duration + 1);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Also try it on another activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    public void testDisableAppThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Try again on other activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    public void testDisableActivity() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should work.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    public void testDisableActivityThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(duration)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        SystemClock.sleep(duration + 1);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
+            throws Exception {
+        Timeouts.ACTIVITY_RESURRECTION.run(
+                "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
+                () -> {
+                    return activity.getAutofillManager().isEnabled() == expected
+                            ? Boolean.TRUE : null;
+                });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DismissType.java b/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
index ae9b9cd..b2e936c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
@@ -24,7 +24,6 @@
 enum DismissType {
     BACK_BUTTON,
     HOME_BUTTON,
-    RECENTS_BUTTON,
     TOUCH_OUTSIDE,
     FOCUS_OUTSIDE
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 0e59e2f..56b5cff 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -18,9 +18,7 @@
 
 import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
 import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
-import static android.autofillservice.cts.Helper.runShellCommand;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -46,9 +44,9 @@
     private DuplicateIdActivity mActivity;
 
     @Before
-    public void setup() {
-        Helper.disableAutoRotation(sUiBot);
-        sUiBot.setScreenOrientation(0);
+    public void setup() throws Exception {
+        Helper.disableAutoRotation(mUiBot);
+        mUiBot.setScreenOrientation(0);
 
         mActivity = mActivityRule.getActivity();
     }
@@ -91,7 +89,6 @@
         // Select field to start autofill
         runShellCommand("input keyevent KEYCODE_TAB");
 
-        waitUntilConnected();
         InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
 
         AssistStructure.ViewNode[] views = findViews(request);
@@ -112,13 +109,21 @@
         sReplier.addResponse(NO_RESPONSE);
 
         // Force rotation to force onDestroy->onCreate cycle
-        sUiBot.setScreenOrientation(1);
+        mUiBot.setScreenOrientation(1);
+        // Wait context and Views being recreated in rotation
+        mUiBot.assertShownByRelativeId(DUPLICATE_ID);
+
+        // Because service returned a null response, rotation will trigger another request.
+        sReplier.addResponse(NO_RESPONSE);
 
         // Select other field to trigger new partition
         runShellCommand("input keyevent KEYCODE_TAB");
 
         request = sReplier.getNextFillRequest();
 
+        // Ignore 2nd request.
+        sReplier.getNextFillRequest();
+
         views = findViews(request);
         AutofillId recreatedId1 = views[0].getAutofillId();
         AutofillId recreatedId2 = views[1].getAutofillId();
@@ -139,7 +144,5 @@
 
         // The views still have different autofill ids
         assertThat(recreatedId1).isNotEqualTo(recreatedId2);
-
-        waitUntilDisconnected();
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
index 6f27fbe..5fe1c7a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
@@ -19,15 +19,25 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.view.View;
 
 /**
  * Empty activity
  */
 public class EmptyActivity extends Activity {
+
+    private View mEmptyView;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.empty);
+        mEmptyView = findViewById(R.id.empty);
     }
+
+    public View getEmptyView() {
+        return mEmptyView;
+    }
+
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
index ce97408..b3e064e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts;
 
+import static android.autofillservice.cts.Helper.findViewByAutofillHint;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
@@ -79,6 +80,8 @@
     private View mNotImportantContainerMixedDescendantsChild;
     private View mNotImportantContainerMixedDescendantsGrandChild;
 
+    private View mViewWithAutofillHints;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -111,6 +114,9 @@
         mNotImportantContainerMixedDescendantsGrandChild = findViewById(
                 R.id.not_important_container_mixed_descendants_grand_child);
 
+        mViewWithAutofillHints = findViewByAutofillHint(this, "importantAmI");
+        assertThat(mViewWithAutofillHints).isNotNull();
+
         // Sanity check for importantForAutofill modes
         assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
         assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
@@ -137,6 +143,10 @@
                 .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
         assertThat(mNotImportantContainerMixedDescendantsGrandChild.getImportantForAutofill())
                 .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+
+        assertThat(mViewWithAutofillHints.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(mViewWithAutofillHints.isImportantForAutofill()).isTrue();
     }
 
     /**
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index b2dd1d4..9e458fd4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -33,9 +33,17 @@
 import static android.autofillservice.cts.Helper.assertNumberOfChildren;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.Helper.findNodeByText;
+import static android.autofillservice.cts.Helper.importantForAutofillAsString;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 
@@ -53,6 +61,7 @@
         new AutofillActivityTestRule<FatActivity>(FatActivity.class);
 
     private FatActivity mFatActivity;
+    private AssistStructure mStructure;
 
     @Before
     public void setActivity() {
@@ -70,31 +79,32 @@
         // Trigger auto-fill.
         mFatActivity.onInput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mStructure = fillRequest.structure;
+        mUiBot.assertNoDatasetsEver();
 
-        // TODO: should only have 5 children, but there is an extra
+        // TODO: should only have X children, but there is an extra
         // TextView that's probably coming from the title. For now we're just ignoring it, but
         // ideally we should change the .xml to exclude it.
-        assertNumberOfChildren(fillRequest.structure, 8);
+        assertNumberOfChildren(fillRequest.structure, 9);
 
         // Should not have ImageView...
         assertThat(findNodeByResourceId(fillRequest.structure, ID_IMAGE)).isNull();
 
         // ...unless app developer asked to:
-        assertThat(findNodeByResourceId(fillRequest.structure, ID_IMPORTANT_IMAGE)).isNotNull();
+        assertNodeExists(ID_IMPORTANT_IMAGE, IMPORTANT_FOR_AUTOFILL_YES);
 
         // Should have TextView, even if it does not have id.
-        assertThat(findNodeByText(fillRequest.structure, "Label with no ID")).isNotNull();
+        assertNodeWithTextExists("Label with no ID", IMPORTANT_FOR_AUTOFILL_YES);
 
         // Should not have EditText that was explicitly removed.
         assertThat(findNodeByResourceId(fillRequest.structure, ID_CAPTCHA)).isNull();
 
         // Make sure container with a resource id was included.
-        final ViewNode inputContainer =
-                findNodeByResourceId(fillRequest.structure, ID_INPUT_CONTAINER);
-        assertThat(inputContainer).isNotNull();
+        final ViewNode inputContainer = assertNodeExists(ID_INPUT_CONTAINER,
+                IMPORTANT_FOR_AUTOFILL_AUTO);
         assertThat(inputContainer.getChildCount()).isEqualTo(1);
         final ViewNode input = inputContainer.getChildAt(0);
+        assertNode(input, IMPORTANT_FOR_AUTOFILL_YES);
         assertThat(input.getIdEntry()).isEqualTo(ID_INPUT);
 
         // Make sure a non-important container can exclude descendants
@@ -106,8 +116,8 @@
                 ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD)).isNull();
 
         // Make sure an important container can exclude descendants
-        assertThat(findNodeByResourceId(fillRequest.structure,
-                ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS)).isNotNull();
+        assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS,
+                IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
         assertThat(findNodeByResourceId(fillRequest.structure,
                 ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD)).isNull();
         assertThat(findNodeByResourceId(fillRequest.structure,
@@ -116,9 +126,93 @@
         // Make sure an intermediary descendant can be excluded
         assertThat(findNodeByResourceId(fillRequest.structure,
                 ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS)).isNull();
-        assertThat(findNodeByResourceId(fillRequest.structure,
-                ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD)).isNotNull();
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD,
+                IMPORTANT_FOR_AUTOFILL_YES);
         assertThat(findNodeByResourceId(fillRequest.structure,
                 ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD)).isNull();
     }
+
+    @Test
+    public void testManualRequest() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mFatActivity.onInput((v) -> mFatActivity.getAutofillManager().requestAutofill(v));
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mStructure = fillRequest.structure;
+        mUiBot.assertNoDatasetsEver();
+
+        // TODO: should only have X children, but there is an extra
+        // TextView that's probably coming from the title. For now we're just ignoring it, but
+        // ideally we should change the .xml to exclude it.
+        assertNumberOfChildren(fillRequest.structure, 28);
+
+        // Assert all nodes are present
+        assertNodeExists(ID_IMAGE, IMPORTANT_FOR_AUTOFILL_NO);
+        assertNodeExists(ID_IMPORTANT_IMAGE, IMPORTANT_FOR_AUTOFILL_YES);
+
+        assertNodeWithTextExists("Label with no ID", IMPORTANT_FOR_AUTOFILL_YES);
+
+        assertNodeExists(ID_CAPTCHA, IMPORTANT_FOR_AUTOFILL_NO);
+
+        final ViewNode inputContainer = assertNodeExists(ID_INPUT_CONTAINER,
+                IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(inputContainer.getChildCount()).isEqualTo(1);
+        final ViewNode input = inputContainer.getChildAt(0);
+        assertNode(input, IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(input.getIdEntry()).isEqualTo(ID_INPUT);
+
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS,
+                IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD,
+                IMPORTANT_FOR_AUTOFILL_YES);
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD,
+                IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS,
+                IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
+        assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD,
+                IMPORTANT_FOR_AUTOFILL_YES);
+        assertNodeExists(ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD,
+                IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS,
+                IMPORTANT_FOR_AUTOFILL_NO);
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD,
+                IMPORTANT_FOR_AUTOFILL_YES);
+        assertNodeExists(ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD,
+                IMPORTANT_FOR_AUTOFILL_NO);
+    }
+
+    private ViewNode assertNodeExists(String resourceId, int expectedImportantForAutofill) {
+        final ViewNode node = findNodeByResourceId(mStructure, resourceId);
+        return assertNode(node, resourceId, expectedImportantForAutofill);
+    }
+
+    private ViewNode assertNodeWithTextExists(String text, int expectedImportantForAutofill) {
+        final ViewNode node = findNodeByText(mStructure, text);
+        return assertNode(node, text, expectedImportantForAutofill);
+    }
+
+    private ViewNode assertNode(ViewNode node, int expectedImportantForAutofill) {
+        return assertNode(node, null, expectedImportantForAutofill);
+    }
+
+    private ViewNode assertNode(ViewNode node, String desc, int expectedImportantForAutofill) {
+        assertThat(node).isNotNull();
+        final String actualMode = importantForAutofillAsString(node.getImportantForAutofill());
+        final String expectedMode = importantForAutofillAsString(expectedImportantForAutofill);
+        if (desc != null) {
+            assertWithMessage("Wrong importantForAutofill mode on %s", desc).that(actualMode)
+                    .isEqualTo(expectedMode);
+        } else {
+            assertThat(actualMode).isEqualTo(expectedMode);
+        }
+
+        return node;
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
new file mode 100644
index 0000000..442f5f8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -0,0 +1,454 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
+import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
+import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+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 com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.Helper.FieldClassificationResult;
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.content.Context;
+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;
+
+public class FieldsClassificationTest extends AutoFillServiceTestCase {
+
+    private static final Context sContext = InstrumentationRegistry.getContext();
+
+    @ClassRule
+    public static final SettingsStateChangerRule sFeatureEnabler =
+            new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+    @Rule
+    public final AutofillActivityTestRule<GridActivity> mActivityRule =
+            new AutofillActivityTestRule<GridActivity>(GridActivity.class);
+
+
+    private GridActivity mActivity;
+    private AutofillManager mAfm;
+
+    @Before
+    public void setFixtures() {
+        mActivity = mActivityRule.getActivity();
+        mAfm = mActivity.getAutofillManager();
+    }
+
+    @Test
+    public void testFeatureIsEnabled() throws Exception {
+        enableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
+
+        disableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
+    }
+
+    @Test
+    public void testGetAlgorithm() throws Exception {
+        enableService();
+
+        // Check algorithms
+        final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(names.size()).isAtLeast(1);
+        final String defaultAlgorithm = getDefaultAlgorithm();
+        assertThat(defaultAlgorithm).isNotEmpty();
+        assertThat(names).contains(defaultAlgorithm);
+
+        // Checks invalid service
+        disableService();
+        assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
+    }
+
+    @Test
+    public void testUserData() throws Exception {
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+
+        enableService();
+        mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
+                .build());
+        assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
+        final UserData userData = mAfm.getUserData();
+        assertThat(userData.getId()).isEqualTo("user_data_id");
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+
+        disableService();
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+    }
+
+    @Test
+    public void testHit_oneUserData_oneDetectableField() throws Exception {
+        simpleHitTest(false, null);
+    }
+
+    @Test
+    public void testHit_invalidAlgorithmIsIgnored() throws Exception {
+        // For simplicity's sake, let's assume that name will never be valid..
+        String invalidName = " ALGORITHM, Y NO INVALID? ";
+
+        simpleHitTest(true, invalidName);
+    }
+
+    @Test
+    public void testHit_userDataAlgorithmIsReset() throws Exception {
+        simpleHitTest(true, null);
+    }
+
+    private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
+        if (setAlgorithm) {
+            userData.setFieldClassificationAlgorithm(algorithm, null);
+        }
+        mAfm.setUserData(userData.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), fieldId, "myId", 1);
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
+        manyUserData_oneDetectableField(true);
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
+        manyUserData_oneDetectableField(false);
+    }
+
+    private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
+                .add("Iam2ND", "2ndId").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, firstMatch ? "IAM111" : "IAM222");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
+        if (firstMatch) {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId, new String[] { "1stId", "2ndId" },
+                            new float[] { 0.66F, 0.5F })});
+        } else {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId, new String[] { "2ndId", "1stId" },
+                            new float[] { 0.66F, 0.5F }) });
+        }
+    }
+
+    @Test
+    public void testHit_oneUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AutofillId fieldId1 = field1.getAutofillId();
+        final EditText field2 = mActivity.getCell(1, 2);
+        final AutofillId fieldId2 = field2.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId1, fieldId2)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // 100%
+        mActivity.setText(1, 2, "fooly"); // 60%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1, "myId", 1.0F),
+                        new FieldClassificationResult(fieldId2, "myId", 0.6F),
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .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();
+        final EditText field4 = mActivity.getCell(2, 2);
+        final AutofillId fieldId4 = field4.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, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // 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", "myId" },
+                                new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
+                                new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
+                                new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
+                .add("FULL1", "myId") // match 80%, should not have been reported
+                .add("FULLY", "myId") // match 100%
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .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();
+        final EditText field4 = mActivity.getCell(2, 2);
+        final AutofillId fieldId4 = field4.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, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // 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", "myId" },
+                                new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
+                                new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
+                                new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testMiss() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").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, "xyz");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    @Test
+    public void testNoUserInput() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").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);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    private String getDefaultAlgorithm() {
+        return mAfm.getDefaultFieldClassificationAlgorithm();
+    }
+
+    /*
+     * TODO(b/70407264): other scenarios:
+     *
+     * - Multipartition (for example, one response with FieldsDetection, others with datasets,
+     *   saveinfo, and/or ignoredIds)
+     * - make sure detectable fields don't trigger a new partition
+     * v test partial hit (for example, 'fool' instead of 'full'
+     * v multiple fields
+     * v multiple value
+     * - combinations of above items
+     */
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
new file mode 100644
index 0000000..e559617
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -0,0 +1,1185 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.Helper.assertDeprecatedClientState;
+import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
+ */
+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();
+    }
+
+    @Test
+    public void testDatasetAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with dataset authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dataset"))
+                        .build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setId("name")
+                        .setPresentation(createPresentation("authentication"))
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(clientState).build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Authenticate
+        mUiBot.selectDataset("authentication");
+        sReplier.getNextFillRequest();
+        mActivity.assertAutoFilled();
+
+        // Verify fill selection
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForDatasetAuthenticationSelected(events.get(0), "name",
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with response wide authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "username")
+                                .setId("name")
+                                .setPresentation(createPresentation("dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
+                .setPresentation(createPresentation("authentication"))
+                .setAuthentication(authentication, ID_USERNAME)
+                .build());
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Authenticate
+        mUiBot.selectDataset("authentication");
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset");
+
+        // Verify fill selection
+        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+        assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+        List<Event> events = selection.getEvents();
+        assertFillEventForAuthenticationSelected(events.get(0), NULL_DATASET_ID,
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testDatasetSelected_twoResponses() throws Exception {
+        enableService();
+
+        // Set up first partition with an anonymous dataset
+        Bundle clientState1 = new Bundle();
+        clientState1.putCharSequence("clientStateKey", "Value1");
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setExtras(clientState1)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+        mUiBot.selectDataset("dataset1");
+        sReplier.getNextFillRequest();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value1");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID,
+                    "clientStateKey", "Value1");
+        }
+
+        // Set up second partition with a named dataset
+        Bundle clientState2 = new Bundle();
+        clientState2.putCharSequence("clientStateKey", "Value2");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password2")
+                                .setPresentation(createPresentation("dataset2"))
+                                .setId("name2")
+                                .build())
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password3")
+                                .setPresentation(createPresentation("dataset3"))
+                                .setId("name3")
+                                .build())
+                .setExtras(clientState2)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
+        mActivity.expectPasswordAutoFill("password3");
+
+        // Trigger autofill on password
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset3");
+        sReplier.getNextFillRequest();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), "name3",
+                    "clientStateKey", "Value2");
+        }
+
+        mActivity.onPassword((v) -> v.setText("new password"));
+        mActivity.syncRunOnUiThread(() -> mActivity.finish());
+        waitUntilDisconnected();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), "name3",
+                    "clientStateKey", "Value2");
+            assertFillEventForSaveShown(events.get(1), NULL_DATASET_ID,
+                    "clientStateKey", "Value2");
+        }
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsFailure() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceTimesout() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    private Bundle getBundle(String key, String value) {
+        final Bundle bundle = new Bundle();
+        bundle.putString(key, value);
+        return bundle;
+    }
+
+    /**
+     * Tests the following scenario:
+     *
+     * <ol>
+     *    <li>Activity A is launched.
+     *    <li>Activity A triggers autofill.
+     *    <li>Activity B is launched.
+     *    <li>Activity B triggers autofill.
+     *    <li>User goes back to Activity A.
+     *    <li>User triggers save on Activity A - at this point, service should have stats of
+     *        activity B, and stats for activity A should have beeen discarded.
+     * </ol>
+     */
+    @Test
+    public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
+        enableService();
+
+        // Launch activity A
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "A"))
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on activity A
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity A
+        final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
+        assertDeprecatedClientState(selectionA, "activity", "A");
+
+        // Launch activity B
+        mActivity.startActivity(new Intent(mActivity, CheckoutActivity.class));
+        mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
+
+        // Trigger autofill on activity B
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "B"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_CC_NUMBER, "4815162342")
+                        .setPresentation(createPresentation("datasetB"))
+                        .build())
+                .build());
+        mUiBot.focusByRelativeId(ID_CC_NUMBER);
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity B
+        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(0);
+        assertDeprecatedClientState(selectionB, "activity", "B");
+
+        // Now switch back to A...
+        mUiBot.pressBack(); // dismiss autofill
+        mUiBot.pressBack(); // dismiss keyboard
+        mUiBot.pressBack(); // dismiss task
+        assertThat(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
+
+        // ...and trigger save
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        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);
+        sReplier.getNextSaveRequest();
+
+        // Finally, make sure history is right
+        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(0);
+        assertDeprecatedClientState(finalSelection, "activity", "B");
+    }
+
+    @Test
+    public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
+        enableService();
+
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert no events where generated
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void textContextCommitted_withoutDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        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);
+        sReplier.getNextSaveRequest();
+
+        // Assert it
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
+    }
+
+    @Test
+    public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
+        enableService();
+        // Trigger 1st autofill request
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+        }
+
+        // Trigger 2st autofill request (which will clear the fill event history)
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                // don't set flags
+                .build());
+        mActivity.expectPasswordAutoFill("whatever");
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+
+            assertFillEventForDatasetSelected(events.get(0), "id2");
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username1"));
+
+        final String expectedMessage = getWelcomeMessage("username1");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+
+            FillEventHistory.Event event2 = events.get(1);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).isEmpty();
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username2", "password2");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset2");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username2"));
+
+        final String expectedMessage = getWelcomeMessage("username2");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetSelected(events.get(0), "id2");
+
+            final FillEventHistory.Event event2 = events.get(1);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
+            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, no dataset was selected by the user,
+     * neither the user entered values that were present in these datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        InstrumentedAutoFillService.getFillEventHistory(0);
+
+        // Enter values not present at the datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            final Event event = events.get(0);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            assertThat(event.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, just one dataset was selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+
+            final FillEventHistory.Event event2 = events.get(1);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+            assertThat(changedFields).containsExactlyEntriesIn(
+                    ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "password")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("password");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+
+            final FillEventHistory.Event event3 = events.get(2);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            final Map<AutofillId, String> changedFields = event3.getChangedFields();
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user didn't change the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("whatever");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+
+            final FillEventHistory.Event event3 = events.get(2);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event3.getChangedFields()).isEmpty();
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user selected the dataset, than changed
+     * the autofilled values, but then change the values again so they match what was provided by
+     * the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setField(ID_PASSWORD, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username", "username");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+        }
+
+        // Change the fields to different values from datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Then change back to dataset values
+        mActivity.onUsername((v) -> v.setText("username"));
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetSelected(events.get(0), "id1");
+
+            FillEventHistory.Event event2 = events.get(1);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event2.getChangedFields()).isEmpty();
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        InstrumentedAutoFillService.getFillEventHistory(0);
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            FillEventHistory.Event event = events.get(0);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields).isNotNull();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service on different
+     * datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id3")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset3"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
+
+        // Verify history
+        InstrumentedAutoFillService.getFillEventHistory(0);
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+
+            final FillEventHistory.Event event = events.get(0);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
+            assertThat(event.getChangedFields()).isEmpty();
+            final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+            final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
new file mode 100644
index 0000000..67771f4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
+import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FillResponseTest {
+
+    private final AutofillId mAutofillId = new AutofillId(42);
+    private final FillResponse.Builder mBuilder = new FillResponse.Builder();
+    private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
+    private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
+    private final Bundle mClientState = new Bundle();
+    private final Dataset mDataset = new Dataset.Builder()
+            .setValue(mAutofillId, AutofillValue.forText("forty-two"))
+            .build();
+    private final long mDisableDuration = 666;
+    @Mock private RemoteViews mPresentation;
+    @Mock private RemoteViews mHeader;
+    @Mock private RemoteViews mFooter;
+    @Mock private IntentSender mIntentSender;
+
+    @Test
+    public void testBuilder_setAuthentication_invalid() {
+        // null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
+        // empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
+                        mPresentation));
+        // null intent sender
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, null, mPresentation));
+        // null presentation
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_valid() {
+        new FillResponse.Builder().setAuthentication(mIds, null, null);
+        new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_illegalState() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
+    }
+
+    @Test
+    public void testBuilder_setFlag_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
+    }
+
+    @Test
+    public void testBuilder_setFlag_valid() {
+        mBuilder.setFlags(0);
+        mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_valid() {
+        mBuilder.disableAutofill(mDisableDuration);
+        mBuilder.disableAutofill(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
+        // No method can be called after disableAutofill()
+        mBuilder.disableAutofill(mDisableDuration);
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setClientState(mClientState));
+
+        // And vice-versa...
+        final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
+        assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
+        assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder3 =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder4 =
+                new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
+        assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder5 =
+                new FillResponse.Builder().setClientState(mClientState);
+        assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId) null));
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
+        final AutofillId[] oneTooMany =
+                new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
+        for (int i = 0; i < oneTooMany.length; i++) {
+            oneTooMany[i] = new AutofillId(i);
+        }
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setFieldClassificationIds(oneTooMany));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_valid() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_setsFlag() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_addsFlag() {
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags())
+                .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // authentication only
+        assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
+                .build()).isNotNull();
+        // save info only
+        assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
+        // dataset only
+        assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
+        // disable autofill only
+        assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
+                .isNotNull();
+        // fill detection only
+        assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
+                .isNotNull();
+        // client state only
+        assertThat(new FillResponse.Builder().setClientState(mClientState).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_build_headerOrFooterWithoutDatasets() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).build());
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
+                .isNotNull();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
+        assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
index 7be4496..2e84a32 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
@@ -71,10 +71,10 @@
     }
 
     public boolean waitUntilResumed() throws InterruptedException {
-        return mResumed.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
     }
 
     public boolean waitUntilStopped() throws InterruptedException {
-        return mStopped.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        return mStopped.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 33321c7..65deff5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
@@ -20,6 +20,7 @@
 import android.view.autofill.AutofillManager;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.GridLayout;
 
 import java.util.ArrayList;
 import java.util.concurrent.BlockingQueue;
@@ -45,6 +46,7 @@
     public static final String ID_L4C1 = getResourceId(4, 1);
     public static final String ID_L4C2 = getResourceId(4, 2);
 
+    private GridLayout mGrid;
     private final EditText[][] mCells = new EditText[4][2];
     private Button mSaveButton;
     private Button mClearButton;
@@ -55,16 +57,17 @@
 
         setContentView(R.layout.grid_activity);
 
-        mCells[0][0] = (EditText) findViewById(R.id.l1c1);
-        mCells[0][1] = (EditText) findViewById(R.id.l1c2);
-        mCells[1][0] = (EditText) findViewById(R.id.l2c1);
-        mCells[1][1] = (EditText) findViewById(R.id.l2c2);
-        mCells[2][0] = (EditText) findViewById(R.id.l3c1);
-        mCells[2][1] = (EditText) findViewById(R.id.l3c2);
-        mCells[3][0] = (EditText) findViewById(R.id.l4c1);
-        mCells[3][1] = (EditText) findViewById(R.id.l4c2);
-        mSaveButton = (Button) findViewById(R.id.save);
-        mClearButton = (Button) findViewById(R.id.clear);
+        mGrid = findViewById(R.id.grid);
+        mCells[0][0] = findViewById(R.id.l1c1);
+        mCells[0][1] = findViewById(R.id.l1c2);
+        mCells[1][0] = findViewById(R.id.l2c1);
+        mCells[1][1] = findViewById(R.id.l2c2);
+        mCells[2][0] = findViewById(R.id.l3c1);
+        mCells[2][1] = findViewById(R.id.l3c2);
+        mCells[3][0] = findViewById(R.id.l4c1);
+        mCells[3][1] = findViewById(R.id.l4c2);
+        mSaveButton = findViewById(R.id.save);
+        mClearButton = findViewById(R.id.clear);
 
         mSaveButton.setOnClickListener((v) -> save());
         mClearButton.setOnClickListener((v) -> resetFields());
@@ -83,7 +86,7 @@
         getSystemService(AutofillManager.class).cancel();
     }
 
-    private EditText getCell(int row, int column) {
+    EditText getCell(int row, int column) {
         return mCells[row - 1][column - 1];
     }
 
@@ -112,6 +115,16 @@
         onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
     }
 
+    public void removeCell(int row, int column) {
+        onCell(row, column, (c) -> mGrid.removeView(c));
+    }
+
+    public void addCell(int row, int column, EditText cell) {
+        mCells[row - 1][column - 1] = cell;
+        // TODO: ideally it should be added in the right place...
+        syncRunOnUiThread(() -> mGrid.addView(cell));
+    }
+
     public void triggerAutofill(boolean manually, int row, int column) {
         if (manually) {
             forceAutofill(row, column);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 2051699..2e494b7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -18,19 +18,32 @@
 
 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;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.app.Activity;
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
+import android.autofillservice.cts.common.SettingsHelper;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.icu.util.Calendar;
+import android.os.Bundle;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
@@ -38,15 +51,19 @@
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStructure.HtmlInfo;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager.AutofillCallback;
 import android.view.autofill.AutofillValue;
 import android.webkit.WebView;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.RequiredFeatureRule;
 
 import java.lang.reflect.Field;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.Function;
 
 /**
@@ -54,7 +71,7 @@
  */
 final class Helper {
 
-    private static final String TAG = "AutoFillCtsHelper";
+    static final String TAG = "AutoFillCtsHelper";
 
     static final boolean VERBOSE = false;
 
@@ -64,147 +81,66 @@
     static final String ID_PASSWORD = "password";
     static final String ID_LOGIN = "login";
     static final String ID_OUTPUT = "output";
+    static final String ID_STATIC_TEXT = "static_text";
+
+    public static final String NULL_DATASET_ID = null;
+
+    /**
+     * 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";
 
-    /**
-     * Timeout (in milliseconds) until framework binds / unbinds from service.
-     */
-    static final long CONNECTION_TIMEOUT_MS = 2000;
-
-    /**
-     * Timeout (in milliseconds) until framework unbinds from a service.
-     */
-    static final long IDLE_UNBIND_TIMEOUT_MS = 5000;
-
-    /**
-     * Timeout (in milliseconds) for expected auto-fill requests.
-     */
-    static final long FILL_TIMEOUT_MS = 2000;
-
-    /**
-     * Timeout (in milliseconds) for expected save requests.
-     */
-    static final long SAVE_TIMEOUT_MS = 5000;
-
-    /**
-     * Time to wait if a UI change is not expected
-     */
-    static final long NOT_SHOWING_TIMEOUT_MS = 500;
-
-    /**
-     * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}.
-     */
-    static final int UI_TIMEOUT_MS = 2000;
-
-    /**
-     * Timeout (in milliseconds) for an activity to be brought out to top.
-     */
-    static final int ACTIVITY_RESURRECTION_MS = 5000;
-
-    /**
-     * Timeout (in milliseconds) for changing the screen orientation.
-     */
-    static final int UI_SCREEN_ORIENTATION_TIMEOUT_MS = 5000;
-
-    /**
-     * Timeout (in milliseconds) for using Recents to swtich activities.
-     */
-    static final int UI_RECENTS_SWITCH_TIMEOUT_MS = 200;
-
-    /**
-     * Time to wait in between retries
-     */
-    static final int RETRY_MS = 100;
-
     private final static String ACCELLEROMETER_CHANGE =
             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
                     + "--bind value:i:%d";
 
     /**
-     * Helper interface used to filter Assist nodes.
+     * Helper interface used to filter nodes.
+     *
+     * @param <T> node type
      */
-    interface NodeFilter {
+    interface NodeFilter<T> {
         /**
          * Returns whether the node passes the filter for such given id.
          */
-        boolean matches(ViewNode node, Object id);
+        boolean matches(T node, Object id);
     }
 
-    private static final NodeFilter RESOURCE_ID_FILTER = (node, id) -> {
+    private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
         return id.equals(node.getIdEntry());
     };
 
-    private static final NodeFilter HTML_NAME_FILTER = (node, id) -> {
+    private static final NodeFilter<ViewNode> AUTOFILL_ID_FILTER = (node, id) -> {
+        return id.equals(node.getAutofillId());
+    };
+
+    private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
         return id.equals(getHtmlName(node));
     };
 
-    private static final NodeFilter TEXT_FILTER = (node, id) -> {
+    private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
+    };
+
+    private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
         return id.equals(node.getText());
     };
 
-    private static final NodeFilter WEBVIEW_ROOT_FILTER = (node, id) -> {
-        // TODO(b/66953802): class name should be android.webkit.WebView, and form name should be
-        // inside HtmlInfo, but Chromium 61 does not implement that.
+    private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
         final String className = node.getClassName();
-        final String formName;
-        if (className.equals("android.webkit.WebView")) {
-            final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
-            formName = getAttributeValue(htmlInfo, "name");
-        } else {
-            formName = className;
-        }
+        if (!className.equals("android.webkit.WebView")) return false;
+
+        final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
+        final String formName = getAttributeValue(htmlInfo, "name");
         return id.equals(formName);
     };
 
-    /**
-     * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
-     * {@link #UI_TIMEOUT_MS} is reached.
-     */
-    static void eventually(Runnable r) throws Exception {
-        eventually(r, UI_TIMEOUT_MS);
-    }
-
-    /**
-     * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
-     * {@code timeout} is reached.
-     */
-    static void eventually(Runnable r, int timeout) throws Exception {
-        long startTime = System.currentTimeMillis();
-
-        while (true) {
-            try {
-                r.run();
-                break;
-            } catch (RuntimeException | Error e) {
-                if (System.currentTimeMillis() - startTime < timeout) {
-                    if (VERBOSE) Log.v(TAG, "Ignoring", e);
-                    Thread.sleep(RETRY_MS);
-                } else {
-                    if (e instanceof RetryableException) {
-                        throw e;
-                    } else {
-                        throw new RetryableException(e, "Timedout out after %d ms", timeout);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Runs a Shell command, returning a trimmed response.
-     */
-    static String runShellCommand(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 static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
+        return hasHint(view.getAutofillHints(), id);
+    };
 
     /**
      * Dump the assist structure on logcat.
@@ -241,32 +177,7 @@
      * Sets whether the user completed the initial setup.
      */
     static void setUserComplete(Context context, boolean complete) {
-        if (isUserComplete() == complete) return;
-
-        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
-                USER_SETUP_COMPLETE);
-        final String newValue = complete ? "1" : null;
-        runShellCommand("settings put secure %s %s default", USER_SETUP_COMPLETE, newValue);
-        observer.assertCalled();
-
-        assertIsUserComplete(complete);
-    }
-
-    /**
-     * Gets whether the user completed the initial setup.
-     */
-    static boolean isUserComplete() {
-        final String isIt = runShellCommand("settings get secure %s", USER_SETUP_COMPLETE);
-        return "1".equals(isIt);
-    }
-
-    /**
-     * Assets that user completed (or not) the initial setup.
-     */
-    static void assertIsUserComplete(boolean expected) {
-        final boolean actual = isUserComplete();
-        assertWithMessage("Invalid value for secure setting %s", USER_SETUP_COMPLETE)
-                .that(actual).isEqualTo(expected);
+        SettingsHelper.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
     }
 
     private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) {
@@ -305,7 +216,7 @@
      * Gets a node if it matches the filter criteria for the given id.
      */
     static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
-            @NonNull NodeFilter filter) {
+            @NonNull NodeFilter<ViewNode> filter) {
         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
         final int nodes = structure.getWindowNodeCount();
         for (int i = 0; i < nodes; i++) {
@@ -323,7 +234,7 @@
      * Gets a node if it matches the filter criteria for the given id.
      */
     static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
-            @NonNull NodeFilter filter) {
+            @NonNull NodeFilter<ViewNode> filter) {
         for (FillContext context : contexts) {
             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
             if (node != null) {
@@ -337,7 +248,7 @@
      * Gets a node if it matches the filter criteria for the given id.
      */
     static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
-            @NonNull NodeFilter filter) {
+            @NonNull NodeFilter<ViewNode> filter) {
         if (filter.matches(node, id)) {
             return node;
         }
@@ -396,6 +307,14 @@
     }
 
     /**
+     * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
+     * not found.
+     */
+    static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
+        return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
+    }
+
+    /**
      * Gets the {@code name} attribute of a node representing an HTML input tag.
      */
     @Nullable
@@ -433,7 +352,39 @@
     }
 
     /**
-     * Asserts a text-base node is sanitized.
+     * Gets a view that contains the an autofill hint, or {@code null} if not found.
+     */
+    static View findViewByAutofillHint(Activity activity, String hint) {
+        final View rootView = activity.getWindow().getDecorView().getRootView();
+        return findViewByAutofillHint(rootView, hint);
+    }
+
+    /**
+     * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
+     * not found.
+     */
+    static View findViewByAutofillHint(View view, String hint) {
+        if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
+        if ((view instanceof ViewGroup)) {
+            final ViewGroup group = (ViewGroup) view;
+            for (int i = 0; i < group.getChildCount(); i++) {
+                final View child = findViewByAutofillHint(group.getChildAt(i), hint);
+                if (child != null) return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a view (or a descendant of it) that has the given {@code id}, or {@code null} if
+     * not found.
+     */
+    static ViewNode findNodeByAutofillId(AssistStructure structure, AutofillId id) {
+        return findNodeByFilter(structure, id, AUTOFILL_ID_FILTER);
+    }
+
+    /**
+     * Asserts a text-based node is sanitized.
      */
     static void assertTextIsSanitized(ViewNode node) {
         final CharSequence text = node.getText();
@@ -441,9 +392,15 @@
         if (!TextUtils.isEmpty(text)) {
             throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
         }
+
+        assertNotFromResources(node);
         assertNodeHasNoAutofillValue(node);
     }
 
+    private static void assertNotFromResources(ViewNode node) {
+        assertThat(node.getTextIdEntry()).isNull();
+    }
+
     static void assertNodeHasNoAutofillValue(ViewNode node) {
         final AutofillValue value = node.getAutofillValue();
         if (value != null) {
@@ -454,22 +411,31 @@
 
     /**
      * Asserts the contents of a text-based node that is also auto-fillable.
-     *
      */
     static void assertTextOnly(ViewNode node, String expectedValue) {
         assertText(node, expectedValue, false);
+        assertNotFromResources(node);
     }
 
     /**
      * Asserts the contents of a text-based node that is also auto-fillable.
-     *
      */
-    static void assertTextAndValue(ViewNode node, String expectedValue) {
-        assertText(node, expectedValue, true);
+    static void assertTextOnly(AssistStructure structure, String resourceId, String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, false);
+        assertNotFromResources(node);
     }
 
     /**
-     * Asserts a text-base node exists and verify its values.
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    static void assertTextAndValue(ViewNode node, String expectedValue) {
+        assertText(node, expectedValue, true);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts a text-based node exists and verify its values.
      */
     static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
             String expectedValue) {
@@ -479,7 +445,7 @@
     }
 
     /**
-     * Asserts a text-base node exists and is sanitized.
+     * Asserts a text-based node exists and is sanitized.
      */
     static ViewNode assertValue(AssistStructure structure, String resourceId,
             String expectedValue) {
@@ -488,16 +454,28 @@
         return node;
     }
 
+    /**
+     * Asserts the values of a text-based node whose string come from resoruces.
+     */
+    static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
+            String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, isAutofillable);
+        assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
+        return node;
+    }
+
     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
-        assertWithMessage("wrong text on %s", node).that(node.getText().toString())
+        assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
                 .isEqualTo(expectedValue);
         final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
         if (isAutofillable) {
-            assertWithMessage("null auto-fill value on %s", node).that(value).isNotNull();
-            assertWithMessage("wrong auto-fill value on %s", node)
+            assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
+            assertWithMessage("wrong auto-fill value on %s", id)
                     .that(value.getTextValue().toString()).isEqualTo(expectedValue);
         } else {
-            assertWithMessage("node %s should not have AutofillValue", node).that(value).isNull();
+            assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
         }
     }
 
@@ -506,9 +484,10 @@
      */
     static ViewNode assertTextValue(ViewNode node, String expectedText) {
         final AutofillValue value = node.getAutofillValue();
-        assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", node).that(value.isText()).isTrue();
-        assertWithMessage("wrong autofill value on %s", node).that(value.getTextValue().toString())
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
                 .isEqualTo(expectedText);
         return node;
     }
@@ -518,9 +497,10 @@
      */
     static ViewNode assertListValue(ViewNode node, int expectedIndex) {
         final AutofillValue value = node.getAutofillValue();
-        assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", node).that(value.isList()).isTrue();
-        assertWithMessage("wrong autofill value on %s", node).that(value.getListValue())
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
                 .isEqualTo(expectedIndex);
         return node;
     }
@@ -530,9 +510,10 @@
      */
     static void assertToggleValue(ViewNode node, boolean expectedToggle) {
         final AutofillValue value = node.getAutofillValue();
-        assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", node).that(value.isToggle()).isTrue();
-        assertWithMessage("wrong autofill value on %s", node).that(value.getToggleValue())
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
                 .isEqualTo(expectedToggle);
     }
 
@@ -599,7 +580,7 @@
     }
 
     /**
-     * Asserts a text-base node exists and is sanitized.
+     * Asserts a text-based node exists and is sanitized.
      */
     static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
@@ -706,7 +687,7 @@
     /**
      * Prevents the screen to rotate by itself
      */
-    public static void disableAutoRotation(UiBot uiBot) {
+    public static void disableAutoRotation(UiBot uiBot) throws Exception {
         runShellCommand(ACCELLEROMETER_CHANGE, 0);
         uiBot.setScreenOrientation(PORTRAIT);
     }
@@ -723,28 +704,21 @@
      *
      * @return The pid of the process
      */
-    public static int getOutOfProcessPid(@NonNull String processName) {
-        long startTime = System.currentTimeMillis();
+    public static int getOutOfProcessPid(@NonNull String processName, @NonNull Timeout timeout)
+            throws Exception {
 
-        while (System.currentTimeMillis() - startTime <= UI_TIMEOUT_MS) {
-            String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n");
+        return timeout.run("getOutOfProcessPid(" + processName + ")", () -> {
+            final String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n");
 
             for (String processDesc : allProcessDescs) {
-                String[] pidAndName = processDesc.trim().split(" ");
+                final String[] pidAndName = processDesc.trim().split(" ");
 
                 if (pidAndName[1].equals(processName)) {
                     return Integer.parseInt(pidAndName[0]);
                 }
             }
-
-            try {
-                Thread.sleep(RETRY_MS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-        }
-
-        throw new IllegalStateException("process not found");
+            return null;
+        });
     }
 
     /**
@@ -770,6 +744,13 @@
     }
 
     /**
+     * Check if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi
+     */
+    public static boolean isAutofillWindowFullScreen(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    /**
      * Uses Shell command to get the Autofill logging level.
      */
     public static String getLoggingLevel() {
@@ -787,58 +768,43 @@
      * Uses Settings to enable the given autofill service for the default user, and checks the
      * value was properly check, throwing an exception if it was not.
      */
-    public static void enableAutofillService(Context context, String serviceName) {
+    public static void enableAutofillService(@NonNull Context context,
+            @NonNull String serviceName) {
         if (isAutofillServiceEnabled(serviceName)) return;
 
-        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
-                AUTOFILL_SERVICE);
-        runShellCommand("settings put secure %s %s default", AUTOFILL_SERVICE, serviceName);
-        observer.assertCalled();
-        assertAutofillServiceStatus(serviceName, true);
+        SettingsHelper.syncSet(context, AUTOFILL_SERVICE, serviceName);
     }
 
     /**
      * 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.
      */
-    public static void disableAutofillService(Context context, String serviceName) {
+    public static void disableAutofillService(@NonNull Context context,
+            @NonNull String serviceName) {
         if (!isAutofillServiceEnabled(serviceName)) return;
 
-        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
-                AUTOFILL_SERVICE);
-        runShellCommand("settings delete secure %s", AUTOFILL_SERVICE);
-        observer.assertCalled();
-        assertAutofillServiceStatus(serviceName, false);
+        SettingsHelper.syncDelete(context, AUTOFILL_SERVICE);
     }
 
     /**
      * Checks whether the given service is set as the autofill service for the default user.
      */
-    public static boolean isAutofillServiceEnabled(String serviceName) {
-        final String actualName = runShellCommand("settings get secure %s", AUTOFILL_SERVICE);
+    private static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
+        final String actualName = SettingsHelper.get(AUTOFILL_SERVICE);
         return serviceName.equals(actualName);
     }
 
     /**
      * Asserts whether the given service is enabled as the autofill service for the default user.
      */
-    public static void assertAutofillServiceStatus(String serviceName, boolean enabled) {
-        final String actual = runShellCommand("settings get secure %s", AUTOFILL_SERVICE);
+    public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
+        final String actual = SettingsHelper.get(AUTOFILL_SERVICE);
         final String expected = enabled ? serviceName : "null";
         assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
                 .that(actual).isEqualTo(expected);
     }
 
     /**
-     * Asserts that there is no session left in the service.
-     */
-    public static void assertNoDanglingSessions() {
-        final String result = runShellCommand(CMD_LIST_SESSIONS);
-        assertWithMessage("Dangling sessions ('%s'): %s'", CMD_LIST_SESSIONS, result).that(result)
-                .isEmpty();
-    }
-
-    /**
      * Asserts that there is a pending session for the given package.
      */
     public static void assertHasSessions(String packageName) {
@@ -847,14 +813,6 @@
     }
 
     /**
-     * Destroys all sessions.
-     */
-    public static void destroyAllSessions() {
-        runShellCommand("cmd autofill destroy sessions");
-        assertNoDanglingSessions();
-    }
-
-    /**
      * Gets the instrumentation context.
      */
     public static Context getContext() {
@@ -916,7 +874,6 @@
         disableAutofillService(getContext(), SERVICE_NAME);
         InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
 
-        destroyAllSessions();
         InstrumentedAutoFillService.resetStaticState();
         AuthenticationActivity.resetStaticState();
     }
@@ -957,17 +914,299 @@
     /**
      * Finds a {@link WebView} node given its expected form name.
      */
-    public static ViewNode findWebViewNode(AssistStructure structure, String formName) {
-        return findNodeByFilter(structure, formName, WEBVIEW_ROOT_FILTER);
+    public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
+        return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
+    }
+
+    private static void assertClientState(Object container, Bundle clientState,
+            String key, String value) {
+        assertWithMessage("'%s' should have client state", container)
+            .that(clientState).isNotNull();
+        assertWithMessage("Wrong number of client state extras on '%s'", container)
+            .that(clientState.keySet().size()).isEqualTo(1);
+        assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
+            .that(clientState.getString(key)).isEqualTo(value);
     }
 
     /**
-     * Finds a {@link WebView} node given its expected form name.
+     * Asserts the content of a {@link FillEventHistory#getClientState()}.
+     *
+     * @param history event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
      */
-    public static ViewNode findWebViewNode(ViewNode node, String formName) {
-        return findNodeByFilter(node, formName, WEBVIEW_ROOT_FILTER);
+    @SuppressWarnings("javadoc")
+    public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
+            @NonNull String key, @NonNull String value) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertClientState(history, clientState, key, value);
+    }
+
+    /**
+     * Asserts the {@link FillEventHistory#getClientState()} is not set.
+     *
+     * @param history event to be asserted
+     */
+    @SuppressWarnings("javadoc")
+    public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertWithMessage("History '%s' should not have client state", history)
+             .that(clientState).isNull();
+    }
+
+    /**
+     * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
+     *
+     * @param event event to be asserted
+     * @param eventType expected type
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
+     * have client state)
+     * @param value the only value expected in the client state bundle (or {@code null} if it
+     * shouldn't have client state)
+     * @param fieldClassificationResults expected results when asserting field classification
+     */
+    private static void assertFillEvent(@NonNull FillEventHistory.Event event,
+            int eventType, @Nullable String datasetId,
+            @Nullable String key, @Nullable String value,
+            @Nullable FieldClassificationResult[] fieldClassificationResults) {
+        assertThat(event).isNotNull();
+        assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
+        if (datasetId == null) {
+            assertWithMessage("Event %s should not have dataset id", event)
+                .that(event.getDatasetId()).isNull();
+        } else {
+            assertWithMessage("Wrong dataset id for %s", event)
+                .that(event.getDatasetId()).isEqualTo(datasetId);
+        }
+        final Bundle clientState = event.getClientState();
+        if (key == null) {
+            assertWithMessage("Event '%s' should not have client state", event)
+                .that(clientState).isNull();
+        } else {
+            assertClientState(event, clientState, key, value);
+        }
+        assertWithMessage("Event '%s' should not have selected datasets", event)
+                .that(event.getSelectedDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have ignored datasets", event)
+                .that(event.getIgnoredDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have changed fields", event)
+                .that(event.getChangedFields()).isEmpty();
+        assertWithMessage("Event '%s' should not have manually-entered fields", event)
+                .that(event.getManuallyEnteredField()).isEmpty();
+        final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
+        if (fieldClassificationResults == null) {
+            assertThat(detectedFields).isEmpty();
+        } else {
+            assertThat(detectedFields).hasSize(fieldClassificationResults.length);
+            int i = 0;
+            for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
+                assertMatches(i, entry, fieldClassificationResults[i]);
+                i++;
+            }
+        }
+    }
+
+    private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
+            FieldClassificationResult expectedResult) {
+        assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
+                .isEqualTo(expectedResult.id);
+        final List<Match> matches = actualResult.getValue().getMatches();
+        assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
+                .isEqualTo(expectedResult.remoteIds.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]);
+            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
+                .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
+        }
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @NonNull String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @NonNull String datasetId) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
+     * event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull AutofillId fieldId, @NonNull String remoteId, float score) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId, remoteId, score)
+                });
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull FieldClassificationResult[] results) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
+    }
+
+    public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
+    }
+
+    @NonNull
+    public static String getActivityName(List<FillContext> contexts) {
+        if (contexts == null) return "N/A (null contexts)";
+
+        if (contexts.isEmpty()) return "N/A (empty contexts)";
+
+        final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
+        if (structure == null) return "N/A (no AssistStructure)";
+
+        final ComponentName componentName = structure.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    public static void assertFloat(float actualValue, float expectedValue) {
+        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+    }
+
+    public static void assertHasFlags(int actualFlags, int expectedFlags) {
+        assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
+                .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
+    }
+
+    public static String callbackEventAsString(int event) {
+        switch (event) {
+            case AutofillCallback.EVENT_INPUT_HIDDEN:
+                return "HIDDEN";
+            case AutofillCallback.EVENT_INPUT_SHOWN:
+                return "SHOWN";
+            case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
+                return "UNAVAILABLE";
+            default:
+                return "UNKNOWN:" + event;
+        }
+    }
+
+    public static String importantForAutofillAsString(int mode) {
+        switch (mode) {
+            case View.IMPORTANT_FOR_AUTOFILL_AUTO:
+                return "IMPORTANT_FOR_AUTOFILL_AUTO";
+            case View.IMPORTANT_FOR_AUTOFILL_YES:
+                return "IMPORTANT_FOR_AUTOFILL_YES";
+            case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
+            case View.IMPORTANT_FOR_AUTOFILL_NO:
+                return "IMPORTANT_FOR_AUTOFILL_NO";
+            case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
+            default:
+                return "UNKNOWN:" + mode;
+        }
+    }
+
+    public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
+        if (hints == null || expectedHint == null) return false;
+        for (String actualHint : hints) {
+            if (expectedHint.equals(actualHint)) return true;
+        }
+        return false;
     }
 
     private Helper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    static class FieldClassificationResult {
+        public final AutofillId id;
+        public final String[] remoteIds;
+        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[] remoteIds,
+                float[] scores) {
+            this.id = id;
+            this.remoteIds = remoteIds;
+            this.scores = scores;
+        }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
index 01878ab..66e857b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
@@ -21,5 +21,6 @@
  */
 enum IdMode {
     RESOURCE_ID,
-    HTML_NAME
+    HTML_NAME,
+    HTML_NAME_OR_RESOURCE_ID
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
index e96df05..935be23 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
@@ -39,30 +39,35 @@
 public class ImageTransformationTest {
 
     @Test
+    @SuppressWarnings("deprecation")
     public void testAllNullBuilder() {
         assertThrows(NullPointerException.class,
                 () ->  new ImageTransformation.Builder(null, null, 0));
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void testNullAutofillIdBuilder() {
         assertThrows(NullPointerException.class,
                 () ->  new ImageTransformation.Builder(null, Pattern.compile(""), 1));
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void testNullRegexBuilder() {
         assertThrows(NullPointerException.class,
                 () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void testNullSubstBuilder() {
         assertThrows(IllegalArgumentException.class,
                 () ->  new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void fieldCannotBeFound() throws Exception {
         AutofillId unknownId = new AutofillId(42);
 
@@ -82,6 +87,7 @@
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void theOneOptionsMatches() throws Exception {
         AutofillId id = new AutofillId(1);
         ImageTransformation trans = new ImageTransformation
@@ -99,6 +105,25 @@
     }
 
     @Test
+    public void theOneOptionsMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 42);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
     public void noOptionsMatches() throws Exception {
         AutofillId id = new AutofillId(1);
         ImageTransformation trans = new ImageTransformation
@@ -116,6 +141,7 @@
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void multipleOptionsOneMatches() throws Exception {
         AutofillId id = new AutofillId(1);
         ImageTransformation trans = new ImageTransformation
@@ -134,6 +160,26 @@
     }
 
     @Test
+    public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*2"), 2, "I am content")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 2);
+        verify(template).setContentDescription(0, "I am content");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
     public void twoOptionsMatch() throws Exception {
         AutofillId id = new AutofillId(1);
         ImageTransformation trans = new ImageTransformation
@@ -151,4 +197,24 @@
         // If two options match, the first one is picked
         verify(template, only()).setImageViewResource(0, 1);
     }
+
+    @Test
+    public void twoOptionsMatchWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("ab");
+
+        trans.apply(finder, template, 0);
+
+        // If two options match, the first one is picked
+        verify(template).setImageViewResource(0, 1);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 6694e5a..02983ba 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -18,24 +18,29 @@
 
 import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
 import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
-import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.IDLE_UNBIND_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.dumpAutofillService;
 import static android.autofillservice.cts.Helper.dumpStructure;
+import static android.autofillservice.cts.Helper.getActivityName;
+import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
 
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.app.assist.AssistStructure;
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.content.ComponentName;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillCallback;
 import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.FillResponse;
 import android.service.autofill.SaveCallback;
 import android.support.annotation.Nullable;
@@ -53,45 +58,119 @@
  */
 public class InstrumentedAutoFillService extends AutofillService {
 
-    static final String SERVICE_NAME = InstrumentedAutoFillService.class.getPackage()
-            .getName() + "/." + InstrumentedAutoFillService.class.getSimpleName();
+    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    static final String SERVICE_CLASS = "InstrumentedAutoFillService";
+
+    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
 
     private static final String TAG = "InstrumentedAutoFillService";
 
     private static final boolean DUMP_FILL_REQUESTS = false;
     private static final boolean DUMP_SAVE_REQUESTS = false;
 
-    private static final String STATE_CONNECTED = "CONNECTED";
-    private static final String STATE_DISCONNECTED = "DISCONNECTED";
-
-    private static final AtomicReference<InstrumentedAutoFillService> sInstance =
+    protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
             new AtomicReference<>();
     private static final Replier sReplier = new Replier();
-    private static final BlockingQueue<String> sConnectionStates = new LinkedBlockingQueue<>();
 
     private static final Object sLock = new Object();
 
-    // @GuardedBy("sLock")
+    // @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;
+
+    protected static String sServiceLabel = SERVICE_CLASS;
+
     public InstrumentedAutoFillService() {
         sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
     }
 
-    public static AutofillService peekInstance() {
+    private static InstrumentedAutoFillService peekInstance() {
         return sInstance.get();
     }
 
+    /**
+     * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
+     * expected size.
+     */
+    public static List<Event> getFillEvents(int expectedSize) throws Exception {
+        final List<Event> events = getFillEventHistory(expectedSize).getEvents();
+        // Sanity check
+        if (expectedSize > 0 && events == null || events.size() != expectedSize) {
+            throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
+                    + ", but it is: " + events);
+        }
+        return events;
+    }
+
+    /**
+     * Gets the {@link FillEventHistory}, waiting until it has the expected size.
+     */
+    public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        final InstrumentedAutoFillService service = peekInstance();
+
+        if (expectedSize == 0) {
+            // Need to always sleep as there is no condition / callback to be used to wait until
+            // expected number of events is set.
+            SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+            final FillEventHistory history = service.getFillEventHistory();
+            assertThat(history.getEvents()).isNull();
+            return history;
+        }
+
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = service.getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                if (events.size() != expectedSize) {
+                    Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
+                    return null;
+                }
+            } else {
+                Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
+                return null;
+            }
+            return history;
+        });
+    }
+
+    /**
+     * Asserts there is no {@link FillEventHistory}.
+     */
+    public static void assertNoFillEventHistory() {
+        // Need to always sleep as there is no condition / callback to be used to wait until
+        // expected number of events is set.
+        SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+        assertThat(peekInstance().getFillEventHistory()).isNull();
+
+    }
+
+    /**
+     * Gets the service label associated with the current instance.
+     */
+    public static String getServiceLabel() {
+        return sServiceLabel;
+    }
+
     @Override
     public void onConnected() {
-        Log.v(TAG, "onConnected(): " + sConnectionStates);
-        sConnectionStates.offer(STATE_CONNECTED);
+        synchronized (sLock) {
+            Log.v(TAG, "onConnected(): connected=" + sConnected);
+            sConnected = true;
+        }
     }
 
     @Override
     public void onDisconnected() {
-        Log.v(TAG, "onDisconnected(): " + sConnectionStates);
-        sConnectionStates.offer(STATE_DISCONNECTED);
+        synchronized (sLock) {
+            Log.v(TAG, "onDisconnected(): connected=" + sConnected);
+            sConnected = false;
+        }
     }
 
     @Override
@@ -118,7 +197,14 @@
                 return;
             }
         }
-        sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback);
+        sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback,
+                request.getDatasetIds());
+    }
+
+    private static boolean isConnected() {
+        synchronized (sLock) {
+            return sConnected;
+        }
     }
 
     private boolean fromSamePackage(List<FillContext> contexts) {
@@ -148,17 +234,13 @@
      * Waits until {@link #onConnected()} is called, or fails if it times out.
      *
      * <p>This method is useful on tests that explicitly verifies the connection, but should be
-     * avoided in other tests, as it adds extra time to the test execution - if a text needs to
-     * block until the service receives a callback, it should use
-     * {@link Replier#getNextFillRequest()} instead.
+     * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
+     * where the service might have being disconnected already; for example, if the fill request
+     * was replied with a {@code null} response) - if a text needs to block until the service
+     * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
      */
-    static void waitUntilConnected() throws InterruptedException {
-        final String state = sConnectionStates.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        if (state == null) {
-            dumpAutofillService();
-            throw new RetryableException("not connected in %d ms", CONNECTION_TIMEOUT_MS);
-        }
-        assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_CONNECTED);
+    static void waitUntilConnected() throws Exception {
+        waitConnectionState(CONNECTION_TIMEOUT, true);
     }
 
     /**
@@ -167,13 +249,14 @@
      * <p>This method is useful on tests that explicitly verifies the connection, but should be
      * avoided in other tests, as it adds extra time to the test execution.
      */
-    static void waitUntilDisconnected() throws InterruptedException {
-        final String state = sConnectionStates.poll(2 * IDLE_UNBIND_TIMEOUT_MS,
-                TimeUnit.MILLISECONDS);
-        if (state == null) {
-            throw new RetryableException("not disconnected in %d ms", IDLE_UNBIND_TIMEOUT_MS);
-        }
-        assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_DISCONNECTED);
+    static void waitUntilDisconnected() throws Exception {
+        waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
+    }
+
+    private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
+        timeout.run("wait for connected=" + expected,  () -> {
+            return isConnected() == expected ? Boolean.TRUE : null;
+        });
     }
 
     /**
@@ -184,7 +267,9 @@
     }
 
     static void resetStaticState() {
-        sConnectionStates.clear();
+        sInstance.set(null);
+        sConnected = false;
+        sServiceLabel = SERVICE_CLASS;
     }
 
     /**
@@ -209,6 +294,11 @@
             this.flags = flags;
             structure = contexts.get(contexts.size() - 1).getStructure();
         }
+
+        @Override
+        public String toString() {
+            return "FillRequest:" + getActivityName(contexts);
+        }
     }
 
     /**
@@ -217,12 +307,14 @@
      * that can be asserted at the end of a test case.
      */
     static final class SaveRequest {
-        final List<FillContext> contexts;
-        final AssistStructure structure;
-        final Bundle data;
-        final SaveCallback callback;
+        public final List<FillContext> contexts;
+        public final AssistStructure structure;
+        public final Bundle data;
+        public final SaveCallback callback;
+        public final List<String> datasetIds;
 
-        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
+        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
             if (contexts != null && contexts.size() > 0) {
                 structure = contexts.get(contexts.size() - 1).getStructure();
             } else {
@@ -231,6 +323,12 @@
             this.contexts = contexts;
             this.data = data;
             this.callback = callback;
+            this.datasetIds = datasetIds;
+        }
+
+        @Override
+        public String toString() {
+            return "SaveRequest:" + getActivityName(contexts);
         }
     }
 
@@ -246,13 +344,16 @@
         private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
         private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
 
-        private List<Exception> mExceptions;
+        private List<Throwable> mExceptions;
+        private IntentSender mOnSaveIntentSender;
         private String mAcceptedPackageName;
 
+        private boolean mReportUnhandledFillRequest = true;
+        private boolean mReportUnhandledSaveRequest = true;
+
         private Replier() {
         }
 
-
         private IdMode mIdMode = IdMode.RESOURCE_ID;
 
         public void setIdMode(IdMode mode) {
@@ -266,11 +367,11 @@
         /**
          * Gets the exceptions thrown asynchronously, if any.
          */
-        @Nullable List<Exception> getExceptions() {
+        @Nullable List<Throwable> getExceptions() {
             return mExceptions;
         }
 
-        private void addException(@Nullable Exception e) {
+        private void addException(@Nullable Throwable e) {
             if (e == null) return;
 
             if (mExceptions == null) {
@@ -301,27 +402,63 @@
         }
 
         /**
+         * Sets the {@link IntentSender} that is passed to
+         * {@link SaveCallback#onSuccess(IntentSender)}.
+         */
+        void setOnSave(IntentSender intentSender) {
+            mOnSaveIntentSender = intentSender;
+        }
+
+        /**
          * Gets the next fill request, in the order received.
          *
          * <p>Typically called at the end of a test case, to assert the initial request.
          */
-        FillRequest getNextFillRequest() throws InterruptedException {
-            final FillRequest request = mFillRequests.poll(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        FillRequest getNextFillRequest() {
+            FillRequest request;
+            try {
+                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
             if (request == null) {
-                throw new RetryableException("onFillRequest() not called in %s ms",
-                        FILL_TIMEOUT_MS);
+                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
             }
             return request;
         }
 
         /**
-         * Asserts the total number of {@link AutofillService#onFillRequest(
-         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback)}, minus those
-         * returned by {@link #getNextFillRequest()}.
+         * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
+         * was not called.
+         *
+         * <p>Should only be called in cases where it's not expected to be called, as it will
+         * sleep for a few ms.
          */
-        void assertNumberUnhandledFillRequests(int expected) {
-            assertWithMessage("Invalid number of fill requests").that(mFillRequests.size())
-                    .isEqualTo(expected);
+        void assertOnFillRequestNotCalled() {
+            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
+            assertThat(mFillRequests).isEmpty();
+        }
+
+        /**
+         * Asserts all {@link AutofillService#onFillRequest(
+         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
+         * received by the service were properly {@link #getNextFillRequest() handled} by the test
+         * case.
+         */
+        void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
         }
 
         /**
@@ -336,23 +473,39 @@
          *
          * <p>Typically called at the end of a test case, to assert the initial request.
          */
-        SaveRequest getNextSaveRequest() throws InterruptedException {
-            final SaveRequest request = mSaveRequests.poll(SAVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        SaveRequest getNextSaveRequest() {
+            SaveRequest request;
+            try {
+                request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
             if (request == null) {
-                throw new RetryableException(
-                        "onSaveRequest() not called in %d ms", SAVE_TIMEOUT_MS);
+                throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
             }
             return request;
         }
 
         /**
-         * Asserts the total number of
-         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
-         * minus those returned by {@link #getNextSaveRequest()}.
+         * Asserts all
+         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
+         * save requests} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
          */
-        void assertNumberUnhandledSaveRequests(int expected) {
-            assertWithMessage("Invalid number of save requests").that(mSaveRequests.size())
-                    .isEqualTo(expected);
+        void assertNoUnhandledSaveRequests() {
+            if (mSaveRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledSaveRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
+                        + "but logging just in case: " + mSaveRequests);
+                return;
+            }
+
+            mReportUnhandledSaveRequest = false;
+            throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
+                    + mSaveRequests);
         }
 
         /**
@@ -363,7 +516,10 @@
             mFillRequests.clear();
             mSaveRequests.clear();
             mExceptions = null;
+            mOnSaveIntentSender = null;
             mAcceptedPackageName = null;
+            mReportUnhandledFillRequest = true;
+            mReportUnhandledSaveRequest = true;
         }
 
         private void onFillRequest(List<FillContext> contexts, Bundle data,
@@ -371,7 +527,7 @@
             try {
                 CannedFillResponse response = null;
                 try {
-                    response = mResponses.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
                     Log.w(TAG, "Interrupted getting CannedResponse: " + e);
                     Thread.currentThread().interrupt();
@@ -379,9 +535,10 @@
                     return;
                 }
                 if (response == null) {
-                    final String msg = "onFillRequest() received when no CannedResponse was set";
+                    final String activityName = getActivityName(contexts);
+                    final String msg = "onFillRequest() for activity " + activityName
+                            + " received when no canned response was set.";
                     dumpStructure(msg, contexts);
-                    addException(new RetryableException(msg));
                     return;
                 }
                 if (response.getResponseType() == NULL) {
@@ -413,24 +570,33 @@
                         fillResponse = response.asFillResponse(
                                 (name) -> Helper.findNodeByHtmlName(contexts, name));
                         break;
+                    case HTML_NAME_OR_RESOURCE_ID:
+                        fillResponse = response.asFillResponse(
+                                (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
+                        break;
                     default:
                         throw new IllegalStateException("Unknown id mode: " + mIdMode);
                 }
 
                 Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
                 callback.onSuccess(fillResponse);
-            } catch (Exception e) {
-                addException(e);
+            } catch (Throwable t) {
+                addException(t);
             } finally {
                 mFillRequests.offer(new FillRequest(contexts, data, cancellationSignal, callback,
                         flags));
             }
         }
 
-        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
-            Log.d(TAG, "onSaveRequest()");
-            mSaveRequests.offer(new SaveRequest(contexts, data, callback));
-            callback.onSuccess();
+        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();
+            }
         }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java
new file mode 100644
index 0000000..b1c2a45
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.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.autofillservice.cts;
+import android.service.autofill.AutofillService;
+
+/**
+ * Implementation of {@link AutofillService} using A11Y compat mode used in the tests.
+ */
+public class InstrumentedAutoFillServiceCompatMode extends InstrumentedAutoFillService {
+
+    @SuppressWarnings("hiding")
+    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    @SuppressWarnings("hiding")
+    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceCompatMode";
+    @SuppressWarnings("hiding")
+    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
+
+    public InstrumentedAutoFillServiceCompatMode() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
new file mode 100644
index 0000000..3d70bd0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import android.support.annotation.NonNull;
+
+/**
+ * Generic helper for JUnit needs.
+ */
+public final class JUnitHelper {
+
+    private static String sCurrentTestNamer;
+
+    @NonNull
+    static String getCurrentTestName() {
+        return sCurrentTestNamer != null ? sCurrentTestNamer : "N/A";
+    }
+
+    public static void setCurrentTestName(String name) {
+        sCurrentTestNamer = name;
+    }
+
+    private JUnitHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index b41cef8..7ea52d2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -22,6 +22,9 @@
 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.OnClickListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
@@ -99,6 +102,42 @@
             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() {
@@ -195,6 +234,10 @@
         syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
     }
 
+    void forceAutofillOnPassword() {
+        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
+    }
+
     /**
      * Visits the {@code username_label} in the UiThread.
      */
@@ -210,6 +253,13 @@
     }
 
     /**
+     * Gets the {@code username_label} view.
+     */
+    TextView getUsernameLabel() {
+        return mUsernameLabel;
+    }
+
+    /**
      * Gets the {@code username} view.
      */
     EditText getUsername() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index ab25326..c427bf7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -16,33 +16,31 @@
 
 package android.autofillservice.cts;
 
-import static android.app.Activity.RESULT_CANCELED;
-import static android.app.Activity.RESULT_OK;
 import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
 import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
 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.assertNoDanglingSessions;
+import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertHasFlags;
 import static android.autofillservice.cts.Helper.assertNumberOfChildren;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.assertTextOnly;
 import static android.autofillservice.cts.Helper.assertValue;
 import static android.autofillservice.cts.Helper.dumpStructure;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.runShellCommand;
 import static android.autofillservice.cts.Helper.setUserComplete;
+import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
+import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
 import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
 import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
 import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
 import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
 import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.tap;
 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;
@@ -64,13 +62,14 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.graphics.Color;
 import android.os.Bundle;
-import android.service.autofill.FillEventHistory;
+import android.os.SystemClock;
 import android.service.autofill.SaveInfo;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
@@ -81,13 +80,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillValue;
 import android.widget.RemoteViews;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
@@ -98,26 +92,10 @@
  * This is the test case covering most scenarios - other test cases will cover characteristics
  * specific to that test's activity (for example, custom views).
  */
-public class LoginActivityTest extends AutoFillServiceTestCase {
+public class LoginActivityTest extends AbstractLoginActivityTestCase {
 
     private static final String TAG = "LoginActivityTest";
 
-    @Rule
-    public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-        new AutofillActivityTestRule<LoginActivity>(LoginActivity.class);
-
-    private LoginActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
-
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testAutoFillNoDatasets() throws Exception {
         // Set service.
@@ -129,19 +107,13 @@
         // Trigger autofill.
         mActivity.onUsername(View::requestFocus);
 
-        // Test connection lifecycle.
-        waitUntilConnected();
+        // Make sure a fill request is called but don't check for connected() - as we're returning
+        // a null response, the service might have been disconnected already by the time we assert
+        // it.
         sReplier.getNextFillRequest();
 
         // Make sure UI is not shown.
-        sUiBot.assertNoDatasets();
-
-        // Try to trigger it again...
-
-        mActivity.onPassword(View::requestFocus);
-        // ...and make sure it didn't
-        sUiBot.assertNoDatasets();
-        sReplier.assertNumberUnhandledFillRequests(0);
+        mUiBot.assertNoDatasetsEver();
 
         // Test connection lifecycle.
         waitUntilDisconnected();
@@ -149,18 +121,28 @@
 
     @Test
     public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
+        autofillAfterServiceReturnedNoDatasets(true);
+    }
+
+    @Test
+    public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
+        autofillAfterServiceReturnedNoDatasets(false);
+    }
+
+    private void autofillAfterServiceReturnedNoDatasets(boolean manually) throws Exception {
         // Set service.
         enableService();
 
         // Set expectations.
         sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger autofill.
         mActivity.onUsername(View::requestFocus);
         sReplier.getNextFillRequest();
 
         // Make sure UI is not shown.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Try again, forcing it
         sReplier.addResponse(new CannedDataset.Builder()
@@ -168,15 +150,21 @@
                 .setField(ID_PASSWORD, "sweet")
                 .setPresentation(createPresentation("The Dude"))
                 .build());
-        mActivity.expectAutoFill("dude", "sweet");
 
-        mActivity.forceAutofillOnUsername();
+        final int expectedFlags;
+        if (manually) {
+            expectedFlags = FLAG_MANUAL_REQUEST;
+            mActivity.forceAutofillOnUsername();
+        } else {
+            expectedFlags = 0;
+            requestFocusOnPassword();
+        }
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest.flags, expectedFlags);
 
-        // Selects the dataset.
-        sUiBot.selectDataset("The Dude");
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -184,6 +172,15 @@
 
     @Test
     public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        autofillAndSaveAfterServiceReturnedNoDatasets(true);
+    }
+
+    @Test
+    public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        autofillAndSaveAfterServiceReturnedNoDatasets(false);
+    }
+
+    private void autofillAndSaveAfterServiceReturnedNoDatasets(boolean manually) throws Exception {
         // Set service.
         enableService();
 
@@ -191,41 +188,158 @@
         sReplier.addResponse(NO_RESPONSE);
 
         // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
+        // NOTE: must be on password, as saveOnlyTest() will trigger on username
+        mActivity.onPassword(View::requestFocus);
         sReplier.getNextFillRequest();
 
         // Make sure UI is not shown.
-        sUiBot.assertNoDatasets();
-        sReplier.assertNumberUnhandledFillRequests(0);
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
         mActivity.onPassword(View::requestFocus);
-        sUiBot.assertNoDatasets();
-        sReplier.assertNumberUnhandledFillRequests(0);
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
 
         // Try again, forcing it
-        saveOnlyTest(true);
+        saveOnlyTest(manually);
     }
 
+    /**
+     * More detailed test of what should happen after a service returns a {@code null} FillResponse:
+     * views that have already been visit should not trigger a new session, unless a manual autofill
+     * workflow was requested.
+     */
     @Test
-    public void testAutoFillOneDataset() throws Exception {
+    public void testMultipleIterationsAfterServiceReturnedNoDatasets() throws Exception {
         // Set service.
         enableService();
 
-        // Set expectations.
+        // Trigger autofill on username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        // Trigger autofill on password - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        // Tap username again - should be ignored
+        mActivity.onUsername(View::requestFocus);
+        sReplier.assertOnFillRequestNotCalled();
+        waitUntilDisconnected();
+
+        // Tap password again - should be ignored
+        mActivity.onPassword(View::requestFocus);
+        sReplier.assertOnFillRequestNotCalled();
+        waitUntilDisconnected();
+
+        // Trigger autofill by manually requesting username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnUsername();
+        final FillRequest manualRequest1 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest1.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+
+        // Trigger autofill by manually requesting password - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnPassword();
+        final FillRequest manualRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest2.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+    }
+
+    @Test
+    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
+        // Set service.
+        enableService();
+
+        // First request
         sReplier.addResponse(new CannedDataset.Builder()
                 .setField(ID_USERNAME, "dude")
                 .setField(ID_PASSWORD, "sweet")
                 .setPresentation(createPresentation("The Dude"))
                 .build());
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Second request
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+
+        mActivity.forceAutofillOnUsername();
+        final FillRequest secondRequest = sReplier.getNextFillRequest();
+        assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
+        mUiBot.assertDatasets("THE DUDE");
+    }
+
+    @Test
+    public void testAutoFillOneDataset() throws Exception {
+        autofillOneDatasetTest(BorderType.NONE);
+    }
+
+    @Test
+    public void testAutoFillOneDataset_withHeader() throws Exception {
+        autofillOneDatasetTest(BorderType.HEADER_ONLY);
+    }
+
+    @Test
+    public void testAutoFillOneDataset_withFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.FOOTER_ONLY);
+    }
+
+    @Test
+    public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.BOTH);
+    }
+
+    private enum BorderType {
+        NONE,
+        HEADER_ONLY,
+        FOOTER_ONLY,
+        BOTH
+    }
+
+    private void autofillOneDatasetTest(BorderType borderType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        String expectedHeader = null, expectedFooter = null;
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
+            expectedHeader = "Head";
+            builder.setHeader(createPresentation(expectedHeader));
+        }
+        if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
+            expectedFooter = "Tails";
+            builder.setFooter(createPresentation(expectedFooter));
+        }
+        sReplier.addResponse(builder.build());
         mActivity.expectAutoFill("dude", "sweet");
 
         // Dynamically set password to make sure it's sanitized.
         mActivity.onPassword((v) -> v.setText("I AM GROOT"));
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
+                "The Dude");
+
+        mUiBot.selectDataset(picker, "The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -265,18 +379,18 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Make sure all datasets are available...
-        sUiBot.assertDatasets("The Dude", "THE DUDE");
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
 
         // ... on all fields.
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("The Dude", "THE DUDE");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -315,23 +429,23 @@
         }
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Make sure all datasets are available on username...
-        sUiBot.assertDatasets("The Dude", "THE DUDE");
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
 
         // ... but just one for password
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("The Dude");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
 
         // Auto-fill it.
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("The Dude", "THE DUDE");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
         if (fillsAll) {
-            sUiBot.selectDataset("The Dude");
+            mUiBot.selectDataset("The Dude");
         } else {
-            sUiBot.selectDataset("THE DUDE");
+            mUiBot.selectDataset("THE DUDE");
         }
 
         // Check the results.
@@ -339,6 +453,43 @@
     }
 
     @Test
+    public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .build())
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available...
+        mUiBot.assertDatasets("The Dude");
+
+        // ... on all fields.
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
     public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
         mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
             @Override
@@ -373,25 +524,27 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Make sure tapping on other fields from the dataset does not trigger it again
-        mActivity.onPassword(View::requestFocus);
-        sReplier.assertNumberUnhandledFillRequests(0);
+        requestFocusOnPassword();
+        sReplier.assertNoUnhandledFillRequests();
 
-        mActivity.onUsername(View::requestFocus);
-        sReplier.assertNumberUnhandledFillRequests(0);
+        requestFocusOnUsername();
+        sReplier.assertNoUnhandledFillRequests();
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
 
         // Make sure tapping on other fields from the dataset does not trigger it again
-        mActivity.onPassword(View::requestFocus);
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
     }
 
     @Test
@@ -408,19 +561,49 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
 
         // Make sure tapping on autofilled field does not trigger it again
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertNoDatasets();
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
 
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertNoDatasets();
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillTapOutside() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // 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 autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        callback.assertUiShownEvent(username);
+        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());
+        callback.assertUiHiddenEvent(username);
+
+        mUiBot.assertNoDatasets();
     }
 
     @Test
@@ -437,24 +620,27 @@
                 .build());
         mActivity.expectAutoFill("dude", "sweet");
 
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        // Trigger autofill.
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
         final View username = mActivity.getUsername();
         final View password = mActivity.getPassword();
 
         callback.assertUiShownEvent(username);
 
-        mActivity.onPassword(View::requestFocus);
+        requestFocusOnPassword();
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(password);
 
-        mActivity.onUsername(View::requestFocus);
+        // Unregister callback to make sure no more events are received
         mActivity.unregisterCallback();
+        requestFocusOnUsername();
+        // Blindly sleep - we cannot wait on any event as none should have been sent
+        SystemClock.sleep(MyAutofillCallback.MY_TIMEOUT.ms());
         callback.assertNumberUnhandledEvents(0);
 
-        // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -500,7 +686,7 @@
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Assert callback was called
         final View username = mActivity.getUsername();
@@ -518,6 +704,7 @@
 
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
                         .setField(ID_USERNAME, "dude")
                         .setField(ID_PASSWORD, "sweet")
                         .setPresentation(createPresentation("The Dude"))
@@ -528,15 +715,23 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Since this is a Presubmit test, wait for connection to avoid flakiness.
         waitUntilConnected();
 
-        sReplier.getNextFillRequest();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized...
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // ...but labels weren't
+        assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -555,23 +750,22 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
         // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "dude");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(password, "dude");
+        assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
+        assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
+        assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "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");
-
-        // Sanity check: once saved, the session should be finished.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -595,7 +789,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Since this is a Presubmit test, wait for connection to avoid flakiness.
         waitUntilConnected();
@@ -609,7 +803,7 @@
             runShellCommand("appops set %s SYSTEM_ALERT_WINDOW allow", mPackageName);
 
             // Make sure the fill UI is shown.
-            sUiBot.assertDatasets("The Dude");
+            mUiBot.assertDatasets("The Dude");
 
             final CountDownLatch latch = new CountDownLatch(1);
 
@@ -639,7 +833,7 @@
             assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
 
             // Auto-fill it.
-            sUiBot.selectDataset("The Dude");
+            mUiBot.selectDataset("The Dude");
 
             // Check the results.
             mActivity.assertAutoFilled();
@@ -659,23 +853,20 @@
             assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
             // Assert the snack bar is shown and tap "Save".
-            sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
             final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
             // Assert value of expected fields - should not be sanitized.
             final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
             assertTextAndValue(username, "dude");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
             assertTextAndValue(password, "dude");
 
             // 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");
-
-            // Sanity check: once saved, the session should be finished.
-            assertNoDanglingSessions();
         } finally {
             // Make sure we can no longer add overlays
             runShellCommand("appops set %s SYSTEM_ALERT_WINDOW ignore", mPackageName);
@@ -744,14 +935,14 @@
         }
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Make sure all datasets are shown.
-        final UiObject2 picker = sUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
+        final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
 
         // Auto-fill it.
-        sUiBot.selectDataset(picker, name);
+        mUiBot.selectDataset(picker, name);
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -776,21 +967,21 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Check initial field.
-        sUiBot.assertDatasets("The Dude");
+        mUiBot.assertDatasets("The Dude");
 
         // Then move around...
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("Dude's password");
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("The Dude");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Dude's password");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
 
         // Auto-fill it.
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.selectDataset("Dude's password");
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Dude's password");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -820,21 +1011,21 @@
         mActivity.expectAutoFill("user1", "pass1");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Check initial field.
-        sUiBot.assertDatasets("Dataset1", "User2");
+        mUiBot.assertDatasets("Dataset1", "User2");
 
         // Then move around...
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("Pass1", "Dataset2");
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("Dataset1", "User2");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Dataset2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("Dataset1", "User2");
 
         // Auto-fill it.
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.selectDataset("Pass1");
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -863,21 +1054,21 @@
         mActivity.expectAutoFill("user1", "pass1");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Check initial field.
-        sUiBot.assertDatasets("User1", "User2");
+        mUiBot.assertDatasets("User1", "User2");
 
         // Then move around...
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("Pass1", "Pass2");
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("User1", "User2");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
 
         // Auto-fill it.
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.selectDataset("Pass1");
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -906,20 +1097,20 @@
         mActivity.expectAutoFill("user2", "pass2");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Check initial field.
-        sUiBot.assertDatasets("User1", "User2");
+        mUiBot.assertDatasets("User1", "User2");
 
         // Then move around...
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("Pass2");
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("User1", "User2");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
 
         // Auto-fill it.
-        sUiBot.selectDataset("User2");
+        mUiBot.selectDataset("User2");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -948,175 +1139,26 @@
         mActivity.expectAutoFill("user1", "pass1");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Check initial field.
-        sUiBot.assertDatasets("User1");
+        mUiBot.assertDatasets("User1");
 
         // Then move around...
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.assertDatasets("Pass1", "Pass2");
-        mActivity.onUsername(View::requestFocus);
-        sUiBot.assertDatasets("User1");
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1");
 
         // Auto-fill it.
-        sUiBot.selectDataset("User1");
+        mUiBot.selectDataset("User1");
 
         // Check the results.
         mActivity.assertAutoFilled();
     }
 
     @Test
-    public void filterText() 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());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA, AB);
-
-        // Only one dataset start with 'aa'
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA);
-
-        // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB);
-
-        // With no filter text all datasets should be shown
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // No dataset start with 'aaa'
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void filterTextNullValuesAlwaysMatched() 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, (String) null)
-                        .setPresentation(createPresentation(B))
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // Two datasets start with 'a' and one with null value always shown
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // One dataset start with 'aa' and one with null value always shown
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA, B);
-
-        // Two datasets start with 'a' and one with null value always shown
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // With no filter text all datasets should be shown
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB, B);
-
-        // No dataset start with 'aaa' and one with null value always shown
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(B);
-    }
-
-    @Test
-    public void filterTextDifferentPrefixes() throws Exception {
-        final String A = "aaa";
-        final String B = "bra";
-        final String C = "cadabra";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, A)
-                        .setPresentation(createPresentation(A))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, B)
-                        .setPresentation(createPresentation(B))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, C)
-                        .setPresentation(createPresentation(C))
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(A, B, C);
-
-        mActivity.onUsername((v) -> v.setText("a"));
-        sUiBot.assertDatasets(A);
-
-        mActivity.onUsername((v) -> v.setText("b"));
-        sUiBot.assertDatasets(B);
-
-        mActivity.onUsername((v) -> v.setText("c"));
-        sUiBot.assertDatasets(C);
-    }
-
-    @Test
     public void testSaveOnly() throws Exception {
         saveOnlyTest(false);
     }
@@ -1142,7 +1184,7 @@
         }
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1158,10 +1200,11 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNumberUnhandledSaveRequests(0);
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
 
         // Assert value of expected fields - should not be sanitized.
         try {
@@ -1173,9 +1216,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finished.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1189,34 +1229,10 @@
     }
 
     @Test
-    @Ignore("Test fail on some devices because Recents UI is not well defined: b/72044685")
-    public void testSaveGoesAwayWhenTappingRecentsButton() throws Exception {
-        // Launches new activity first...
-        startCheckoutActivityAsNewTask();
-        try {
-            // .. then the real activity being tested.
-            sUiBot.switchAppsUsingRecents();
-            sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-
-            saveGoesAway(DismissType.RECENTS_BUTTON);
-        } finally {
-            CheckoutActivity.finishIt();
-        }
-    }
-
-    @Test
     public void testSaveGoesAwayWhenTouchingOutside() throws Exception {
         saveGoesAway(DismissType.TOUCH_OUTSIDE);
     }
 
-    private void startCheckoutActivityAsNewTask() {
-        final Intent intent = new Intent(mContext, CheckoutActivity.class);
-        intent.setFlags(
-                Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
-        mContext.startActivity(intent);
-        sUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
-    }
-
     private void saveGoesAway(DismissType dismissType) throws Exception {
         enableService();
 
@@ -1229,7 +1245,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1245,27 +1261,23 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // Then make sure it goes away when user doesn't want it..
         switch (dismissType) {
             case BACK_BUTTON:
-                sUiBot.pressBack();
+                mUiBot.pressBack();
                 break;
             case HOME_BUTTON:
-                sUiBot.pressHome();
+                mUiBot.pressHome();
                 break;
             case TOUCH_OUTSIDE:
-                sUiBot.assertShownByText(expectedMessage).click();
-                break;
-            case RECENTS_BUTTON:
-                sUiBot.switchAppsUsingRecents();
-                sUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
+                mUiBot.assertShownByText(expectedMessage).click();
                 break;
             default:
                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
     }
 
     @Test
@@ -1298,7 +1310,7 @@
         }
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1314,10 +1326,10 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNumberUnhandledSaveRequests(0);
+        sReplier.assertNoUnhandledSaveRequests();
 
         // Assert value of expected fields - should not be sanitized.
         try {
@@ -1329,9 +1341,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1352,7 +1361,7 @@
         // Wait for onFill() before changing value, otherwise the fields might be changed before
         // the session started
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Set credentials...
         mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
@@ -1363,10 +1372,10 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNumberUnhandledSaveRequests(0);
+        sReplier.assertNoUnhandledSaveRequests();
 
         // Assert value of expected fields - should not be sanitized.
         try {
@@ -1378,9 +1387,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1397,7 +1403,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1414,7 +1420,7 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
@@ -1423,9 +1429,6 @@
         assertTextAndValue(username, "malkovich");
         final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
         assertTextAndValue(password, "malkovich");
-
-        // Sanity check: once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1462,7 +1465,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1487,13 +1490,12 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         if (filledFields == FilledFields.NONE) {
-            sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-            assertNoDanglingSessions();
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
             return;
         }
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
@@ -1505,9 +1507,6 @@
             final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
             assertTextAndValue(password, "whatever");
         }
-
-        // Sanity check: once saved, the session should be finished.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1555,7 +1554,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started.
@@ -1571,14 +1570,47 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        final UiObject2 saveSnackBar = sUiBot.assertSaveShowing(saveDescription, type);
-        sUiBot.saveForAutofill(saveSnackBar, true);
+        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, type);
+        mUiBot.saveForAutofill(saveSnackBar, true);
 
         // Assert save was called.
         sReplier.getNextSaveRequest();
     }
 
     @Test
+    public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Sanity check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Make sure it didn't trigger save.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
     public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
         mActivity.setFlags(FLAG_SECURE);
         testAutoFillOneDatasetAndSave();
@@ -1591,806 +1623,6 @@
     }
 
     @Test
-    public void testFillResponseAuthBothFields() throws Exception {
-        fillResponseAuthBothFields(false);
-    }
-
-    @Test
-    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
-        fillResponseAuthBothFields(true);
-    }
-
-    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        mActivity.onPassword(View::requestFocus);
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            sUiBot.selectDataset("Tap to auth response");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            sUiBot.assertDatasets("Tap to auth response");
-
-            // Make sure it's still shown on other fields...
-            mActivity.onPassword(View::requestFocus);
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            sUiBot.assertDatasets("Tap to auth response");
-
-            // Tap on 1st field to show it again...
-            mActivity.onUsername(View::requestFocus);
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
-        sUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    public void testFillResponseAuthJustOneField() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME)
-                .setIgnoreFields(ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is not show on 2nd field
-        mActivity.onPassword(View::requestFocus);
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-        // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
-        callback.assertUiShownEvent(username);
-
-        // ...and select it this time
-        sUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
-
-        callback.assertUiShownEvent(username);
-        sUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // Disables autofill so it's not triggered again after the auth activity is finished
-        // (and current session is canceled) and the login activity is resumed.
-        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
-
-        // Autofill it.
-        final CountDownLatch latch = new CountDownLatch(1);
-        AuthenticationActivity.setResultCode(latch, RESULT_OK);
-
-        sUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-
-        // Cancel session...
-        mActivity.getAutofillManager().cancel();
-
-        // ...before finishing the Auth UI.
-        latch.countDown();
-
-        sUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(true);
-    }
-
-    @Test
-    public void testFillResponseAuthServiceHasNoData() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(false);
-    }
-
-    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final CannedFillResponse response = canSave
-                ? new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                        .build()
-                : CannedFillResponse.NO_RESPONSE;
-
-        final IntentSender authentication =
-                AuthenticationActivity.createSender(mContext, 1, response);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-
-        // Select the authentication dialog.
-        sUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testFillResponseFiltering() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // ..then type something to hide it.
-        runShellCommand("input keyevent KEYCODE_A");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        runShellCommand("input keyevent KEYCODE_DEL");
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
-        sUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    public void testDatasetAuthTwoFields() throws Exception {
-        datasetAuthTwoFields(false);
-    }
-
-    @Test
-    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
-        datasetAuthTwoFields(true);
-    }
-
-    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(bogusPresentation)
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        mActivity.onPassword(View::requestFocus);
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        sUiBot.assertDatasets("Tap to auth dataset");
-
-        // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            sUiBot.selectDataset("Tap to auth dataset");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            sUiBot.assertDatasets("Tap to auth dataset");
-
-            // Make sure it's still shown on other fields...
-            mActivity.onPassword(View::requestFocus);
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            sUiBot.assertDatasets("Tap to auth dataset");
-
-            // Tap on 1st field to show it again...
-            mActivity.onUsername(View::requestFocus);
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Set up the authentication response client state
-        final Bundle authentionClientState = new Bundle();
-        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (AutofillValue) null)
-                        .setField(ID_PASSWORD, (AutofillValue) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(authentionClientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-
-        // Select a dataset from the new response
-        callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("clientStateKey1");
-        assertThat(extraValue).isEqualTo("clientStateValue1");
-    }
-
-    @Test
-    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intent
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null)
-                        .setField(ID_PASSWORD, (String) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthTwoDatasets() throws Exception {
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(bogusPresentation)
-                .build();
-        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
-                        .setPresentation(createPresentation("Tap to auth dataset 1"))
-                        .setAuthentication(authentication1)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
-                        .setPresentation(createPresentation("Tap to auth dataset 2"))
-                        .setAuthentication(authentication2)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
-
-        sUiBot.selectDataset("Tap to auth dataset 1");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthMixedSelectAuth() throws Exception {
-        datasetAuthMixedTest(true);
-    }
-
-    @Test
-    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
-        datasetAuthMixedTest(false);
-    }
-
-    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        }
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        sUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthFiltering() throws Exception {
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(bogusPresentation)
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
-
-        // ..then type something to hide it.
-        runShellCommand("input keyevent KEYCODE_A");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        runShellCommand("input keyevent KEYCODE_DEL");
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
-
-        // ...and select it this time
-        sUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
-        datasetAuthMixedFilteringTest(true);
-    }
-
-    @Test
-    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
-        datasetAuthMixedFilteringTest(false);
-    }
-
-    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(bogusPresentation)
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        } else {
-            mActivity.expectAutoFill("dude", "sweet");
-        }
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // Filter the auth dataset.
-        runShellCommand("input keyevent KEYCODE_D");
-        sUiBot.assertDatasets("What, me auth?");
-
-        // Filter all.
-        runShellCommand("input keyevent KEYCODE_W");
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Now delete the char and assert the non-auth is shown again.
-        runShellCommand("input keyevent KEYCODE_DEL");
-        callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("What, me auth?");
-
-        // Delete again and assert all dataset are shown.
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // ...and select it this time
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        sUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
     public void testDisableSelf() throws Exception {
         enableService();
 
@@ -2442,13 +1674,11 @@
         }, intentFilter);
 
         // Trigger the negative button.
-        sUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT,
+        mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT,
                 false, SAVE_DATA_TYPE_PASSWORD);
 
         // Wait for the custom action.
         assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
-
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -2491,13 +1721,11 @@
         }, intentFilter);
 
         // Trigger the negative button.
-        sUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+        mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
                 false, SAVE_DATA_TYPE_PASSWORD);
 
         // Wait for the custom action.
         assertThat(latch.await(500, TimeUnit.SECONDS)).isTrue();
-
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -2533,7 +1761,7 @@
         // Trigger auto-fill.
         mActivity.onUsername(View::requestFocus);
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
 
@@ -2578,13 +1806,13 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Explicitly uses the contextual menu to test that functionality.
-        sUiBot.getAutofillMenuOption(ID_USERNAME).click();
+        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
 
         // Should have been automatically filled.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2628,11 +1856,11 @@
         mActivity.forceAutofillOnUsername();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
 
         // Auto-fill it.
-        final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
-        sUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2659,14 +1887,14 @@
         mActivity.forceAutofillOnUsername();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
         // Username value should be available because it triggered the manual request...
         assertValue(fillRequest.structure, ID_USERNAME, "dud");
         // ... but password didn't
         assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
 
         // Selects the dataset.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2689,7 +1917,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Assert request.
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
@@ -2698,7 +1926,7 @@
         assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
 
         // Select it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2721,12 +1949,12 @@
 
         // Assert request.
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
         assertValue(fillRequest2.structure, ID_USERNAME, "dude");
         assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
 
         // Select it.
-        sUiBot.selectDataset("THE DUDE");
+        mUiBot.selectDataset("THE DUDE");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2753,12 +1981,12 @@
 
         // Assert request.
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
         assertValue(fillRequest1.structure, ID_USERNAME, "");
         assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
 
         // Select it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2781,12 +2009,12 @@
 
         // Assert request.
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
         assertValue(fillRequest2.structure, ID_USERNAME, "dude");
         assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
 
         // Select it.
-        sUiBot.selectDataset("THE DUDE");
+        mUiBot.selectDataset("THE DUDE");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2812,14 +2040,14 @@
                 // Trigger auto-fill.
                 mActivity.onUsername(View::requestFocus);
 
-                // Sanity check.
-                sUiBot.assertNoDatasets();
-
                 // Wait for onFill() before proceeding, otherwise the fields might be changed before
                 // the session started
                 waitUntilConnected();
                 sReplier.getNextFillRequest();
 
+                // Sanity check.
+                mUiBot.assertNoDatasetsEver();
+
                 // Set credentials...
                 mActivity.onUsername((v) -> v.setText(username));
                 mActivity.onPassword((v) -> v.setText(password));
@@ -2831,7 +2059,7 @@
                 mActivity.tapSave();
 
                 // Assert the snack bar is shown and tap "Save".
-                sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
                 final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
@@ -2844,7 +2072,6 @@
                 assertTextAndValue(passwordNode, password);
 
                 waitUntilDisconnected();
-                assertNoDanglingSessions();
             } catch (RetryableException e) {
                 throw new RetryableException(e, "on step %d", i);
             } catch (Throwable t) {
@@ -2870,25 +2097,24 @@
             mActivity.expectAutoFill(username, password);
             try {
                 // Trigger auto-fill.
-                mActivity.onUsername(View::requestFocus);
+                requestFocusOnUsername();
 
                 waitUntilConnected();
                 sReplier.getNextFillRequest();
 
                 // Auto-fill it.
-                sUiBot.selectDataset("The Dude");
+                mUiBot.selectDataset("The Dude");
 
                 // Check the results.
                 mActivity.assertAutoFilled();
 
                 // Change focus to prepare for next step - must do it before session is gone
-                mActivity.onPassword(View::requestFocus);
+                requestFocusOnPassword();
 
                 // Rinse and repeat...
                 mActivity.tapClear();
 
                 waitUntilDisconnected();
-                assertNoDanglingSessions();
             } catch (RetryableException e) {
                 throw e;
             } catch (Throwable t) {
@@ -2921,424 +2147,19 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
 
         // Click on the custom button
-        sUiBot.selectByText("Poke");
+        mUiBot.selectByText("Poke");
 
         // Make sure the click worked
-        sUiBot.selectByText("foo");
+        mUiBot.selectByText("foo");
 
         // Go back to the filled app.
-        sUiBot.pressBack();
-
-        // The session should be gone
-        assertNoDanglingSessions();
-    }
-
-    @Test
-    public void checkFillSelectionAfterSelectingDatasetAuthentication() throws Exception {
-        enableService();
-
-        // Set up FillResponse with dataset authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
-                        .build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setId("name")
-                        .setPresentation(createPresentation("authentication"))
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(clientState).build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Authenticate
-        sUiBot.selectDataset("authentication");
-        sReplier.getNextFillRequest();
-        mActivity.assertAutoFilled();
-
-        // Verify fill selection
-        FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                .getFillEventHistory();
-        assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
-                "clientStateValue");
-
-        assertThat(selection.getEvents().size()).isEqualTo(1);
-        FillEventHistory.Event event = selection.getEvents().get(0);
-        assertThat(event.getType()).isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED);
-        assertThat(event.getDatasetId()).isEqualTo("name");
-    }
-
-    @Test
-    public void checkFillSelectionAfterSelectingAuthentication() throws Exception {
-        enableService();
-
-        // Set up FillResponse with response wide authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "username")
-                                .setId("name")
-                                .setPresentation(createPresentation("dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
-                .setPresentation(createPresentation("authentication"))
-                .setAuthentication(authentication, ID_USERNAME)
-                .build());
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Authenticate
-        sUiBot.selectDataset("authentication");
-        sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("dataset");
-
-        // Verify fill selection
-        FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                .getFillEventHistory();
-        assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
-                "clientStateValue");
-
-        assertThat(selection.getEvents().size()).isEqualTo(1);
-        FillEventHistory.Event event = selection.getEvents().get(0);
-        assertThat(event.getType()).isEqualTo(TYPE_AUTHENTICATION_SELECTED);
-        assertThat(event.getDatasetId()).isNull();
-    }
-
-    @Test
-    public void checkFillSelectionAfterSelectingTwoDatasets() throws Exception {
-        enableService();
-
-        // Set up first partition with an anonymous dataset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-        sUiBot.selectDataset("dataset1");
-        sReplier.getNextFillRequest();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState()).isNull();
-
-            assertThat(selection.getEvents().size()).isEqualTo(1);
-            FillEventHistory.Event event = selection.getEvents().get(0);
-            assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event.getDatasetId()).isNull();
-        }
-
-        // Set up second partition with a named dataset
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password2")
-                                .setPresentation(createPresentation("dataset2"))
-                                .setId("name2")
-                                .build())
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password3")
-                                .setPresentation(createPresentation("dataset3"))
-                                .setId("name3")
-                                .build())
-                .setExtras(clientState)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
-        mActivity.expectPasswordAutoFill("password3");
-
-        // Trigger autofill on password
-        mActivity.onPassword(View::requestFocus);
-        sUiBot.selectDataset("dataset3");
-        sReplier.getNextFillRequest();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
-                    "clientStateValue");
-
-            assertThat(selection.getEvents().size()).isEqualTo(1);
-            FillEventHistory.Event event = selection.getEvents().get(0);
-            assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event.getDatasetId()).isEqualTo("name3");
-        }
-
-        mActivity.onPassword((v) -> v.setText("new password"));
-        mActivity.syncRunOnUiThread(() -> mActivity.finish());
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
-                    "clientStateValue");
-
-            assertThat(selection.getEvents().size()).isEqualTo(2);
-            FillEventHistory.Event event1 = selection.getEvents().get(0);
-            assertThat(event1.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event1.getDatasetId()).isEqualTo("name3");
-
-            FillEventHistory.Event event2 = selection.getEvents().get(1);
-            assertThat(event2.getType()).isEqualTo(TYPE_SAVE_SHOWN);
-            assertThat(event2.getDatasetId()).isNull();
-        }
-    }
-
-    @Test
-    public void checkFillSelectionIsResetAfterReturningNull() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        sUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState()).isNull();
-
-            assertThat(selection.getEvents().size()).isEqualTo(1);
-            FillEventHistory.Event event = selection.getEvents().get(0);
-            assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event.getDatasetId()).isNull();
-        }
-
-        // Second request
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection).isNull();
-        }
-    }
-
-    @Test
-    public void checkFillSelectionIsResetAfterReturningError() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        sUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState()).isNull();
-
-            assertThat(selection.getEvents().size()).isEqualTo(1);
-            FillEventHistory.Event event = selection.getEvents().get(0);
-            assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event.getDatasetId()).isNull();
-        }
-
-        // Second request
-        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection).isNull();
-        }
-    }
-
-    @Test
-    public void checkFillSelectionIsResetAfterTimeout() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        sUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection.getClientState()).isNull();
-
-            assertThat(selection.getEvents().size()).isEqualTo(1);
-            FillEventHistory.Event event = selection.getEvents().get(0);
-            assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
-            assertThat(event.getDatasetId()).isNull();
-        }
-
-        // Second request
-        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
-                    .getFillEventHistory();
-            assertThat(selection).isNull();
-        }
-    }
-
-    private Bundle getBundle(String key, String value) {
-        final Bundle bundle = new Bundle();
-        bundle.putString(key, value);
-        return bundle;
-    }
-
-    /**
-     * Tests the following scenario:
-     *
-     * <ol>
-     *    <li>Activity A is launched.
-     *    <li>Activity A triggers autofill.
-     *    <li>Activity B is launched.
-     *    <li>Activity B triggers autofill.
-     *    <li>User goes back to Activity A.
-     *    <li>User triggers save on Activity A - at this point, service should have stats of
-     *        activity B, and stats for activity A should have beeen discarded.
-     * </ol>
-     */
-    @Test
-    public void checkFillSelectionFromPreviousSessionIsDiscarded() throws Exception {
-        enableService();
-
-        // Launch activity A
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "A"))
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill on activity A
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity A
-        FillEventHistory selectionA = InstrumentedAutoFillService.peekInstance()
-                .getFillEventHistory();
-        assertThat(selectionA.getClientState().getString("activity")).isEqualTo("A");
-        assertThat(selectionA.getEvents()).isNull();
-
-        // Launch activity B
-        mContext.startActivity(new Intent(mContext, CheckoutActivity.class));
-
-        // Trigger autofill on activity B
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "B"))
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_CC_NUMBER, "4815162342")
-                        .setPresentation(createPresentation("datasetB"))
-                        .build())
-                .build());
-        sUiBot.focusByRelativeId(ID_CC_NUMBER);
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity B
-        final FillEventHistory selectionB = InstrumentedAutoFillService.peekInstance()
-                .getFillEventHistory();
-        assertThat(selectionB.getClientState().getString("activity")).isEqualTo("B");
-        assertThat(selectionB.getEvents()).isNull();
-
-        // Now switch back to A...
-        sUiBot.pressBack(); // dismiss keyboard
-        sUiBot.pressBack(); // dismiss task
-        sUiBot.assertShownByRelativeId(ID_USERNAME);
-        // ...and trigger save
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        sReplier.getNextSaveRequest();
-
-        // Finally, make sure history is right
-        final FillEventHistory finalSelection = InstrumentedAutoFillService.peekInstance()
-                .getFillEventHistory();
-        assertThat(finalSelection.getClientState().getString("activity")).isEqualTo("B");
-        assertThat(finalSelection.getEvents()).isNull();
-
+        mUiBot.pressBack();
     }
 
     @Test
@@ -3355,6 +2176,19 @@
     }
 
     @Test
+    public void testGetAutofillServiceComponentName() throws Exception {
+        final AutofillManager afm = mActivity.getAutofillManager();
+
+        enableService();
+        final ComponentName componentName = afm.getAutofillServiceComponentName();
+        assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
+        assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
+
+        disableService();
+        assertThat(afm.getAutofillServiceComponentName()).isNull();
+    }
+
+    @Test
     public void testSetupComplete() throws Exception {
         enableService();
 
@@ -3385,15 +2219,55 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("The Dude");
+        mUiBot.assertDatasets("The Dude");
 
         // Now disable service by setting another service
         Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
 
         // ...and make sure popup's gone
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
+    }
+
+    // TODO(b/70682223): add a new test to make sure service with BIND_AUTOFILL permission works
+    @Test
+    public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
+    }
+
+    @Test
+    public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
+    }
+
+    private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) 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 autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Now disable service by setting another service...
+        Helper.enableAutofillService(mContext, serviceName);
+
+        // ...and make sure popup's gone
+        mUiBot.assertNoDatasets();
+
+        // Then try to trigger autofill again...
+        mActivity.onPassword(View::requestFocus);
+        //...it should not work!
+        mUiBot.assertNoDatasetsEver();
     }
 
     @Test
@@ -3410,11 +2284,11 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -3461,13 +2335,13 @@
         sReplier.addResponse(response.build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // Make sure all datasets are shown.
         // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
         // shown
-        sUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
+        mUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
 
         // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
     }
@@ -3479,7 +2353,7 @@
 
         // Set expectations.
         final OneTimeCancellationSignalListener listener =
-                new OneTimeCancellationSignalListener(Helper.FILL_TIMEOUT_MS + 2000);
+                new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
         sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
 
         // Trigger auto-fill.
@@ -3493,4 +2367,27 @@
         waitUntilDisconnected();
         listener.assertOnCancelCalled();
     }
+
+    @Test
+    public void testNewTextAttributes() throws Exception {
+        enableService();
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+        assertThat(username.getMinTextEms()).isEqualTo(2);
+        assertThat(username.getMaxTextEms()).isEqualTo(5);
+        assertThat(username.getMaxTextLength()).isEqualTo(25);
+
+        final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
+        assertThat(container.getMinTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextLength()).isEqualTo(-1);
+
+        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+        assertThat(password.getMinTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextLength()).isEqualTo(-1);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
new file mode 100644
index 0000000..90c3e93
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+/**
+ * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
+ */
+public class LoginWithStringsActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_with_strings_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
new file mode 100644
index 0000000..73ab7d9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.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 android.autofillservice.cts;
+
+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.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.os.Bundle;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class LoginWithStringsActivityTest extends AutoFillServiceTestCase {
+
+    @Rule
+    public final AutofillActivityTestRule<LoginWithStringsActivity> mActivityRule =
+            new AutofillActivityTestRule<LoginWithStringsActivity>(LoginWithStringsActivity.class);
+
+    private LoginWithStringsActivity mActivity;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized.
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // Make sure labels were not sanitized
+        assertTextFromResouces(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Try to login, it will fail.
+        final String loginMessage = mActivity.tapLogin();
+
+        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+        // Set right password...
+        mActivity.onPassword((v) -> v.setText("dude"));
+
+        // ... and try again
+        final String expectedMessage = getWelcomeMessage("dude");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "dude");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(password, "dude");
+
+        // Make sure labels were not sanitized
+        assertTextFromResouces(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // 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");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
new file mode 100644
index 0000000..c6cd06a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.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.autofillservice.cts;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Empty activity that allows to be put in different split window.
+ */
+public class MultiWindowEmptyActivity extends EmptyActivity {
+
+    private static MultiWindowEmptyActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
+        sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS);
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
new file mode 100644
index 0000000..e0b3109
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.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.autofillservice.cts;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that allows capture recreated instance for testing multi window scenarios.
+ */
+public class MultiWindowLoginActivity extends LoginActivity {
+
+    private static MultiWindowLoginActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
+        sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS);
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
new file mode 100644
index 0000000..59ab7ce
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.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 android.autofillservice.cts;
+
+import static android.app.ActivityManager.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;
+import static android.autofillservice.cts.common.ShellHelper.tap;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.TimeoutException;
+
+public class MultiWindowLoginActivityTest extends AutoFillServiceTestCase {
+
+    @Rule
+    public final AutofillActivityTestRule<MultiWindowLoginActivity> mActivityRule =
+            new AutofillActivityTestRule<MultiWindowLoginActivity>(MultiWindowLoginActivity.class);
+
+    private LoginActivity mActivity;
+    protected ActivityManager mAm;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Before
+    public void setup() {
+        assumeTrue("Skipping test: no split multi-window support",
+                ActivityManager.supportsSplitScreenMultiWindow(mContext));
+        mAm = mContext.getSystemService(ActivityManager.class);
+    }
+
+    /**
+     * Touch a view and exepct autofill window change
+     */
+    protected void tapViewAndExpectWindowEvent(View view) throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> tap(view), Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+
+    protected String runAmStartActivity(Class<? extends Activity> activityClass, int flags) {
+        return runAmStartActivity(activityClass.getName(), flags);
+    }
+
+    protected String runAmStartActivity(String activity, int flags) {
+        return runShellCommand("am start %s/%s -f 0x%s", mPackageName, activity,
+                Integer.toHexString(flags));
+    }
+
+    /**
+     * Put activity1 in TOP, will be followed by amStartActivity()
+     */
+    protected void splitWindow(Activity activity1) {
+        mAm.setTaskWindowingModeSplitScreenPrimary(activity1.getTaskId(),
+                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, false, null, true);
+
+    }
+
+    protected void amStartActivity(Class<? extends Activity> activity2) {
+        // it doesn't work using startActivity(intent), have to go through shell command.
+        runAmStartActivity(activity2,
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+    }
+
+    @Test
+    public void testSplitWindow() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // split window and launch EmptyActivity, note that LoginActivity will be recreated.
+        MultiWindowLoginActivity.expectNewInstance(false);
+        MultiWindowEmptyActivity.expectNewInstance(true);
+
+        splitWindow(mActivity);
+        MultiWindowLoginActivity loginActivity = MultiWindowLoginActivity.waitNewInstance();
+
+        amStartActivity(MultiWindowEmptyActivity.class);
+        MultiWindowEmptyActivity emptyActivity = MultiWindowEmptyActivity.waitNewInstance();
+
+        // No dataset as LoginActivity loses window focus
+        mUiBot.assertNoDatasets();
+        // EmptyActivity will have window focus
+        assertThat(emptyActivity.hasWindowFocus()).isTrue();
+        // LoginActivity username field is still focused but window has no focus
+        assertThat(loginActivity.getUsername().hasFocus()).isTrue();
+        assertThat(loginActivity.hasWindowFocus()).isFalse();
+
+        // Make LoginActivity to regain window focus and fill ui is expected to show
+        tapViewAndExpectWindowEvent(loginActivity.getUsernameLabel());
+        mUiBot.assertDatasets("The Dude");
+        assertThat(emptyActivity.hasWindowFocus()).isFalse();
+
+        // Tap on EmptyActivity and fill ui is gone.
+        tapViewAndExpectWindowEvent(emptyActivity.getEmptyView());
+        mUiBot.assertNoDatasets();
+        assertThat(emptyActivity.hasWindowFocus()).isTrue();
+        // LoginActivity username field is still focused but window has no focus
+        assertThat(loginActivity.getUsername().hasFocus()).isTrue();
+        assertThat(loginActivity.hasWindowFocus()).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java
deleted file mode 100644
index 52c3bcc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java
+++ /dev/null
@@ -1,91 +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.autofillservice.cts;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper used to catch multiple exceptions that might have happened in a test case.
- */
-// TODO: move to common CTS code (and add test cases to it)
-public final class MultipleExceptionsCatcher {
-
-    private static final String TAG = "MultipleExceptionsCatcher";
-
-    private final List<Throwable> mThrowables = new ArrayList<>();
-
-    /**
-     * Runs {@code r} postponing any thrown exception to {@link #throwIfAny()}.
-     */
-    public MultipleExceptionsCatcher run(@NonNull Runnable r) {
-        try {
-            r.run();
-        } catch (Throwable t) {
-            mThrowables.add(t);
-        }
-        return this;
-    }
-
-    /**
-     * Adds an exception - if it's not {@code null} to the exceptions thrown by
-     * {@link #throwIfAny()}.
-     */
-    public MultipleExceptionsCatcher add(@Nullable Throwable t) {
-        if (t != null) {
-            mThrowables.add(t);
-        }
-        return this;
-    }
-
-    /**
-     * Throws one exception merging all exceptions thrown or added so far, if any.
-     */
-    public void throwIfAny() throws Throwable {
-        if (mThrowables.isEmpty()) return;
-
-        final int numberExceptions = mThrowables.size();
-        if (numberExceptions == 1) {
-            throw mThrowables.get(0);
-        }
-
-        String msg = "D'OH!";
-        try {
-            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
-                sw.write("Caught " + numberExceptions + " exceptions\n");
-                for (int i = 0; i < numberExceptions; i++) {
-                    sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
-                    final Throwable exception = mThrowables.get(i);
-                    exception.printStackTrace(pw);
-                    sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
-                }
-                msg = sw.toString();
-            }
-        } catch (IOException e) {
-            // ignore close() errors - should not happen...
-            Log.e(TAG, "Exception closing StringWriter: " + e);
-        }
-        throw new AssertionError(msg);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
index 3e30b9b..8da4add 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
@@ -18,8 +18,6 @@
 
 import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
 import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.eventually;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
@@ -70,18 +68,12 @@
                 new InstrumentedAutoFillService.FillRequest[1];
 
         // Trigger autofill
-        eventually(() -> {
-            mActivity.syncRunOnUiThread(() -> {
-                mEditText2.requestFocus();
-                mEditText1.requestFocus();
-            });
+        mActivity.syncRunOnUiThread(() -> {
+            mEditText2.requestFocus();
+            mEditText1.requestFocus();
+        });
 
-            try {
-                fillRequest[0] = sReplier.getNextFillRequest();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }, (int) (FILL_TIMEOUT_MS * 2));
+        fillRequest[0] = sReplier.getNextFillRequest();
 
         assertThat(fillRequest[0].data).isNull();
 
@@ -94,8 +86,8 @@
         assertThat(findNodeByResourceId(structure, "editText5")).isNull();
 
         // Wait until autofill has been applied
-        sUiBot.selectDataset("dataset1");
-        sUiBot.assertShownByText("editText1-autofilled");
+        mUiBot.selectDataset("dataset1");
+        mUiBot.assertShownByText("editText1-autofilled");
 
         // Manually fill view
         mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
@@ -153,16 +145,16 @@
         assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
 
         // Wait until autofill has been applied
-        sUiBot.selectDataset("dataset2");
-        sUiBot.assertShownByText("editText3-autofilled");
-        sUiBot.assertShownByText("editText4-autofilled");
+        mUiBot.selectDataset("dataset2");
+        mUiBot.assertShownByText("editText3-autofilled");
+        mUiBot.assertShownByText("editText4-autofilled");
 
         // Manually fill view
         mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
 
         // Finish activity and save data
         mActivity.finish();
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        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();
@@ -229,7 +221,7 @@
 
         // Check UI is shown, but don't select it.
         sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("dataset1");
+        mUiBot.assertDatasets("dataset1");
 
         // Switch fragments
         sReplier.addResponse(NO_RESPONSE);
@@ -239,7 +231,7 @@
                         FRAGMENT_TAG).commitNow());
         // Make sure UI is gone.
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
     }
 
     // TODO: add similar tests for fragment with virtual view
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
index b264a46..5af2762 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -26,7 +26,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * Custom {@link RadioGroup.OnCheckedChangeListener} used to assert an
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
  * {@link RadioGroup} was auto-filled properly.
  */
 final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
@@ -49,8 +49,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT_MS, mName)
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
             .that(set).isTrue();
         final int actual = mRadioGroup.getAutofillValue().getListValue();
         assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
index c928f31..c3a4f60 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -62,9 +62,10 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on EditText %s", FILL_TIMEOUT_MS, mName)
-                .that(set).isTrue();
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (!set) {
+            throw new RetryableException(FILL_TIMEOUT, "Timeout (%s ms) on EditText %s", mName);
+        }
         final String actual = mEditText.getText().toString();
         assertWithMessage("Wrong auto-fill value on EditText %s", mName)
                 .that(actual).isEqualTo(mExpected.toString());
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
index 1aed119..2519aec 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -50,8 +50,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT_MS, name)
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
                 .that(set).isTrue();
         assertWithMessage("Wrong hour on TimePicker %s", name)
                 .that(timePicker.getHour()).isEqualTo(expectedHour);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
new file mode 100644
index 0000000..65cef72
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.findNodeByAutofillId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.GridActivity.FillExpectation;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.service.autofill.FillContext;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+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 {
+
+    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();
+
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AutofillId oldIdField1 = field1.getAutofillId();
+        final EditText field2 = mActivity.getCell(1, 2);
+        final AutofillId idField2 = field2.getAutofillId();
+
+        // Prepare response
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(oldIdField1, "l1c1", createPresentation("l1c1"))
+                        .setField(idField2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger autofill on 1st view.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        final ViewNode node1Request1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertThat(node1Request1.getAutofillId()).isEqualTo(oldIdField1);
+        mUiBot.assertDatasets("l1c1");
+
+        // Make sure 2nd field shows picker
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
+
+        // Now change id of 1st view
+        final AutofillId newIdField1 = mActivity.getAutofillManager().getNextAutofillId();
+
+        // TODO: move to an autofill unit test class for View
+        // Make sure view has to be detached first
+        assertThrows(IllegalStateException.class, () -> field1.setAutofillId(newIdField1));
+
+        // Change id
+        mActivity.removeCell(1, 1);
+
+        // TODO: move to an autofill unit test class for View
+        // Also assert it does not accept virtual ids
+        assertThrows(IllegalStateException.class,
+                () -> field1.setAutofillId(new AutofillId(newIdField1, 42)));
+
+        field1.setAutofillId(newIdField1);
+        assertThat(field1.getAutofillId()).isEqualTo(newIdField1);
+
+        Log.d(TAG, "Changed id of " + ID_L1C1 + " from " + oldIdField1 + " to " + newIdField1);
+
+        // Trigger another request because 1st view has a different id. Service will ignore it now.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(idField2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Re-add the cell before triggering autofill on it
+        mActivity.addCell(1, 1, field1);
+        mActivity.focusCell(1, 1);
+
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        final ViewNode node1Request2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
+        // Make sure node has new id.
+        assertThat(node1Request2.getAutofillId()).isEqualTo(newIdField1);
+        mUiBot.assertNoDatasets();
+
+        // Make sure 2nd field shows picker
+        focusCell(1, 2);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("l1c2");
+
+        // Now autofill
+        final FillExpectation expectation = mActivity.expectAutofill()
+                .onCell(1, 2, "l1c2");
+        mUiBot.selectDataset(datasetPicker, "l1c2");
+        expectation.assertAutoFilled();
+    }
+
+    @Test
+    public void testSave_serviceIgnoresNewId() throws Exception {
+        saveWhenIdChanged(true);
+    }
+
+    @Test
+    public void testSave_serviceExpectingOldId() throws Exception {
+        saveWhenIdChanged(false);
+    }
+
+    private void saveWhenIdChanged(boolean serviceIgnoresNewId) throws Exception {
+        enableService();
+
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AutofillId oldIdField1 = field1.getAutofillId();
+        final EditText field2 = mActivity.getCell(1, 2);
+        final AutofillId idField2 = field2.getAutofillId();
+
+        // Prepare response
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .setRequiredSavableAutofillIds(SAVE_DATA_TYPE_GENERIC, oldIdField1, idField2)
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger autofill on 1st view.
+        mActivity.focusCell(1, 1); // No window change because it's not showing dataset picker.
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        final ViewNode node1Request1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertThat(node1Request1.getAutofillId()).isEqualTo(oldIdField1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure 2nd field doesn't trigger a new request
+        mActivity.focusCell(1, 2); // No window change because it's not showing dataset picker.
+        mUiBot.assertNoDatasetsEver();
+
+        // Now change 1st view value...
+        mActivity.setText(1, 1, "OLD");
+        // ...and its id
+        final AutofillId newIdField1 = mActivity.getAutofillManager().getNextAutofillId();
+        mActivity.removeCell(1, 1);
+        field1.setAutofillId(newIdField1);
+        assertThat(field1.getAutofillId()).isEqualTo(newIdField1);
+        Log.d(TAG, "Changed id of " + ID_L1C1 + " from " + oldIdField1 + " to " + newIdField1);
+
+        // Trigger another request because 1st view has a different id...
+        final CannedFillResponse.Builder response2 = new CannedFillResponse.Builder();
+        if (serviceIgnoresNewId) {
+            // ... and service will ignore it now.
+            response2.setRequiredSavableAutofillIds(SAVE_DATA_TYPE_GENERIC, idField2);
+        } else {
+            // ..but service is still expecting the old id.
+            response2.setRequiredSavableAutofillIds(SAVE_DATA_TYPE_GENERIC, oldIdField1, idField2);
+        }
+        sReplier.addResponse(response2.build());
+
+        mActivity.addCell(1, 1, field1);
+        mActivity.focusCell(1, 1); // No window change because it's not showing dataset picker.
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        final ViewNode node1Request2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
+        // Make sure node has new id.
+        assertThat(node1Request2.getAutofillId()).isEqualTo(newIdField1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now triggers save
+        mActivity.setText(1, 1, "NEW");
+        mActivity.setText(1, 2, "NOD2");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final List<FillContext> contexts = saveRequest.contexts;
+        assertThat(contexts).hasSize(2);
+
+        // Assert 1st context
+        final AssistStructure structure1 = contexts.get(0).getStructure();
+
+        final ViewNode oldNode1Context1 = findNodeByAutofillId(structure1, oldIdField1);
+        assertThat(oldNode1Context1).isNotNull();
+        assertThat(oldNode1Context1.getIdEntry()).isEqualTo(ID_L1C1);
+        assertThat(oldNode1Context1.getText().toString()).isEqualTo("OLD");
+
+        final ViewNode newNode1Context1 = findNodeByAutofillId(structure1, newIdField1);
+        assertThat(newNode1Context1).isNull();
+
+        final ViewNode node2Context1 = findNodeByAutofillId(structure1, idField2);
+        assertThat(node2Context1).isNotNull();
+        assertThat(node2Context1.getIdEntry()).isEqualTo(ID_L1C2);
+        assertThat(node2Context1.getText().toString()).isEqualTo("NOD2");
+
+        // Assert 2nd context
+        final AssistStructure structure2 = contexts.get(1).getStructure();
+
+        final ViewNode oldNode1Context2 = findNodeByAutofillId(structure2, oldIdField1);
+        assertThat(oldNode1Context2).isNull();
+
+        final ViewNode newNode1Context2 = findNodeByAutofillId(structure2, newIdField1);
+        assertThat(newNode1Context2).isNotNull();
+        assertThat(newNode1Context2.getIdEntry()).isEqualTo(ID_L1C1);
+        assertThat(newNode1Context2.getText().toString()).isEqualTo("NEW");
+
+        final ViewNode node2Context2 = findNodeByAutofillId(structure2, idField2);
+        assertThat(node2Context2).isNotNull();
+        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 1ba8755..2108a4b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
@@ -16,10 +16,12 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
+import static android.autofillservice.cts.Helper.callbackEventAsString;
+import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.util.Log;
 import android.view.View;
 import android.view.autofill.AutofillManager.AutofillCallback;
 
@@ -32,15 +34,21 @@
  */
 final class MyAutofillCallback extends AutofillCallback {
 
+    private static final String TAG = "MyAutofillCallback";
     private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
 
+    public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
+
     @Override
     public void onAutofillEvent(View view, int event) {
+        Log.v(TAG, "onAutofillEvent: view=" + view + ", event=" + callbackEventAsString(event));
         mEvents.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));
     }
 
@@ -48,18 +56,30 @@
      * Gets the next available event or fail if it times out.
      */
     MyEvent getEvent() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
         if (event == null) {
-            throw new RetryableException("no event in %d ms", CONNECTION_TIMEOUT_MS);
+            throw new RetryableException(CONNECTION_TIMEOUT, "no event");
         }
         return event;
     }
 
     /**
+     * Assert no more events were received.
+     */
+    void assertNotCalled() throws InterruptedException {
+        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (event != null) {
+            // Not retryable.
+            throw new IllegalStateException("should not have received " + event);
+        }
+    }
+
+    /**
      * Used to assert there is no event left behind.
      */
     void assertNumberUnhandledEvents(int expected) {
-        assertWithMessage("Invalid number of events left").that(mEvents.size()).isEqualTo(expected);
+        assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size())
+                .isEqualTo(expected);
     }
 
     /**
@@ -153,7 +173,7 @@
 
         @Override
         public String toString() {
-            return event + ": " + view + " (childId: " + childId + ")";
+            return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")";
         }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
new file mode 100644
index 0000000..9cddac6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.autofill.AutofillValue;
+import android.webkit.WebView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link WebView} used to assert contents were autofilled.
+ */
+public class MyWebView extends WebView {
+
+    private FillExpectation mExpectation;
+
+    public MyWebView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void expectAutofill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+    }
+
+    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);
+    }
+
+    @Override
+    public void autofill(SparseArray<AutofillValue> values) {
+        super.autofill(values);
+
+        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();
+        }
+    }
+
+    private class FillExpectation {
+        private final CountDownLatch mLatch = 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;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
index 4255b6d..a75ded8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
@@ -21,21 +21,26 @@
 import android.service.autofill.FillRequest;
 import android.service.autofill.SaveCallback;
 import android.service.autofill.SaveRequest;
+import android.util.Log;
 
 /**
  * {@link AutofillService} implementation that does not do anything...
  */
 public class NoOpAutofillService extends AutofillService {
 
+    private static final String TAG = "NoOpAutofillService";
+
     static final String SERVICE_NAME = NoOpAutofillService.class.getPackage().getName()
             + "/." + NoOpAutofillService.class.getSimpleName();
 
     @Override
     public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
             FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
     }
 
     @Override
     public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onFillResponse()");
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
index 071dec6..4d7af94 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -48,8 +48,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT_MS, name)
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
             .that(set).isTrue();
         final boolean actual = button.isChecked();
         assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
index ef28a23..407861d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -51,8 +51,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT_MS, name)
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
             .that(set).isTrue();
         assertWithMessage("Wrong year on DatePicker %s", name)
             .that(datePicker.getYear()).isEqualTo(expectedYear);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
index 1903cb9..73ed648 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -47,8 +47,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT_MS, name)
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
             .that(set).isTrue();
         final int actual = radioGroup.getCheckedRadioButtonId();
         assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java
deleted file mode 100644
index e723357..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package android.autofillservice.cts;
-
-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.provider.Settings;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a secure settings value has been updated.
- */
-final class OneTimeSettingsListener extends ContentObserver {
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-    private final ContentResolver mResolver;
-    private final String mKey;
-
-    public OneTimeSettingsListener(Context context, String key) {
-        super(new Handler(Looper.getMainLooper()));
-        mKey = key;
-        mResolver = context.getContentResolver();
-        mResolver.registerContentObserver(Settings.Secure.getUriFor(key), false, this);
-    }
-
-    @Override
-    public void onChange(boolean selfChange, Uri uri) {
-        mResolver.unregisterContentObserver(this);
-        mLatch.countDown();
-    }
-
-    /**
-     * Blocks for a few seconds until it's called.
-     *
-     * @throws IllegalStateException if it's not called.
-     */
-    void assertCalled() {
-        try {
-            final boolean updated = mLatch.await(5, TimeUnit.SECONDS);
-            if (!updated) {
-                throw new IllegalStateException("Settings " + mKey + " not called in 5s");
-            }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException("Interrupted", e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
index 6bc8279..5fb5973 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -44,8 +44,8 @@
     }
 
     void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT_MS, name)
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
             .that(set).isTrue();
         final int actual = spinner.getSelectedItemPosition();
         assertWithMessage("Wrong auto-fill value on Spinner %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
index c82fa42..d726c9e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
@@ -15,7 +15,6 @@
  */
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.OptionalSaveActivity.ID_ADDRESS1;
@@ -32,7 +31,6 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -64,11 +62,6 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     /**
      * Creates a standard builder common to all tests.
      */
@@ -122,7 +115,7 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started.
@@ -135,16 +128,13 @@
         mActivity.save();
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
 
         // Assert value of fields
         assertions.visit(saveRequest.structure);
-
-        // Once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -182,7 +172,7 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started.
@@ -195,10 +185,7 @@
         mActivity.save();
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-
-        // Once saved, the session should be finsihed.
-        assertNoDanglingSessions();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
     }
 
     @Test
@@ -320,7 +307,7 @@
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.selectDataset("Da Dataset");
+        mUiBot.selectDataset("Da Dataset");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -332,16 +319,13 @@
         mActivity.save();
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
 
         // Assert value of fields
         assertions.visit(saveRequest.structure);
-
-        // Once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -419,7 +403,7 @@
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.selectDataset("Da Dataset");
+        mUiBot.selectDataset("Da Dataset");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -431,10 +415,7 @@
         mActivity.save();
 
         // Assert the snack bar is not shown.
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-
-        // Once saved, the session should be finsihed.
-        assertNoDanglingSessions();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
     }
 
     @Test
@@ -585,16 +566,16 @@
 
         // Make sure the snack bar is not shown.
         if (expectSaveUi) {
-            sUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
         } else {
-            sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
         }
 
         // ...then tap save.
         mActivity.save();
 
         // Assert the snack bar is not shown.
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
     }
 
     @Test
@@ -621,7 +602,7 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
         sReplier.getNextFillRequest();
 
-        sUiBot.selectDataset("SF");
+        mUiBot.selectDataset("SF");
         mActivity.assertAutoFilled();
 
         // Clear the field.
@@ -631,7 +612,7 @@
         mActivity.save();
 
         // ...and make sure the snack bar is not shown.
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
     }
 
     @Test
@@ -658,7 +639,7 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
         sReplier.getNextFillRequest();
 
-        sUiBot.selectDataset("SF");
+        mUiBot.selectDataset("SF");
         mActivity.assertAutoFilled();
 
         // Clear the field.
@@ -668,7 +649,7 @@
         mActivity.save();
 
         // ...and make sure the snack bar is shown.
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
 
         // Finally, assert values.
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
index 83d1ce2..8fecba0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
@@ -39,9 +39,20 @@
 
         setContentView(R.layout.login_activity);
 
-        findViewById(R.id.login).setOnClickListener((v) -> {
-            finish();
-        });
+        findViewById(R.id.login).setOnClickListener((v) -> finish());
+    }
+
+    @Override
+    protected void onStart() {
+        Log.i(LOG_TAG, "onStart()");
+        super.onStart();
+        try {
+            if (!getStartedMarker(this).createNewFile()) {
+                Log.e(LOG_TAG, "cannot write started file");
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "cannot write started file");
+        }
     }
 
     @Override
@@ -71,4 +82,14 @@
     @NonNull public static File getStoppedMarker(@NonNull Context context) {
         return new File(context.getFilesDir(), "stopped");
     }
+
+    /**
+     * Get the file that signals that the activity has entered {@link Activity#onStart()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onStart()
+     */
+    @NonNull public static File getStartedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "started");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
index d458940..8023bf4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
@@ -23,6 +23,8 @@
 import static android.autofillservice.cts.GridActivity.ID_L3C2;
 import static android.autofillservice.cts.GridActivity.ID_L4C1;
 import static android.autofillservice.cts.GridActivity.ID_L4C2;
+import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.Helper.assertHasFlags;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.assertValue;
 import static android.autofillservice.cts.Helper.getContext;
@@ -46,12 +48,13 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.service.autofill.FillResponse;
-import android.widget.RemoteViews;
 
 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.
  */
@@ -68,6 +71,30 @@
         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));
+    }
+
     @Test
     public void testAutofillTwoPartitionsSkipFirst() throws Exception {
         // Set service.
@@ -83,7 +110,7 @@
         sReplier.addResponse(response1);
 
         // Trigger auto-fill on 1st partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
         assertThat(fillRequest1.flags).isEqualTo(0);
         final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
@@ -92,9 +119,9 @@
         assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
 
         // Make sure UI is shown, but don't tap it.
-        sUiBot.assertDatasets("l1c1");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("l1c2");
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
 
         // Now tap a field in a different partition
         final CannedFillResponse response2 = new CannedFillResponse.Builder()
@@ -106,7 +133,7 @@
         sReplier.addResponse(response2);
 
         // Trigger auto-fill on 2nd partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
         assertThat(fillRequest2.flags).isEqualTo(0);
         final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
@@ -118,16 +145,16 @@
         assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
         assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
         // Make sure UI is shown, but don't tap it.
-        sUiBot.assertDatasets("l2c1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("l2c2");
+        mUiBot.assertDatasets("l2c1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("l2c2");
 
         // Now fill them
         final FillExpectation expectation1 = mActivity.expectAutofill()
               .onCell(1, 1, "l1c1")
               .onCell(1, 2, "l1c2");
-        mActivity.focusCell(1, 1);
-        sUiBot.selectDataset("l1c1");
+        focusCell(1, 1);
+        mUiBot.selectDataset("l1c1");
         expectation1.assertAutoFilled();
 
         // Change previous values to make sure they are not filled again
@@ -137,8 +164,8 @@
         final FillExpectation expectation2 = mActivity.expectAutofill()
                 .onCell(2, 1, "l2c1")
                 .onCell(2, 2, "l2c2");
-        mActivity.focusCell(2, 2);
-        sUiBot.selectDataset("l2c2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("l2c2");
         expectation2.assertAutoFilled();
 
         // Make sure previous partition didn't change
@@ -166,7 +193,7 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger auto-fill.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
         assertThat(fillRequest1.flags).isEqualTo(0);
 
@@ -174,7 +201,7 @@
         assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 1");
+        mUiBot.selectDataset("Partition 1");
 
         // Check the results.
         expectation1.assertAutoFilled();
@@ -194,7 +221,7 @@
                 .onCell(2, 2, "l2c2");
 
         // Trigger auto-fill.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
         assertThat(fillRequest2.flags).isEqualTo(0);
 
@@ -204,7 +231,7 @@
         assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 2");
+        mUiBot.selectDataset("Partition 2");
 
         // Check the results.
         expectation2.assertAutoFilled();
@@ -253,7 +280,7 @@
         assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 1");
+        mUiBot.selectDataset("Partition 1");
 
         // Check the results.
         expectation1.assertAutoFilled();
@@ -287,7 +314,7 @@
         assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 2");
+        mUiBot.selectDataset("Partition 2");
 
         // Check the results.
         expectation2.assertAutoFilled();
@@ -323,7 +350,7 @@
         assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 3");
+        mUiBot.selectDataset("Partition 3");
 
         // Check the results.
         expectation3.assertAutoFilled();
@@ -361,7 +388,7 @@
         assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 4");
+        mUiBot.selectDataset("Partition 4");
 
         // Check the results.
         expectation4.assertAutoFilled();
@@ -387,7 +414,7 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger auto-fill.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
         assertThat(fillRequest1.flags).isEqualTo(0);
 
@@ -395,7 +422,7 @@
         assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 1");
+        mUiBot.selectDataset("Partition 1");
 
         // Check the results.
         expectation1.assertAutoFilled();
@@ -420,7 +447,7 @@
         // Trigger auto-fill.
         mActivity.forceAutofill(2, 1);
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
 
         assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
         assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
@@ -428,7 +455,7 @@
         assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 2");
+        mUiBot.selectDataset("Partition 2");
 
         // Check the results.
         expectation2.assertAutoFilled();
@@ -448,7 +475,7 @@
                 .onCell(3, 2, "l3c2");
 
         // Trigger auto-fill.
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         final FillRequest fillRequest3 = sReplier.getNextFillRequest();
         assertThat(fillRequest3.flags).isEqualTo(0);
 
@@ -460,7 +487,7 @@
         assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 3");
+        mUiBot.selectDataset("Partition 3");
 
         // Check the results.
         expectation3.assertAutoFilled();
@@ -484,7 +511,7 @@
         // Trigger auto-fill.
         mActivity.forceAutofill(4, 1);
         final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-        assertThat(fillRequest4.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+        assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
 
         assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
         assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
@@ -496,7 +523,7 @@
         assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
 
         // Auto-fill it.
-        sUiBot.selectDataset("Partition 4");
+        mUiBot.selectDataset("Partition 4");
 
         // Check the results.
         expectation4.assertAutoFilled();
@@ -521,11 +548,11 @@
         sReplier.addResponse(response1);
 
         // Trigger auto-fill on 1st partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
         assertThat(fillRequest1.flags).isEqualTo(0);
         assertThat(fillRequest1.data).isNull();
-        sUiBot.assertDatasets("l1c1");
+        mUiBot.assertDatasets("l1c1");
 
         // Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
         extras.clear();
@@ -542,7 +569,7 @@
         sReplier.addResponse(response2);
 
         // Trigger auto-fill on 2nd partition
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
         assertThat(fillRequest2.flags).isEqualTo(0);
         assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
@@ -561,7 +588,7 @@
         sReplier.addResponse(response3);
 
         // Trigger auto-fill on 3rd partition
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         final FillRequest fillRequest3 = sReplier.getNextFillRequest();
         assertThat(fillRequest3.flags).isEqualTo(0);
         assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
@@ -586,7 +613,7 @@
         sReplier.addResponse(response4);
 
         // Trigger auto-fill on 4th partition
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         final FillRequest fillRequest4 = sReplier.getNextFillRequest();
         assertThat(fillRequest4.flags).isEqualTo(0);
         assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
@@ -595,7 +622,7 @@
         mActivity.setText(1, 1, "L1C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
         assertWithMessage("wrong number of extras on save request bundle")
@@ -616,7 +643,7 @@
                         .build())
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -628,14 +655,14 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger save
         mActivity.setText(2, 1, "L2C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L2C1, "L2C1");
     }
@@ -653,7 +680,7 @@
                         .build())
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -665,14 +692,14 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger save
         mActivity.setText(1, 1, "L1C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L1C1, "L1C1");
     }
@@ -691,7 +718,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -704,7 +731,7 @@
                         ID_L2C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger save
@@ -712,7 +739,7 @@
         mActivity.setText(2, 1, "L2C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L1C1, "L1C1");
         assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -732,7 +759,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -745,7 +772,7 @@
                         ID_L2C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 3rd partition.
@@ -758,7 +785,7 @@
                         | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
                 .build();
         sReplier.addResponse(response3);
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
         // Trigger save
@@ -767,7 +794,7 @@
         mActivity.setText(3, 1, "L3C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
                 SAVE_DATA_TYPE_USERNAME);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L1C1, "L1C1");
@@ -789,7 +816,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -801,7 +828,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 3rd partition.
@@ -815,7 +842,7 @@
                         ID_L3C1)
                 .build();
         sReplier.addResponse(response3);
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
 
@@ -826,7 +853,7 @@
         mActivity.save();
 
         // Make sure GENERIC type is not shown on snackbar
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L1C1, "L1C1");
         assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -847,7 +874,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
                 .build();
         sReplier.addResponse(response1);
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 2nd partition.
@@ -860,7 +887,7 @@
                         ID_L2C1)
                 .build();
         sReplier.addResponse(response2);
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 3rd partition.
@@ -873,7 +900,7 @@
                         | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
                 .build();
         sReplier.addResponse(response3);
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
         // Trigger 4th partition.
@@ -886,7 +913,7 @@
                         | SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
                 .build();
         sReplier.addResponse(response4);
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
 
@@ -897,7 +924,7 @@
         mActivity.setText(4, 1, "L4C1");
         mActivity.save();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertValue(saveRequest.structure, ID_L1C1, "L1C1");
         assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -921,7 +948,7 @@
         sReplier.addResponse(response1);
 
         // Trigger auto-fill on 1st partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
         assertThat(fillRequest1.flags).isEqualTo(0);
         final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
@@ -930,15 +957,15 @@
         assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
 
         // Make sure UI is shown on 1st partition
-        sUiBot.assertDatasets("l1c1");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("l1c2");
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
 
         // Make sure UI is not shown on ignored partition
-        mActivity.focusCell(2, 1);
-        sUiBot.assertNoDatasets();
-        mActivity.focusCell(2, 2);
-        sUiBot.assertNoDatasets();
+        focusCell(2, 1);
+        mUiBot.assertNoDatasets();
+        focusCellNoWindowChange(2, 2);
+        mUiBot.assertNoDatasetsEver();
     }
 
     /**
@@ -971,7 +998,7 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
 
@@ -995,7 +1022,7 @@
                 .onCell(2, 2, "L2C2");
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1020,7 +1047,7 @@
                 .onCell(3, 2, "L3C2");
 
         // Trigger partition.
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1043,49 +1070,49 @@
                 .onCell(4, 1, "l4c1");
 
         // Trigger partition.
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         /*
          *  Now move focus around to make sure the proper values are displayed each time.
          */
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         /*
          *  Finally, autofill and check results.
          */
-        mActivity.focusCell(4, 1);
-        sUiBot.selectDataset("P4D1");
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D1");
         expectation4.assertAutoFilled();
 
-        mActivity.focusCell(1, 1);
-        sUiBot.selectDataset("P1D1");
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
         expectation1.assertAutoFilled();
 
-        mActivity.focusCell(3, 1);
-        sUiBot.selectDataset("P3D2");
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D2");
         expectation3.assertAutoFilled();
 
-        mActivity.focusCell(2, 2);
-        sUiBot.selectDataset("P2D2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
         expectation2.assertAutoFilled();
     }
 
@@ -1141,14 +1168,13 @@
         sReplier.addResponse(response1);
 
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
         /**
          * 2nd partition.
@@ -1169,18 +1195,18 @@
         sReplier.addResponse(response2);
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P2D1"); // changed
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
         /**
          * 3rd partition.
@@ -1203,22 +1229,22 @@
         sReplier.addResponse(response3);
 
         // Trigger partition.
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P3D1"); // changed
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P3D2"); // changed
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         /**
          * 4th partition.
@@ -1251,26 +1277,26 @@
         sReplier.addResponse(response4);
 
         // Trigger partition.
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
         /*
          * Finally, autofill and check results.
@@ -1300,8 +1326,8 @@
             chosenOne = "P4D2";
         }
 
-        mActivity.focusCell(4, 1);
-        sUiBot.selectDataset(chosenOne);
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
         expectation.assertAutoFilled();
     }
 
@@ -1310,10 +1336,6 @@
         // Set service.
         enableService();
 
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
         /**
          * 1st partition.
          */
@@ -1322,19 +1344,18 @@
                 new CannedDataset.Builder()
                         .setField(ID_L1C1, "l1c1")
                         .setField(ID_L1C2, "l1c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
         final CannedFillResponse response1 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth11)
-                        .setField(ID_L1C1, bogusValue)
-                        .setField(ID_L1C2, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D1"))
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth12)
-                        .setField(ID_L1C1, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D2"))
                         .build())
                 .build();
@@ -1344,16 +1365,16 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Focus around different fields in the partition.
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
         // Autofill it...
-        sUiBot.selectDataset("P1D1");
+        mUiBot.selectDataset("P1D1");
         // ... and assert result
         expectation1.assertAutoFilled();
 
@@ -1365,19 +1386,18 @@
         final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
                 new CannedDataset.Builder()
                     .setField(ID_L2C2, "L2C2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response2 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth21)
                         .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, bogusValue)
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth22)
                         .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response2);
@@ -1385,16 +1405,16 @@
                 .onCell(2, 2, "L2C2");
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Focus around different fields in the partition.
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
         // Autofill it...
-        sUiBot.selectDataset("P2D2");
+        mUiBot.selectDataset("P2D2");
         // ... and assert result
         expectation2.assertAutoFilled();
 
@@ -1406,21 +1426,20 @@
                 new CannedDataset.Builder()
                         .setField(ID_L3C1, "l3c1")
                         .setField(ID_L3C2, "l3c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
         final CannedFillResponse response3 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth31)
                         .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth32)
                         .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response3);
@@ -1429,16 +1448,16 @@
                 .onCell(3, 2, "l3c2");
 
         // Trigger partition.
-        mActivity.focusCell(3, 2);
+        focusCell(3, 2);
         sReplier.getNextFillRequest();
 
         // Focus around different fields in the partition.
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         // Autofill it...
-        sUiBot.selectDataset("P3D1");
+        mUiBot.selectDataset("P3D1");
         // ... and assert result
         expectation3.assertAutoFilled();
 
@@ -1451,19 +1470,18 @@
                 new CannedDataset.Builder()
                     .setField(ID_L4C1, "L4C1")
                     .setField(ID_L4C2, "L4C2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response4 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth41)
                         .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, bogusValue)
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth42)
                         .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, bogusValue)
-                        .setField(ID_L4C2, bogusValue)
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response4);
@@ -1472,16 +1490,16 @@
                 .onCell(4, 2, "L4C2");
 
         // Trigger partition.
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         // Focus around different fields in the partition.
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
         // Autofill it...
-        sUiBot.selectDataset("P4D2");
+        mUiBot.selectDataset("P4D2");
         // ... and assert result
         expectation4.assertAutoFilled();
     }
@@ -1496,10 +1514,6 @@
         // Set service.
         enableService();
 
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
         /**
          * 1st partition.
          */
@@ -1508,19 +1522,18 @@
                 new CannedDataset.Builder()
                         .setField(ID_L1C1, "l1c1")
                         .setField(ID_L1C2, "l1c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
         final CannedFillResponse response1 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth11)
-                        .setField(ID_L1C1, bogusValue)
-                        .setField(ID_L1C2, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D1"))
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth12)
-                        .setField(ID_L1C1, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D2"))
                         .build())
                 .build();
@@ -1530,7 +1543,7 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1541,19 +1554,18 @@
         final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
                 new CannedDataset.Builder()
                     .setField(ID_L2C2, "L2C2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response2 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth21)
                         .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, bogusValue)
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth22)
                         .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response2);
@@ -1561,7 +1573,7 @@
                 .onCell(2, 2, "L2C2");
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1572,21 +1584,20 @@
                 new CannedDataset.Builder()
                         .setField(ID_L3C1, "l3c1")
                         .setField(ID_L3C2, "l3c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
         final CannedFillResponse response3 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth31)
                         .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth32)
                         .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response3);
@@ -1595,7 +1606,7 @@
                 .onCell(3, 2, "l3c2");
 
         // Trigger partition.
-        mActivity.focusCell(3, 2);
+        focusCell(3, 2);
         sReplier.getNextFillRequest();
 
         /**
@@ -1607,19 +1618,18 @@
                 new CannedDataset.Builder()
                     .setField(ID_L4C1, "L4C1")
                     .setField(ID_L4C2, "L4C2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response4 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth41)
                         .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, bogusValue)
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth42)
                         .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, bogusValue)
-                        .setField(ID_L4C2, bogusValue)
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response4);
@@ -1627,49 +1637,49 @@
                 .onCell(4, 1, "L4C1")
                 .onCell(4, 2, "L4C2");
 
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         /*
          *  Now move focus around to make sure the proper values are displayed each time.
          */
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         /*
          *  Finally, autofill and check results.
          */
-        mActivity.focusCell(4, 1);
-        sUiBot.selectDataset("P4D2");
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
         expectation4.assertAutoFilled();
 
-        mActivity.focusCell(1, 1);
-        sUiBot.selectDataset("P1D1");
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
         expectation1.assertAutoFilled();
 
-        mActivity.focusCell(3, 1);
-        sUiBot.selectDataset("P3D1");
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
         expectation3.assertAutoFilled();
 
-        mActivity.focusCell(2, 2);
-        sUiBot.selectDataset("P2D2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
         expectation2.assertAutoFilled();
     }
 
@@ -1683,10 +1693,6 @@
         // Set service.
         enableService();
 
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
         /**
          * 1st partition.
          */
@@ -1700,7 +1706,7 @@
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth12)
-                        .setField(ID_L1C1, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D2"))
                         .build())
                 .build();
@@ -1710,7 +1716,7 @@
                 .onCell(1, 2, "l1c2");
 
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1720,7 +1726,6 @@
         final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
                 new CannedDataset.Builder()
                     .setField(ID_L2C2, "L2C2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response2 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
@@ -1731,7 +1736,7 @@
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth22)
                         .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response2);
@@ -1739,7 +1744,7 @@
                 .onCell(2, 2, "L2C2");
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         /**
@@ -1750,14 +1755,13 @@
                 new CannedDataset.Builder()
                         .setField(ID_L3C1, "l3c1")
                         .setField(ID_L3C2, "l3c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final CannedFillResponse response3 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth31)
                         .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setPresentation(createPresentation("P3D2"))
@@ -1771,7 +1775,7 @@
                 .onCell(3, 2, "l3c2");
 
         // Trigger partition.
-        mActivity.focusCell(3, 2);
+        focusCell(3, 2);
         sReplier.getNextFillRequest();
 
         /**
@@ -1783,7 +1787,7 @@
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth41)
                         .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, bogusValue)
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setPresentation(createPresentation("P4D2"))
@@ -1796,49 +1800,49 @@
                 .onCell(4, 1, "L4C1")
                 .onCell(4, 2, "L4C2");
 
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         /*
          *  Now move focus around to make sure the proper values are displayed each time.
          */
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         /*
          *  Finally, autofill and check results.
          */
-        mActivity.focusCell(4, 1);
-        sUiBot.selectDataset("P4D2");
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
         expectation4.assertAutoFilled();
 
-        mActivity.focusCell(1, 1);
-        sUiBot.selectDataset("P1D1");
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
         expectation1.assertAutoFilled();
 
-        mActivity.focusCell(3, 1);
-        sUiBot.selectDataset("P3D1");
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
         expectation3.assertAutoFilled();
 
-        mActivity.focusCell(2, 2);
-        sUiBot.selectDataset("P2D2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
         expectation2.assertAutoFilled();
     }
 
@@ -1876,10 +1880,6 @@
         // Set service.
         enableService();
 
-        // TODO: current API requires these fields...
-        final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
-        final String bogusValue = "Y U REQUIRE IT?";
-
         /**
          * 1st partition.
          */
@@ -1893,20 +1893,19 @@
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth12)
-                        .setField(ID_L1C1, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("P1D2"))
                         .build())
                 .build();
         sReplier.addResponse(response1);
         // Trigger partition.
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P1D1", "P1D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
 
         /**
          * 2nd partition.
@@ -1917,15 +1916,14 @@
                     .setField(ID_L1C1, "2l1c1") // from previous partition
                     .setField(ID_L2C1, "2l2c1")
                     .setField(ID_L2C2, "2l2c2")
-                    .setPresentation(bogusPresentation)
                     .build());
         final CannedFillResponse response2 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth21)
                         .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L1C1, bogusValue) // from previous partition
-                        .setField(ID_L2C1, bogusValue)
-                        .setField(ID_L2C2, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setPresentation(createPresentation("P2D2"))
@@ -1935,18 +1933,18 @@
         sReplier.addResponse(response2);
 
         // Trigger partition.
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P2D1"); // changed
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P1D1");
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P2D1", "P2D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
 
         /**
          * 3rd partition.
@@ -1957,44 +1955,43 @@
                         .setField(ID_L1C2, "3l1c2") // from previous partition
                         .setField(ID_L3C1, "3l3c1")
                         .setField(ID_L3C2, "3l3c2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
         final CannedFillResponse response3 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth31)
                         .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L1C2, bogusValue) // from previous partition
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth32)
                         .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L2C2, bogusValue) // from previous partition
-                        .setField(ID_L3C1, bogusValue)
-                        .setField(ID_L3C2, bogusValue)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response3);
 
         // Trigger partition.
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P3D1"); // changed
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P2D1");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P3D2"); // changed
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P3D1", "P3D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
 
         /**
          * 4th partition.
@@ -2009,7 +2006,6 @@
                         .setField(ID_L3C1, "4l3c1") // from previous partition
                         .setField(ID_L3C2, "4l3c2") // from previous partition
                         .setField(ID_L4C1, "4l4c1")
-                        .setPresentation(bogusPresentation)
                         .build());
         final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
                 new CannedDataset.Builder()
@@ -2022,57 +2018,56 @@
                         .setField(ID_L1C1, "4L1C1") // from previous partition
                         .setField(ID_L4C1, "4L4C1")
                         .setField(ID_L4C2, "4L4C2")
-                        .setPresentation(bogusPresentation)
                         .build());
         final CannedFillResponse response4 = new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth41)
                         .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L1C1, bogusValue) // from previous partition
-                        .setField(ID_L1C2, bogusValue) // from previous partition
-                        .setField(ID_L2C1, bogusValue) // from previous partition
-                        .setField(ID_L2C2, bogusValue) // from previous partition
-                        .setField(ID_L3C1, bogusValue) // from previous partition
-                        .setField(ID_L3C2, bogusValue) // from previous partition
-                        .setField(ID_L4C1, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setAuthentication(auth42)
                         .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L1C1, bogusValue) // from previous partition
-                        .setField(ID_L1C2, bogusValue) // from previous partition
-                        .setField(ID_L2C1, bogusValue) // from previous partition
-                        .setField(ID_L2C2, bogusValue) // from previous partition
-                        .setField(ID_L3C1, bogusValue) // from previous partition
-                        .setField(ID_L3C2, bogusValue) // from previous partition
-                        .setField(ID_L1C1, bogusValue) // from previous partition
-                        .setField(ID_L4C1, bogusValue)
-                        .setField(ID_L4C2, bogusValue)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
                         .build())
                 .build();
         sReplier.addResponse(response4);
 
         // Trigger partition.
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
         // Asserts proper datasets are shown on each field defined so far.
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("P4D1", "P4D2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("P4D2");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
 
         /*
          * Finally, autofill and check results.
@@ -2102,9 +2097,9 @@
             chosenOne = "P4D2";
         }
 
-          mActivity.focusCell(4, 1);
-          sUiBot.selectDataset(chosenOne);
-          expectation.assertAutoFilled();
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
+        expectation.assertAutoFilled();
     }
 
     @Test
@@ -2129,10 +2124,10 @@
         final FillExpectation expectation1 = mActivity.expectAutofill()
                 .onCell(1, 1, "l1c1")
                 .onCell(1, 2, "l1c2");
-        mActivity.focusCell(1, 1);
+        focusCell(1, 1);
         sReplier.getNextFillRequest();
 
-        sUiBot.assertDatasets("Auth 1");
+        mUiBot.assertDatasets("Auth 1");
 
         // Prepare 2nd partition.
         final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
@@ -2151,10 +2146,10 @@
         final FillExpectation expectation2 = mActivity.expectAutofill()
                 .onCell(2, 1, "l2c1")
                 .onCell(2, 2, "l2c2");
-        mActivity.focusCell(2, 1);
+        focusCell(2, 1);
         sReplier.getNextFillRequest();
 
-        sUiBot.assertDatasets("Auth 2");
+        mUiBot.assertDatasets("Auth 2");
 
         // Prepare 3rd partition.
         final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
@@ -2173,10 +2168,10 @@
         final FillExpectation expectation3 = mActivity.expectAutofill()
                 .onCell(3, 1, "l3c1")
                 .onCell(3, 2, "l3c2");
-        mActivity.focusCell(3, 1);
+        focusCell(3, 1);
         sReplier.getNextFillRequest();
 
-        sUiBot.assertDatasets("Auth 3");
+        mUiBot.assertDatasets("Auth 3");
 
         // Prepare 4th partition.
         final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
@@ -2195,52 +2190,52 @@
         final FillExpectation expectation4 = mActivity.expectAutofill()
                 .onCell(4, 1, "l4c1")
                 .onCell(4, 2, "l4c2");
-        mActivity.focusCell(4, 1);
+        focusCell(4, 1);
         sReplier.getNextFillRequest();
 
-        sUiBot.assertDatasets("Auth 4");
+        mUiBot.assertDatasets("Auth 4");
 
         // Now play around the focus to make sure they still display the right values.
 
-        mActivity.focusCell(1, 2);
-        sUiBot.assertDatasets("Auth 1");
-        mActivity.focusCell(1, 1);
-        sUiBot.assertDatasets("Auth 1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("Auth 1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("Auth 1");
 
-        mActivity.focusCell(3, 1);
-        sUiBot.assertDatasets("Auth 3");
-        mActivity.focusCell(3, 2);
-        sUiBot.assertDatasets("Auth 3");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("Auth 3");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("Auth 3");
 
-        mActivity.focusCell(2, 1);
-        sUiBot.assertDatasets("Auth 2");
-        mActivity.focusCell(4, 2);
-        sUiBot.assertDatasets("Auth 4");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("Auth 4");
 
-        mActivity.focusCell(2, 2);
-        sUiBot.assertDatasets("Auth 2");
-        mActivity.focusCell(4, 1);
-        sUiBot.assertDatasets("Auth 4");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("Auth 4");
 
         // Finally, autofill and check them.
-        mActivity.focusCell(2, 1);
-        sUiBot.selectDataset("Auth 2");
-        sUiBot.selectDataset("Partition 2");
+        focusCell(2, 1);
+        mUiBot.selectDataset("Auth 2");
+        mUiBot.selectDataset("Partition 2");
         expectation2.assertAutoFilled();
 
-        mActivity.focusCell(4, 1);
-        sUiBot.selectDataset("Auth 4");
-        sUiBot.selectDataset("Partition 4");
+        focusCell(4, 1);
+        mUiBot.selectDataset("Auth 4");
+        mUiBot.selectDataset("Partition 4");
         expectation4.assertAutoFilled();
 
-        mActivity.focusCell(3, 1);
-        sUiBot.selectDataset("Auth 3");
-        sUiBot.selectDataset("Partition 3");
+        focusCell(3, 1);
+        mUiBot.selectDataset("Auth 3");
+        mUiBot.selectDataset("Partition 3");
         expectation3.assertAutoFilled();
 
-        mActivity.focusCell(1, 1);
-        sUiBot.selectDataset("Auth 1");
-        sUiBot.selectDataset("Partition 1");
+        focusCell(1, 1);
+        mUiBot.selectDataset("Auth 1");
+        mUiBot.selectDataset("Partition 1");
         expectation1.assertAutoFilled();
     }
 
@@ -2262,13 +2257,13 @@
             sReplier.addResponse(response1);
 
             // Trigger autofill.
-            mActivity.focusCell(1, 1);
+            focusCell(1, 1);
             sReplier.getNextFillRequest();
 
             // Make sure UI is shown, but don't tap it.
-            sUiBot.assertDatasets("l1c1");
-            mActivity.focusCell(1, 2);
-            sUiBot.assertDatasets("l1c2");
+            mUiBot.assertDatasets("l1c1");
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
 
             // Prepare 2nd partition.
             final CannedFillResponse response2 = new CannedFillResponse.Builder()
@@ -2279,16 +2274,16 @@
             sReplier.addResponse(response2);
 
             // Trigger autofill on 2nd partition.
-            mActivity.focusCell(2, 1);
+            focusCell(2, 1);
 
             // Make sure it was ignored.
-            sUiBot.assertNoDatasets();
+            mUiBot.assertNoDatasets();
 
             // Make sure 1st partition is still working.
-            mActivity.focusCell(1, 2);
-            sUiBot.assertDatasets("l1c2");
-            mActivity.focusCell(1, 1);
-            sUiBot.assertDatasets("l1c1");
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
 
             // Prepare 3rd partition.
             final CannedFillResponse response3 = new CannedFillResponse.Builder()
@@ -2298,21 +2293,21 @@
                     .build();
             sReplier.addResponse(response3);
             // Trigger autofill on 3rd partition.
-            mActivity.focusCell(3, 2);
+            focusCell(3, 2);
 
             // Make sure it was ignored.
-            sUiBot.assertNoDatasets();
+            mUiBot.assertNoDatasets();
 
             // Make sure 1st partition is still working...
-            mActivity.focusCell(1, 2);
-            sUiBot.assertDatasets("l1c2");
-            mActivity.focusCell(1, 1);
-            sUiBot.assertDatasets("l1c1");
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
 
             //...and can be autofilled.
             final FillExpectation expectation = mActivity.expectAutofill()
                     .onCell(1, 1, "l1c1");
-            sUiBot.selectDataset("l1c1");
+            mUiBot.selectDataset("l1c1");
             expectation.assertAutoFilled();
         } finally {
             setMaxPartitions(maxBefore);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
index 75c742f..0f571ef 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -20,6 +20,7 @@
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextFromResouces;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.assertTextOnly;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
@@ -28,7 +29,6 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -49,11 +49,6 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testSanitization() throws Exception {
         // Set service.
@@ -78,8 +73,8 @@
         assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
 
         // ...password label should be ok because it was set from other resource id
-        assertTextOnly(findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL),
-                "DA PASSWORD");
+        assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
 
         // ...username and password should be ok because they were set in the SML
         assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
@@ -92,13 +87,13 @@
         mActivity.tapLogin();
 
         // Assert the snack bar is shown and tap "Save".
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
         // Assert sanitization on save: everything should be available!
         assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
-        assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_PASSWORD_LABEL),
-                "DA PASSWORD");
+        assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
index 5cdcf9b..1cb5942 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
@@ -29,10 +29,20 @@
     static final String ID_PRE_LABEL = "preLabel";
     static final String ID_PRE_INPUT = "preInput";
 
+    private static PreSimpleSaveActivity sInstance;
+
     TextView mPreLabel;
     EditText mPreInput;
     Button mSubmit;
 
+    public static PreSimpleSaveActivity getInstance() {
+        return sInstance;
+    }
+
+    public PreSimpleSaveActivity() {
+        sInstance = this;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -48,4 +58,22 @@
             startActivity(new Intent(this, SimpleSaveActivity.class));
         });
     }
+
+    FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input);
+        mPreInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+
+        private FillExpectation(String input) {
+            mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
+        }
+
+        void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
index b193ddf..92fa94b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
@@ -15,6 +15,7 @@
  */
 package android.autofillservice.cts;
 
+import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
@@ -24,13 +25,23 @@
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
+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;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import org.junit.Rule;
 
+import java.util.regex.Pattern;
+
 public class PreSimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
 
     @Rule
@@ -43,7 +54,7 @@
         final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class);
         if (remainOnRecents) {
             intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
         }
         mActivity = mActivityRule.launchActivity(intent);
     }
@@ -72,27 +83,28 @@
             mActivity.mSubmit.performClick();
         });
         // Make sure post-save activity is shown...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Tap the link.
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // .. then do something to return to previous activity...
         switch (type) {
             case ROTATE_THEN_TAP_BACK_BUTTON:
-                sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
                 // not breaking on purpose
             case TAP_BACK_BUTTON:
-                sUiBot.pressBack();
+                mUiBot.pressBack();
                 break;
             case FINISH_ACTIVITY:
                 // ..then finishes it.
-                WelcomeActivity.finishIt();
+                WelcomeActivity.finishIt(mUiBot);
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
@@ -100,7 +112,7 @@
 
         // ... and tap save.
         final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        sUiBot.saveForAutofill(newSaveUi, true);
+        mUiBot.saveForAutofill(newSaveUi, true);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
@@ -130,45 +142,43 @@
             mActivity.mSubmit.performClick();
         });
         // Make sure post-save activity is shown...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Tap the link.
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // Tap back to restore the Save UI...
-        sUiBot.pressBack();
+        mUiBot.pressBack();
 
         // ...but don't tap it...
-        final UiObject2 saveUi2 = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // ...instead, do something to dismiss it:
         switch (action) {
             case TOUCH_OUTSIDE:
-                sUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
                 break;
             case TAP_NO_ON_SAVE_UI:
-                sUiBot.saveForAutofill(saveUi2, false);
+                mUiBot.saveForAutofill(saveUi2, false);
                 break;
             case TAP_YES_ON_SAVE_UI:
-                sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
                 final SaveRequest saveRequest = sReplier.getNextSaveRequest();
                 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
                         "108");
-                Helper.assertNoDanglingSessions();
                 break;
             default:
                 throw new IllegalArgumentException("invalid action: " + action);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // Make sure previous session was finished.
-        Helper.assertNoDanglingSessions();
 
         // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -192,10 +202,10 @@
             newActivty.mCommit.performClick();
         });
         // Make sure post-save activity is shown...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Save it...
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -205,7 +215,7 @@
     @Override
     protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
             throws Exception {
-        startActivity(type == PostSaveLinkTappedAction.TAP_RECENTS);
+        startActivity(false);
         // Set service.
         enableService();
 
@@ -226,39 +236,34 @@
             mActivity.mSubmit.performClick();
         });
         // Make sure post-save activity is shown...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Tap the link.
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
         tapSaveUiLink(saveUi);
 
         // Make sure linked activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
 
         switch (type) {
-            case TAP_RECENTS:
-                sUiBot.switchAppsUsingRecents();
-                // Make sure right activity is showing.
-                sUiBot.assertShownByRelativeId(ID_INPUT);
-                break;
             case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivity(PreSimpleSaveActivity.class);
-                sUiBot.assertShownByRelativeId(ID_INPUT);
+                startActivityOnNewTask(PreSimpleSaveActivity.class);
+                mUiBot.assertShownByRelativeId(ID_INPUT);
                 break;
             case LAUNCH_NEW_ACTIVITY:
                 // Launch a 3rd activity...
-                startActivity(LoginActivity.class);
-                sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
                 // ...then go back
-                sUiBot.pressBack();
-                sUiBot.assertShownByRelativeId(ID_INPUT);
+                mUiBot.pressBack();
+                mUiBot.assertShownByRelativeId(ID_INPUT);
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
         }
 
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
     }
 
     @Override
@@ -294,14 +299,17 @@
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
 
         // Save UI should be showing as well, since Trampoline finished.
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // Go back and make sure it's showing the right activity.
-        sUiBot.pressBack();
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        // first BACK cancels save dialog
+        mUiBot.pressBack();
+        // second BACK cancel WelcomeActivity
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -321,13 +329,67 @@
             newActivty.mCommit.performClick();
         });
         // Make sure post-save activity is shown...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // Save it...
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
 
         // ... and assert results
         final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
         assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
     }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        final CustomDescription.Builder customDescription =
+                newCustomDescriptionBuilder(WelcomeActivity.class);
+        final RemoteViews update = newTemplate();
+        if (updateLinkView) {
+            update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+        } else {
+            update.setCharSequence(R.id.static_text, "setText", "ME!");
+        }
+        Validator validCondition = new RegexValidator(mActivity.mPreInput.getAutofillId(),
+                Pattern.compile(".*"));
+        customDescription.batchUpdate(validCondition,
+                new BatchUpdates.Builder().updateTemplate(update).build());
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setCustomDescription(customDescription.build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+        Helper.assertHasSessions(mPackageName);
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RequiredFeatureRule.java b/tests/autofillservice/src/android/autofillservice/cts/RequiredFeatureRule.java
deleted file mode 100644
index 17b2504..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/RequiredFeatureRule.java
+++ /dev/null
@@ -1,61 +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.autofillservice.cts;
-
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not have a given feature.
- */
-// TODO: move to common CTS codebase
-public class RequiredFeatureRule implements TestRule {
-    private static final String TAG = "RequiredFeatureRule";
-
-    private final String mFeature;
-    private final boolean mHasFeature;
-
-    public RequiredFeatureRule(String feature) {
-        mFeature = feature;
-        mHasFeature = hasFeature(feature);
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (!mHasFeature) {
-                    Log.d(TAG, "skipping "
-                            + description.getClassName() + "#" + description.getMethodName()
-                            + " because device does not have feature '" + mFeature + "'");
-                    return;
-                }
-                base.evaluate();
-            }
-        };
-    }
-
-    public static boolean hasFeature(String feature) {
-        return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
index be740b1..0eda6be 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
@@ -45,17 +45,34 @@
 
             @Override
             public void evaluate() throws Throwable {
+                final String name = description.getDisplayName();
                 Throwable caught = null;
                 for (int i = 1; i <= mMaxAttempts; i++) {
                     try {
                         base.evaluate();
+                        if (i == 1) {
+                            Log.v(TAG, "Good News, Everyone! " + name + " passed right away");
+                        } else {
+                            Log.d(TAG,
+                                    "Better late than never: " + name + "passed at attempt #" + i);
+                        }
                         return;
-                    } catch (RetryableException | StaleObjectException e) {
+                    } catch (RetryableException e) {
+                        final Timeout timeout = e.getTimeout();
+                        if (timeout != null) {
+                            long before = timeout.ms();
+                            timeout.increase();
+                            Log.d(TAG, "Increased " + timeout.getName() + " from " + before + "ms "
+                                    + " to " + timeout.ms() + "ms");
+                        }
                         caught = e;
-                        Log.w(TAG,
-                                description.getDisplayName() + ": attempt " + i + " failed: " + e);
+                    } catch (StaleObjectException e) {
+                        caught = e;
                     }
+                    Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
+                            + ": " + caught);
                 }
+                Log.e(TAG, "D'OH! " + name + ": giving up after " + mMaxAttempts + " attempts");
                 throw caught;
             }
         };
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
index 3b733f6..473ec4e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
@@ -68,6 +68,17 @@
     }
 
     @Test
+    public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
+        final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
+        final RetryableException exception = new RetryableException(timeout, "Y U NO?");
+        final RetryRule rule = new RetryRule(2);
+        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 {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java b/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
index 7ca7d62..7069aa5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
@@ -16,20 +16,52 @@
 
 package android.autofillservice.cts;
 
+import android.support.annotation.Nullable;
+
 /**
  * Exception that cause the {@link RetryRule} to re-try a test.
  */
 public class RetryableException extends RuntimeException {
 
+    @Nullable
+    private final Timeout mTimeout;
+
     public RetryableException(String msg) {
-        super(msg);
+        this((Timeout) null, msg);
     }
 
     public RetryableException(String format, Object...args) {
-        super(String.format(format, args));
+        this((Timeout) null, String.format(format, args));
     }
 
     public RetryableException(Throwable cause, String format, Object...args) {
+        this((Timeout) null, cause, String.format(format, args), cause);
+    }
+
+    public RetryableException(@Nullable Timeout timeout, String msg) {
+        super(msg);
+        this.mTimeout = timeout;
+    }
+
+    public RetryableException(@Nullable Timeout timeout, String format, Object...args) {
+        super(String.format(format, args));
+        this.mTimeout = timeout;
+    }
+
+    public RetryableException(@Nullable Timeout timeout, Throwable cause, String format,
+            Object...args) {
         super(String.format(format, args), cause);
+        this.mTimeout = timeout;
+    }
+
+    @Nullable
+    public Timeout getTimeout() {
+        return mTimeout;
+    }
+
+    @Override
+    public String getMessage() {
+        final String superMessage = super.getMessage();
+        return mTimeout == null ? superMessage : superMessage + " (timeout=" + mTimeout + ")";
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java
new file mode 100644
index 0000000..817e42b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java
@@ -0,0 +1,170 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Rule used to safely run clean up code after a test is finished, so that exceptions thrown by
+ * the cleanup code don't hide exception thrown by the test body
+ */
+// TODO: move to common CTS code
+public final class SafeCleanerRule implements TestRule {
+
+    private static final String TAG = "SafeCleanerRule";
+
+    private final List<Runnable> mCleaners = new ArrayList<>();
+    private final List<Callable<List<Throwable>>> mExtraThrowables = new ArrayList<>();
+    private final List<Throwable> mThrowables = new ArrayList<>();
+    private Dumper mDumper;
+
+    /**
+     * Runs {@code cleaner} after the test is finished, catching any {@link Throwable} thrown by it.
+     */
+    public SafeCleanerRule run(@NonNull Runnable cleaner) {
+        mCleaners.add(cleaner);
+        return this;
+    }
+
+    /**
+     * Adds exceptions directly.
+     *
+     * <p>Typically used when exceptions were caught asychronously during the test execution.
+     */
+    public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
+        mExtraThrowables.add(exceptions);
+        return this;
+    }
+
+    /**
+     * Sets a {@link Dumper} used to log errors.
+     */
+    public SafeCleanerRule setDumper(@NonNull Dumper dumper) {
+        mDumper = dumper;
+        return this;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                // First run the test
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    Log.w(TAG, "Adding exception from main test");
+                    mThrowables.add(t);
+                }
+
+                // Then the cleanup runners
+                for (Runnable runner : mCleaners) {
+                    try {
+                        runner.run();
+                    } catch (Throwable t) {
+                        Log.w(TAG, "Adding exception from cleaner");
+                        mThrowables.add(t);
+                    }
+                }
+
+                // And finally add the extra exceptions
+                for (Callable<List<Throwable>> extraThrowablesCallable : mExtraThrowables) {
+                    final List<Throwable> extraThrowables = extraThrowablesCallable.call();
+                    if (extraThrowables != null) {
+                        Log.w(TAG, "Adding " + extraThrowables.size() + " extra exceptions");
+                        mThrowables.addAll(extraThrowables);
+                    }
+                }
+
+                // Finally, throw up!
+                if (mThrowables.isEmpty()) return;
+
+                final int numberExceptions = mThrowables.size();
+                if (numberExceptions == 1) {
+                    fail(description, mThrowables.get(0));
+                }
+                fail(description, new MultipleExceptions(mThrowables));
+            }
+
+        };
+    }
+
+    private void fail(Description description, Throwable t) throws Throwable {
+        if (mDumper != null) {
+            mDumper.dump(description.getDisplayName(), t);
+        }
+        throw t;
+    }
+
+    private static String toMesssage(List<Throwable> throwables) {
+        String msg = "D'OH!";
+        try {
+            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+                sw.write("Caught " + throwables.size() + " exceptions\n");
+                for (int i = 0; i < throwables.size(); i++) {
+                    sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
+                    final Throwable exception = throwables.get(i);
+                    exception.printStackTrace(pw);
+                    sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
+                }
+                msg = sw.toString();
+            }
+        } catch (IOException e) {
+            // ignore close() errors - should not happen...
+            Log.e(TAG, "Exception closing StringWriter: " + e);
+        }
+        return msg;
+    }
+
+    // VisibleForTesting
+    static class MultipleExceptions extends AssertionError {
+        private final List<Throwable> mThrowables;
+
+        private MultipleExceptions(List<Throwable> throwables) {
+            super(toMesssage(throwables));
+
+            this.mThrowables = throwables;
+        }
+
+        List<Throwable> getThrowables() {
+            return mThrowables;
+        }
+    }
+
+    /**
+     * Optional interface used to dump an error.
+     */
+    public interface Dumper {
+
+        /**
+         * Dumps an error.
+         */
+        void dump(@NonNull String testName, @NonNull Throwable t);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java
new file mode 100644
index 0000000..dde0a38
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.expectThrows;
+
+import android.autofillservice.cts.SafeCleanerRule.Dumper;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SafeCleanerRuleTest {
+
+    private static class FailureStatement extends Statement {
+        private final Throwable mThrowable;
+
+        FailureStatement(Throwable t) {
+            mThrowable = t;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            throw mThrowable;
+        }
+    }
+
+    private final Description mDescription = Description.createSuiteDescription("Whatever");
+    private final RuntimeException mRuntimeException = new RuntimeException("D'OH!");
+
+    @Mock private Dumper mDumper;
+
+    // Use mocks for objects that don't throw any exception.
+    @Mock private Runnable mGoodGuyRunner1;
+    @Mock private Runnable mGoodGuyRunner2;
+    @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions1;
+    @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions2;
+    @Mock private Statement mGoodGuyStatement;
+
+    @Test
+    public void testEmptyRule_testPass() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule();
+        rule.apply(mGoodGuyStatement, mDescription).evaluate();
+    }
+
+    @Test
+    public void testEmptyRule_testFails() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule();
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+    }
+
+    @Test
+    public void testEmptyRule_testFails_withDumper() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule().setDumper(mDumper);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mDumper).dump("Whatever", actualException);
+    }
+
+    @Test
+    public void testOnlyTestFails() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .run(mGoodGuyRunner1)
+                .add(mGoodGuyExtraExceptions1);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyExtraExceptions1).call();
+    }
+
+    @Test
+    public void testOnlyTestFails_withDumper() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .setDumper(mDumper)
+                .run(mGoodGuyRunner1)
+                .add(mGoodGuyExtraExceptions1);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyExtraExceptions1).call();
+        verify(mDumper).dump("Whatever", actualException);
+    }
+
+    @Test
+    public void testTestPass_oneRunnerFails() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .run(mGoodGuyRunner1)
+                .run(() -> { throw mRuntimeException; })
+                .run(mGoodGuyRunner2)
+                .add(mGoodGuyExtraExceptions1);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+    }
+
+    @Test
+    public void testTestPass_oneRunnerFails_withDumper() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .setDumper(mDumper)
+                .run(mGoodGuyRunner1)
+                .run(() -> {
+                    throw mRuntimeException;
+                })
+                .run(mGoodGuyRunner2)
+                .add(mGoodGuyExtraExceptions1);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+        verify(mDumper).dump("Whatever", actualException);
+    }
+
+    @Test
+    public void testTestPass_oneExtraExceptionThrown() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .run(mGoodGuyRunner1)
+                .add(() -> {
+                    return ImmutableList.of(mRuntimeException);
+                })
+                .add(mGoodGuyExtraExceptions1)
+                .run(mGoodGuyRunner2);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+    }
+
+    @Test
+    public void testTestPass_oneExtraExceptionThrown_withDumper() throws Throwable {
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .setDumper(mDumper)
+                .run(mGoodGuyRunner1)
+                .add(() -> { return ImmutableList.of(mRuntimeException); })
+                .add(mGoodGuyExtraExceptions1)
+                .run(mGoodGuyRunner2);
+        final Throwable actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+        verify(mDumper).dump("Whatever", actualException);
+    }
+
+    @Test
+    public void testThrowTheKitchenSinkAKAEverybodyThrows() throws Throwable {
+        final Exception extra1 = new Exception("1");
+        final Exception extra2 = new Exception("2");
+        final Exception extra3 = new Exception("3");
+        final Error error1 = new Error("one");
+        final Error error2 = new Error("two");
+        final RuntimeException testException  = new RuntimeException("TEST, Y U NO PASS?");
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .run(mGoodGuyRunner1)
+                .add(mGoodGuyExtraExceptions1)
+                .add(() -> {
+                    return ImmutableList.of(extra1, extra2);
+                })
+                .run(() -> {
+                    throw error1;
+                })
+                .run(mGoodGuyRunner2)
+                .add(() -> {
+                    return ImmutableList.of(extra3);
+                })
+                .add(mGoodGuyExtraExceptions2)
+                .run(() -> {
+                    throw error2;
+                });
+
+        final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
+                SafeCleanerRule.MultipleExceptions.class,
+                () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
+        assertThat(actualException.getThrowables())
+                .containsExactly(testException, error1, error2, extra1, extra2, extra3)
+                .inOrder();
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+    }
+
+    @Test
+    public void testThrowTheKitchenSinkAKAEverybodyThrows_withDumper() throws Throwable {
+        final Exception extra1 = new Exception("1");
+        final Exception extra2 = new Exception("2");
+        final Exception extra3 = new Exception("3");
+        final Error error1 = new Error("one");
+        final Error error2 = new Error("two");
+        final RuntimeException testException  = new RuntimeException("TEST, Y U NO PASS?");
+        final SafeCleanerRule rule = new SafeCleanerRule()
+                .setDumper(mDumper)
+                .run(mGoodGuyRunner1)
+                .add(mGoodGuyExtraExceptions1)
+                .add(() -> {
+                    return ImmutableList.of(extra1, extra2);
+                })
+                .run(() -> {
+                    throw error1;
+                })
+                .run(mGoodGuyRunner2)
+                .add(() -> { return ImmutableList.of(extra3); })
+                .add(mGoodGuyExtraExceptions2)
+                .run(() -> { throw error2; });
+
+        final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
+                SafeCleanerRule.MultipleExceptions.class,
+                () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
+        assertThat(actualException.getThrowables())
+                .containsExactly(testException, error1, error2, extra1, extra2, extra3)
+                .inOrder();
+        verify(mGoodGuyRunner1).run();
+        verify(mGoodGuyRunner2).run();
+        verify(mGoodGuyExtraExceptions1).call();
+        verify(mDumper).dump("Whatever", actualException);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
index 702e1b1..d34eda1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
@@ -16,8 +16,11 @@
 
 package android.autofillservice.cts;
 
+import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
+import android.service.autofill.InternalSanitizer;
+import android.service.autofill.Sanitizer;
 import android.service.autofill.SaveInfo;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.autofill.AutofillId;
@@ -28,6 +31,9 @@
 @RunWith(AndroidJUnit4.class)
 public class SaveInfoTest {
 
+    private  final AutofillId mId = new AutofillId(42);
+    private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
+
     @Test
     public void testRequiredIdsBuilder_null() {
         assertThrows(IllegalArgumentException.class,
@@ -56,14 +62,14 @@
     @Test
     public void testSetOptionalIds_null() {
         final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { new AutofillId(42) });
+                new AutofillId[] { mId });
         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[] { new AutofillId(42) });
+                new AutofillId[] { mId });
         assertThrows(IllegalArgumentException.class,
                 () -> builder.setOptionalIds(new AutofillId[] {}));
     }
@@ -71,8 +77,38 @@
     @Test
     public void testSetOptional_nullEntry() {
         final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { new AutofillId(42) });
+                new AutofillId[] { mId });
         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 });
+        // Null sanitizer
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(null, mId));
+        // Invalid sanitizer class
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mock(Sanitizer.class), mId));
+        // Null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
+        // Empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
+        // Repeated ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
+    }
+
+    @Test
+    public void testAddSanitizer_sameIdOnDifferentCalls() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                new AutofillId[] { mId });
+        builder.addSanitizer(mSanitizer, mId);
+        assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
+    }
+
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index bd26426..6a2fe8f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -19,16 +19,15 @@
 import static android.autofillservice.cts.Helper.ID_LOGIN;
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.eventually;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.Helper.getContext;
 import static android.autofillservice.cts.Helper.getOutOfProcessPid;
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.OutOfProcessLoginActivity.getStartedMarker;
 import static android.autofillservice.cts.OutOfProcessLoginActivity.getStoppedMarker;
 import static android.autofillservice.cts.UiBot.LANDSCAPE;
 import static android.autofillservice.cts.UiBot.PORTRAIT;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -46,27 +45,42 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Callable;
+
 /**
  * Test the lifecycle of a autofill session
  */
 public class SessionLifecycleTest extends AutoFillServiceTestCase {
-    private static final String USERNAME_FULL_ID = "android.autofillservice.cts:id/" + ID_USERNAME;
-    private static final String PASSWORD_FULL_ID = "android.autofillservice.cts:id/" + ID_PASSWORD;
-    private static final String LOGIN_FULL_ID = "android.autofillservice.cts:id/" + ID_LOGIN;
-    private static final String BUTTON_FULL_ID = "android.autofillservice.cts:id/button";
-    private static final String CANCEL_FULL_ID = "android.autofillservice.cts:id/cancel";
+    private static final String ID_BUTTON = "button";
+    private static final String ID_CANCEL = "cancel";
 
-    @Before
-    public void cleanUpState() {
-        Helper.preTestCleanup();
+    /**
+     * Delay for activity start/stop.
+     */
+    // TODO: figure out a better way to wait without using sleep().
+    private static final long WAIT_ACTIVITY_MS = 1000;
+
+    private static final Timeout SESSION_LIFECYCLE_TIMEOUT = new Timeout(
+            "SESSION_LIFECYCLE_TIMEOUT", 2000, 2F, 5000);
+
+    /**
+     * Runs an {@code assertion}, retrying until {@code timeout} is reached.
+     */
+    private static void eventually(String description, Callable<Boolean> assertion)
+            throws Exception {
+        SESSION_LIFECYCLE_TIMEOUT.run(description, assertion);
+    }
+
+    public SessionLifecycleTest() {
+        super(new UiBot(SESSION_LIFECYCLE_TIMEOUT));
     }
 
     /**
      * Prevents the screen to rotate by itself
      */
     @Before
-    public void disableAutoRotation() {
-        Helper.disableAutoRotation(sUiBot);
+    public void disableAutoRotation() throws Exception {
+        Helper.disableAutoRotation(mUiBot);
     }
 
     /**
@@ -79,14 +93,32 @@
 
     private void killOfProcessLoginActivityProcess() throws Exception {
         // Waiting for activity to stop (stop marker appears)
-        eventually(() -> assertThat(getStoppedMarker(getContext()).exists()).isTrue());
+        eventually("getStoppedMarker()", () -> {
+            return getStoppedMarker(getContext()).exists();
+        });
 
         // onStop might not be finished, hence wait more
-        SystemClock.sleep(1000);
+        SystemClock.sleep(WAIT_ACTIVITY_MS);
 
         // Kill activity that is in the background
         runShellCommand("kill -9 %d",
-                getOutOfProcessPid("android.autofillservice.cts.outside"));
+                getOutOfProcessPid("android.autofillservice.cts.outside",
+                        SESSION_LIFECYCLE_TIMEOUT));
+    }
+
+    private void startAndWaitExternalActivity() throws Exception {
+        final Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
+                OutOfProcessLoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getStartedMarker(getContext()).delete();
+        getContext().startActivity(outOfProcessAcvitityStartIntent);
+        eventually("getStartedMarker()", () -> {
+            return getStartedMarker(getContext()).exists();
+        });
+        getStartedMarker(getContext()).delete();
+        // Even if we wait the activity started, UiObject still fails. Have to wait a little bit.
+        SystemClock.sleep(WAIT_ACTIVITY_MS);
+
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
     }
 
     @Test
@@ -95,9 +127,7 @@
         enableService();
 
         // Start activity that is autofilled in a separate process so it can be killed
-        Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
-                OutOfProcessLoginActivity.class);
-        getContext().startActivity(outOfProcessAcvitityStartIntent);
+        startAndWaitExternalActivity();
 
         // Set expectations.
         final Bundle extras = new Bundle();
@@ -123,62 +153,67 @@
         sReplier.addResponse(response);
 
         // Trigger autofill on username
-        sUiBot.selectById(USERNAME_FULL_ID);
+        mUiBot.selectByRelativeId(ID_USERNAME);
 
         // Wait for fill request to be processed
         sReplier.getNextFillRequest();
 
         // Wait until authentication is shown
-        sUiBot.assertDatasets("authenticate");
+        mUiBot.assertDatasets("authenticate");
 
         // Change orientation which triggers a destroy -> create in the app as the activity
         // cannot deal with such situations
-        sUiBot.setScreenOrientation(LANDSCAPE);
+        mUiBot.setScreenOrientation(LANDSCAPE);
+        mUiBot.setScreenOrientation(PORTRAIT);
+
+        // Wait context and Views being recreated in rotation
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
 
         // Delete stopped marker
         getStoppedMarker(getContext()).delete();
 
         // Authenticate
-        sUiBot.selectDataset("authenticate");
+        mUiBot.selectDataset("authenticate");
 
         // Kill activity that is in the background
         killOfProcessLoginActivityProcess();
 
         // Change orientation which triggers a destroy -> create in the app as the activity
         // cannot deal with such situations
-        sUiBot.setScreenOrientation(PORTRAIT);
+        mUiBot.setScreenOrientation(PORTRAIT);
 
         // Approve authentication
-        sUiBot.selectById(BUTTON_FULL_ID);
+        mUiBot.selectByRelativeId(ID_BUTTON);
 
         // Wait for dataset to be shown
-        sUiBot.assertDatasets("dataset");
+        mUiBot.assertDatasets("dataset");
 
         // Change orientation which triggers a destroy -> create in the app as the activity
         // cannot deal with such situations
-        sUiBot.setScreenOrientation(LANDSCAPE);
+        mUiBot.setScreenOrientation(LANDSCAPE);
 
         // Select dataset
-        sUiBot.selectDataset("dataset");
+        mUiBot.selectDataset("dataset");
 
         // Check the results.
-        eventually(() -> assertThat(sUiBot.getTextById(USERNAME_FULL_ID)).isEqualTo(
-                "autofilled username"));
+        eventually("getTextById(" + ID_USERNAME + ")", () -> {
+            return mUiBot.getTextByRelativeId(ID_USERNAME).equals("autofilled username");
+        });
 
         // Set password
-        sUiBot.setTextById(PASSWORD_FULL_ID, "new password");
+        mUiBot.setTextByRelativeId(ID_PASSWORD, "new password");
 
         // Login
-        sUiBot.selectById(LOGIN_FULL_ID);
+        mUiBot.selectByRelativeId(ID_LOGIN);
 
         // Wait for save UI to be shown
-        sUiBot.assertShownById("android:id/autofill_save_yes");
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
 
         // Change orientation to make sure save UI can handle this
-        sUiBot.setScreenOrientation(PORTRAIT);
+        mUiBot.setScreenOrientation(PORTRAIT);
 
         // Tap "Save".
-        sUiBot.selectById("android:id/autofill_save_yes");
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Get save request
         InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -196,8 +231,6 @@
         assertThat(saveRequest.data).isNotNull();
         final String extraValue = saveRequest.data.getString("numbers");
         assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-
-        eventually(() -> assertNoDanglingSessions());
     }
 
     @Test
@@ -206,9 +239,7 @@
         enableService();
 
         // Start activity that is autofilled in a separate process so it can be killed
-        Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
-                OutOfProcessLoginActivity.class);
-        getContext().startActivity(outOfProcessAcvitityStartIntent);
+        startAndWaitExternalActivity();
 
         // Create the authentication intent (launching a full screen activity)
         IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
@@ -222,28 +253,28 @@
         sReplier.addResponse(response);
 
         // Trigger autofill on username
-        sUiBot.selectById(USERNAME_FULL_ID);
+        mUiBot.selectByRelativeId(ID_USERNAME);
 
         // Wait for fill request to be processed
         sReplier.getNextFillRequest();
 
         // Wait until authentication is shown
-        sUiBot.assertDatasets("authenticate");
+        mUiBot.assertDatasets("authenticate");
 
         // Delete stopped marker
         getStoppedMarker(getContext()).delete();
 
         // Authenticate
-        sUiBot.selectDataset("authenticate");
+        mUiBot.selectDataset("authenticate");
 
         // Kill activity that is in the background
         killOfProcessLoginActivityProcess();
 
         // Cancel authentication activity
-        sUiBot.pressBack();
+        mUiBot.pressBack();
 
         // Authentication should still be shown
-        sUiBot.assertDatasets("authenticate");
+        mUiBot.assertDatasets("authenticate");
     }
 
     @Test
@@ -252,25 +283,23 @@
         enableService();
 
         // Start activity that is autofilled in a separate process so it can be killed
-        Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
-                OutOfProcessLoginActivity.class);
-        getContext().startActivity(outOfProcessAcvitityStartIntent);
+        startAndWaitExternalActivity();
 
         CannedFillResponse response = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder(
                         createPresentation("dataset"))
-                        .setField(ID_USERNAME, "filled").build())
+                                .setField(ID_USERNAME, "filled").build())
                 .build();
         sReplier.addResponse(response);
 
         // Trigger autofill on username
-        sUiBot.selectById(USERNAME_FULL_ID);
+        mUiBot.selectByRelativeId(ID_USERNAME);
 
         // Wait for fill request to be processed
         sReplier.getNextFillRequest();
 
         // Wait until dataset is shown
-        sUiBot.assertDatasets("dataset");
+        mUiBot.assertDatasets("dataset");
 
         // Delete stopped marker
         getStoppedMarker(getContext()).delete();
@@ -285,10 +314,10 @@
         killOfProcessLoginActivityProcess();
 
         // Cancel activity on top
-        sUiBot.pressBack();
+        mUiBot.pressBack();
 
         // Dataset should still be shown
-        sUiBot.assertDatasets("dataset");
+        mUiBot.assertDatasets("dataset");
     }
 
     @Test
@@ -297,26 +326,24 @@
         enableService();
 
         // Start activity that is autofilled in a separate process so it can be killed
-        Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
-                OutOfProcessLoginActivity.class);
-        getContext().startActivity(outOfProcessAcvitityStartIntent);
+        startAndWaitExternalActivity();
 
         // Prepare response for first activity
         CannedFillResponse response = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder(
                         createPresentation("dataset1"))
-                        .setField(ID_USERNAME, "filled").build())
+                                .setField(ID_USERNAME, "filled").build())
                 .build();
         sReplier.addResponse(response);
 
         // Trigger autofill on username
-        sUiBot.selectById(USERNAME_FULL_ID);
+        mUiBot.selectByRelativeId(ID_USERNAME);
 
         // Wait for fill request to be processed
         sReplier.getNextFillRequest();
 
         // Wait until dataset1 is shown
-        sUiBot.assertDatasets("dataset1");
+        mUiBot.assertDatasets("dataset1");
 
         // Delete stopped marker
         getStoppedMarker(getContext()).delete();
@@ -325,7 +352,7 @@
         response = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder(
                         createPresentation("dataset2"))
-                        .setField(ID_USERNAME, "filled").build())
+                                .setField(ID_USERNAME, "filled").build())
                 .build();
         sReplier.addResponse(response);
 
@@ -338,18 +365,18 @@
         killOfProcessLoginActivityProcess();
 
         // Trigger autofill on username in nested activity
-        sUiBot.selectById(USERNAME_FULL_ID);
+        mUiBot.selectByRelativeId(ID_USERNAME);
 
         // Wait for fill request to be processed
         sReplier.getNextFillRequest();
 
         // Wait until dataset in nested activity is shown
-        sUiBot.assertDatasets("dataset2");
+        mUiBot.assertDatasets("dataset2");
 
         // Tap "Cancel".
-        sUiBot.selectById(CANCEL_FULL_ID);
+        mUiBot.selectByRelativeId(ID_CANCEL);
 
         // Dataset should still be shown
-        sUiBot.assertDatasets("dataset1");
+        mUiBot.assertDatasets("dataset1");
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
index 0866b2d..cbb8523 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
@@ -16,6 +16,8 @@
 package android.autofillservice.cts;
 
 import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -25,19 +27,24 @@
  */
 public class SimpleSaveActivity extends AbstractAutoFillActivity {
 
+    private static final String TAG = "SimpleSaveActivity";
+
     static final String ID_LABEL = "label";
     static final String ID_INPUT = "input";
     static final String ID_PASSWORD = "password";
     static final String ID_COMMIT = "commit";
     static final String TEXT_LABEL = "Label:";
 
+    private static SimpleSaveActivity sInstance;
+
     TextView mLabel;
     EditText mInput;
     EditText mPassword;
     Button mCancel;
     Button mCommit;
 
-    private static SimpleSaveActivity sInstance;
+    private boolean mAutoCommit = true;
+    private boolean mClearFieldsOnSubmit = false;
 
     public static SimpleSaveActivity getInstance() {
         return sInstance;
@@ -60,7 +67,46 @@
         mCommit = findViewById(R.id.commit);
 
         mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
-        mCommit.setOnClickListener((v) -> getAutofillManager().commit());
+        mCommit.setOnClickListener((v) -> onCommit());
+    }
+
+    private void onCommit() {
+        if (mClearFieldsOnSubmit) {
+            resetFields();
+        }
+        if (mAutoCommit) {
+            Log.d(TAG, "onCommit(): calling AFM.commit()");
+            getAutofillManager().commit();
+        } else {
+            Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
+        }
+    }
+
+    private void resetFields() {
+        Log.d(TAG, "resetFields()");
+        mInput.setText("");
+        mPassword.setText("");
+    }
+
+    /**
+     * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
+     * the commit button is tapped.
+     */
+    void setAutoCommit(boolean flag) {
+        mAutoCommit = flag;
+    }
+
+    /**
+     * Defines whether the activity should automatically clear its fields when submit is clicked.
+     */
+    void setClearFieldsOnSubmit(boolean flag) {
+        mClearFieldsOnSubmit = flag;
+    }
+
+    FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input, null);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
     }
 
     FillExpectation expectAutoFill(String input, String password) {
@@ -76,12 +122,16 @@
 
         private FillExpectation(String input, String password) {
             mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
-            mPasswordWatcher = new OneTimeTextWatcher("password", mPassword, password);
+            mPasswordWatcher = password == null
+                    ? null
+                    : new OneTimeTextWatcher("password", mPassword, password);
         }
 
         void assertAutoFilled() throws Exception {
             mInputWatcher.assertAutoFilled();
-            mPasswordWatcher.assertAutoFilled();
+            if (mPasswordWatcher != null) {
+                mPasswordWatcher.assertAutoFilled();
+            }
         }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index 24b5e4c..758220e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -15,7 +15,10 @@
  */
 package android.autofillservice.cts;
 
+import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
+import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextValue;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
 import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
@@ -26,25 +29,40 @@
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
 import android.content.Intent;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.regex.Pattern;
+
 public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
 
     @Rule
     public final AutofillActivityTestRule<SimpleSaveActivity> mActivityRule =
             new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
 
+    @Rule
+    public final AutofillActivityTestRule<WelcomeActivity> mWelcomeActivityRule =
+            new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
+
     private SimpleSaveActivity mActivity;
 
     private void startActivity() {
@@ -55,7 +73,7 @@
         final Intent intent = new Intent(mContext, SimpleSaveActivity.class);
         if (remainOnRecents) {
             intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
         }
         mActivity = mActivityRule.launchActivity(intent);
     }
@@ -64,7 +82,7 @@
         final Intent intent = new Intent(mContext.getApplicationContext(),
                 SimpleSaveActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        mContext.startActivity(intent);
+        mActivity.startActivity(intent);
     }
 
     @Test
@@ -90,7 +108,7 @@
 
         // Select dataset.
         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        sUiBot.selectDataset("YO");
+        mUiBot.selectDataset("YO");
         autofillExpecation.assertAutoFilled();
 
         mActivity.syncRunOnUiThread(() -> {
@@ -98,10 +116,10 @@
             mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
-        final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Save it...
-        sUiBot.saveForAutofill(saveUi, true);
+        mUiBot.saveForAutofill(saveUi, true);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -131,15 +149,15 @@
                 .build());
 
         // Trigger autofill.
-        sUiBot.assertShownByRelativeId(ID_INPUT).click();
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
         sReplier.getNextFillRequest();
 
         // Select dataset...
-        sUiBot.selectDataset("YO");
+        mUiBot.selectDataset("YO");
 
         // ...and assert autofilled values.
-        final UiObject2 input = sUiBot.assertShownByRelativeId(ID_INPUT);
-        final UiObject2 password = sUiBot.assertShownByRelativeId(ID_PASSWORD);
+        final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
+        final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
 
         assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
         // TODO: password field is shown as **** ; ideally we should assert it's a password
@@ -152,8 +170,8 @@
         // Trigger save...
         input.setText("ID");
         password.setText("PASS");
-        sUiBot.assertShownByRelativeId(ID_COMMIT).click();
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertShownByRelativeId(ID_COMMIT).click();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -168,11 +186,11 @@
 
     @Test
     public void testSave_afterRotation() throws Exception {
-        sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
             saveTest(true);
         } finally {
-            sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
             cleanUpAfterScreenOrientationIsBackToPortrait();
         }
     }
@@ -198,18 +216,18 @@
             mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
-        UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         if (rotate) {
             // After the device rotates, the input field get focus and generate a new session.
             sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
 
-            sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-            saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+            mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+            saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
         }
 
         // Save it...
-        sUiBot.saveForAutofill(saveUi, true);
+        mUiBot.saveForAutofill(saveUi, true);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -217,6 +235,37 @@
     }
 
     @Test
+    public void testSave_launchIntent() throws Exception {
+        startActivity();
+
+        // Set service.
+        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());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        Helper.assertHasSessions(mPackageName);
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextSaveRequest();
+        // ... and assert activity was launched
+        WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
+    }
+
+    @Test
     public void testSaveThenStartNewSessionRightAway() throws Exception {
         startActivity();
 
@@ -241,10 +290,7 @@
         });
 
         // Make sure Save UI for 1st session was canceled....
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        //... and 2nd session canceled as well.
-        Helper.assertNoDanglingSessions();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
     @Test
@@ -266,7 +312,6 @@
 
         // Cancel session.
         mActivity.getAutofillManager().cancel();
-        Helper.assertNoDanglingSessions();
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -275,7 +320,7 @@
         });
 
         // Assert it's not showing.
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
     @Test
@@ -302,20 +347,6 @@
         dismissSaveTest(DismissType.FOCUS_OUTSIDE);
     }
 
-    @Test
-    @Ignore("Test fail on some devices because Recents UI is not well defined: b/72044685")
-    public void testDismissSave_byTappingRecents() throws Exception {
-        // Launches a different activity first.
-        startWelcomeActivityOnNewTask();
-
-        // Then launches the main activity.
-        startActivity(true);
-        sUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // And finally test it..
-        dismissSaveTest(DismissType.RECENTS_BUTTON);
-    }
-
     private void dismissSaveTest(DismissType dismissType) throws Exception {
         // Set service.
         enableService();
@@ -335,31 +366,27 @@
             mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Then make sure it goes away when user doesn't want it..
         switch (dismissType) {
             case BACK_BUTTON:
-                sUiBot.pressBack();
+                mUiBot.pressBack();
                 break;
             case HOME_BUTTON:
-                sUiBot.pressHome();
+                mUiBot.pressHome();
                 break;
             case TOUCH_OUTSIDE:
-                sUiBot.assertShownByText(TEXT_LABEL).click();
+                mUiBot.assertShownByText(TEXT_LABEL).click();
                 break;
             case FOCUS_OUTSIDE:
                 mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
-                sUiBot.assertShownByText(TEXT_LABEL).click();
-                break;
-            case RECENTS_BUTTON:
-                sUiBot.switchAppsUsingRecents();
-                WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+                mUiBot.assertShownByText(TEXT_LABEL).click();
                 break;
             default:
                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
     @Test
@@ -378,25 +405,25 @@
                 .build());
 
         // Trigger autofill.
-        sUiBot.assertShownByRelativeId(ID_INPUT).click();
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
         sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("YO");
+        mUiBot.assertDatasets("YO");
         callback.assertUiShownEvent(mActivity.mInput);
 
         // Go home, you are drunk!
-        sUiBot.pressHome();
-        sUiBot.assertNoDatasets();
+        mUiBot.pressHome();
+        mUiBot.assertNoDatasets();
         callback.assertUiHiddenEvent(mActivity.mInput);
 
         // Switch back to the activity.
         restartActivity();
-        sUiBot.assertShownByText(TEXT_LABEL, Helper.ACTIVITY_RESURRECTION_MS);
-        final UiObject2 datasetPicker = sUiBot.assertDatasets("YO");
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("YO");
         callback.assertUiShownEvent(mActivity.mInput);
 
         // Now autofill it.
         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        sUiBot.selectDataset(datasetPicker, "YO");
+        mUiBot.selectDataset(datasetPicker, "YO");
         autofillExpecation.assertAutoFilled();
     }
 
@@ -413,19 +440,18 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Trigger save, but don't tap it.
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Go home, you are drunk!
-        sUiBot.pressHome();
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        Helper.assertNoDanglingSessions();
+        mUiBot.pressHome();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Prepare the response for the next session, which will be automatically triggered
         // when the activity is brought back.
@@ -440,26 +466,25 @@
 
         // Switch back to the activity.
         restartActivity();
-        sUiBot.assertShownByText(TEXT_LABEL, Helper.ACTIVITY_RESURRECTION_MS);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Trigger and select UI.
         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        sUiBot.selectDataset("YO");
+        mUiBot.selectDataset("YO");
 
         // Assert it.
         autofillExpecation.assertAutoFilled();
     }
 
     private void startWelcomeActivityOnNewTask() throws Exception {
-        final Intent intent = new Intent(mContext, WelcomeActivity.class);
-        intent.setFlags(
-                Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-        mContext.startActivity(intent);
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+        final Intent intent = new Intent(mContext, WelcomeActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mWelcomeActivityRule.launchActivity(intent);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
     }
 
     @Override
@@ -491,8 +516,8 @@
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         // .. then do something to return to previous activity...
         switch (type) {
@@ -500,25 +525,26 @@
                 // After the device rotates, the input field get focus and generate a new session.
                 sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
 
-                sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
                 // not breaking on purpose
             case TAP_BACK_BUTTON:
                 // ..then go back and save it.
-                sUiBot.pressBack();
+                mUiBot.pressBack();
                 break;
             case FINISH_ACTIVITY:
                 // ..then finishes it.
-                WelcomeActivity.finishIt();
+                WelcomeActivity.finishIt(mUiBot);
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
         }
         // Make sure previous activity is back...
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // ... and tap save.
         final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-        sUiBot.saveForAutofill(newSaveUi, true);
+        mUiBot.saveForAutofill(newSaveUi, true);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
@@ -559,38 +585,34 @@
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown.
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Tap back to restore the Save UI...
-        sUiBot.pressBack();
+        mUiBot.pressBack();
         // Make sure previous activity is back...
-        sUiBot.assertShownByRelativeId(ID_LABEL);
+        mUiBot.assertShownByRelativeId(ID_LABEL);
 
         // ...but don't tap it...
-        final UiObject2 saveUi2 = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         // ...instead, do something to dismiss it:
         switch (action) {
             case TOUCH_OUTSIDE:
-                sUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
                 break;
             case TAP_NO_ON_SAVE_UI:
-                sUiBot.saveForAutofill(saveUi2, false);
+                mUiBot.saveForAutofill(saveUi2, false);
                 break;
             case TAP_YES_ON_SAVE_UI:
-                sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
                 final SaveRequest saveRequest = sReplier.getNextSaveRequest();
                 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-                Helper.assertNoDanglingSessions();
                 break;
             default:
                 throw new IllegalArgumentException("invalid action: " + action);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Make sure previous session was finished.
-        Helper.assertNoDanglingSessions();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Now triggers a new session and do business as usual...
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -614,7 +636,7 @@
         });
 
         // Save it...
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -624,7 +646,7 @@
     @Override
     protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
             throws Exception {
-        startActivity(type == PostSaveLinkTappedAction.TAP_RECENTS);
+        startActivity(false);
         // Set service.
         enableService();
 
@@ -649,30 +671,85 @@
         // Tap the link.
         tapSaveUiLink(saveUi);
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
 
         switch (type) {
-            case TAP_RECENTS:
-                sUiBot.switchAppsUsingRecents();
-                break;
             case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivity(SimpleSaveActivity.class);
+                startActivityOnNewTask(SimpleSaveActivity.class);
                 break;
             case LAUNCH_NEW_ACTIVITY:
                 // Launch a 3rd activity...
-                startActivity(LoginActivity.class);
-                sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
                 // ...then go back
-                sUiBot.pressBack();
+                mUiBot.pressBack();
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
         }
         // Make sure right activity is showing
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
 
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                // Added on reversed order on purpose
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D2")
+                        .setField(ID_INPUT, "id again")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("D2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D1")
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("D1"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Select 1st dataset.
+        final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
+        final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
+        mUiBot.selectDataset(picker1, "D1");
+        autofillExpecation1.assertAutoFilled();
+
+        // Select 2nd dataset.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
+        final UiObject2 picker2 = mUiBot.assertDatasets("D2");
+        mUiBot.selectDataset(picker2, "D2");
+        autofillExpecation2.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+        assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
     }
 
     @Override
@@ -708,14 +785,16 @@
         tapSaveUiLink(saveUi);
 
         // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
 
         // Save UI should be showing as well, since Trampoline finished.
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
+        // Dismiss Save Dialog
+        mUiBot.pressBack();
         // Go back and make sure it's showing the right activity.
-        sUiBot.pressBack();
-        sUiBot.assertShownByRelativeId(ID_LABEL);
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_LABEL);
 
         // Now start a new session.
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -727,9 +806,327 @@
             mActivity.mPassword.setText("42");
             mActivity.mCommit.performClick();
         });
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
     }
+
+    @Test
+    public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId inputId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        Helper.assertHasSessions(mPackageName);
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    public void testSanitizeOnSaveNoChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId inputId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("#id#");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId inputId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"), passwordId)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId inputId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                        inputId, passwordId)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "#id#")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId inputId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                        inputId, passwordId)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    public void testExplicitySaveButton() throws Exception {
+        explicitySaveButtonTest(false, 0);
+    }
+
+    @Test
+    public void testExplicitySaveButtonWhenAppClearFields() throws Exception {
+        explicitySaveButtonTest(true, 0);
+    }
+
+    @Test
+    public void testExplicitySaveButtonOnly() throws Exception {
+        explicitySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
+    }
+
+    /**
+     * Tests scenario where service explicitly indicates which button is used to save.
+     */
+    private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+        startActivity();
+        mActivity.setAutoCommit(false);
+        mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveTriggerId(mActivity.mCommit.getAutofillId())
+                .setSaveInfoFlags(flags)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        Helper.assertHasSessions(mPackageName);
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+    }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CustomDescription.Builder customDescription =
+                newCustomDescriptionBuilder(WelcomeActivity.class);
+        final RemoteViews update = newTemplate();
+        if (updateLinkView) {
+            update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+        } else {
+            update.setCharSequence(R.id.static_text, "setText", "ME!");
+        }
+        Validator validCondition = new RegexValidator(mActivity.mInput.getAutofillId(),
+                Pattern.compile(".*"));
+        customDescription.batchUpdate(validCondition,
+                new BatchUpdates.Builder().updateTemplate(update).build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setCustomDescription(customDescription.build())
+                .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();
+        });
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
new file mode 100644
index 0000000..572e3b1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.TextValueSanitizer;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class TextValueSanitizerTest {
+
+    @Test
+    public void testConstructor_nullValues() {
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(Pattern.compile("42"), null));
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(null, "42"));
+    }
+
+    @Test
+    public void testSanitize_nullValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_nonTextValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        final AutofillValue value = AutofillValue.forToggle(true);
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_badRegex() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "$2"); // invalid group
+        final AutofillValue value = AutofillValue.forText("blah 42  blaH");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_valueMismatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
+        final AutofillValue value = AutofillValue.forText("43");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_simpleMatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
+                "forty-two");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+            .isEqualTo("forty-two");
+    }
+
+    @Test
+    public void testSanitize_multipleMatches() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "Number");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42  blaH")).getTextValue())
+            .isEqualTo("NumberNumber");
+    }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
+                .isEqualTo("42");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
index d4be2e6..4bd8cf0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
@@ -31,7 +31,6 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.icu.util.Calendar;
 
-import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -42,11 +41,6 @@
 
     protected abstract T getTimePickerActivity();
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testAutoFillAndSave() throws Exception {
         final T activity = getTimePickerActivity();
@@ -78,7 +72,7 @@
         assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
         assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
         // Auto-fill it.
-        sUiBot.selectDataset("Adventure Time");
+        mUiBot.selectDataset("Adventure Time");
 
         // Check the results.
         activity.assertAutoFilled();
@@ -87,7 +81,7 @@
         activity.setTime(10, 40);
         activity.tapOk();
 
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(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
new file mode 100644
index 0000000..e709dac
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
@@ -0,0 +1,181 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A "smart" timeout that supports exponential backoff.
+ */
+//TODO: move to common CTS Code
+public final class Timeout {
+
+    private static final String TAG = "Timeout";
+    private static final boolean VERBOSE = true;
+
+    private final String mName;
+    private long mCurrentValue;
+    private final float mMultiplier;
+    private final long mMaxValue;
+
+    /**
+     * Default constructor.
+     *
+     * @param name name to be used for logging purposes.
+     * @param initialValue initial timeout value, in ms.
+     * @param multiplier multiplier for {@link #increase()}.
+     * @param maxValue max timeout value (in ms) set by {@link #increase()}.
+     *
+     * @throws IllegalArgumentException if {@code name} is {@code null} or empty,
+     * {@code initialValue}, {@code multiplir} or {@code maxValue} are less than {@code 1},
+     * or if {@code initialValue} is higher than {@code maxValue}
+     */
+    public Timeout(String name, long initialValue, float multiplier, long maxValue) {
+        if (initialValue < 1 || maxValue < 1 || initialValue > maxValue) {
+            throw new IllegalArgumentException(
+                    "invalid initial and/or max values: " + initialValue + " and " + maxValue);
+        }
+        if (multiplier <= 1) {
+            throw new IllegalArgumentException("multiplier must be higher than 1: " + multiplier);
+        }
+        if (TextUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("no name");
+        }
+        mName = name;
+        mCurrentValue = initialValue;
+        mMultiplier = multiplier;
+        mMaxValue = maxValue;
+        Log.d(TAG, "Constructor: " + this + " at " + JUnitHelper.getCurrentTestName());
+    }
+
+    /**
+     * Gets the current timeout, in ms.
+     */
+    public long ms() {
+        return mCurrentValue;
+    }
+
+    /**
+     * Gets the max timeout, in ms.
+     */
+    public long getMaxValue() {
+        return mMaxValue;
+    }
+
+    /**
+     * @return the mMultiplier
+     */
+    public float getMultiplier() {
+        return mMultiplier;
+    }
+
+    /**
+     * Gets the user-friendly name of this timeout.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Increases the current value by the {@link #getMultiplier()}, up to {@link #getMaxValue()}.
+     *
+     * @return previous current value.
+     */
+    public long increase() {
+        final long oldValue = mCurrentValue;
+        mCurrentValue = Math.min(mMaxValue, (long) (mCurrentValue * mMultiplier));
+        if (oldValue != mCurrentValue) {
+            Log.w(TAG, mName + " increased from " + oldValue + "ms to " + mCurrentValue + "ms at "
+                    + JUnitHelper.getCurrentTestName());
+        }
+        return oldValue;
+    }
+
+    /**
+     * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
+     * {@link #ms()}.
+     *
+     * @param description description of the job for logging purposes.
+     * @param job job to be run, must return {@code null} if it failed and should be retried.
+     * @throws RetryableException if all attempts failed.
+     * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
+     * {@code job} is {@code  null}, or if {@code maxAttempts} is less than 1.
+     * @throws Exception any other exception thrown by helper methods.
+     *
+     * @return job's result.
+     */
+    public <T> T run(String description, Callable<T> job) throws Exception {
+        return run(description, 100, job);
+    }
+
+    /**
+     * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
+     * {@link #ms()}.
+     *
+     * @param description description of the job for logging purposes.
+     * @param job job to be run, must return {@code null} if it failed and should be retried.
+     * @param retryMs how long to sleep between failures.
+     * @throws RetryableException if all attempts failed.
+     * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
+     * {@code job} is {@code  null}, or if {@code maxAttempts} is less than 1.
+     * @throws Exception any other exception thrown by helper methods.
+     *
+     * @return job's result.
+     */
+    public <T> T run(String description, long retryMs, Callable<T> job) throws Exception {
+        if (TextUtils.isEmpty(description)) {
+            throw new IllegalArgumentException("no description");
+        }
+        if (job == null) {
+            throw new IllegalArgumentException("no job");
+        }
+        if (retryMs < 1) {
+            throw new IllegalArgumentException("need to sleep at least 1ms, right?");
+        }
+        long startTime = System.currentTimeMillis();
+        int attempt = 0;
+        while (System.currentTimeMillis() - startTime <= mCurrentValue) {
+            final T result = job.call();
+            if (result != null) {
+                // Good news, everyone: job succeeded on first attempt!
+                return result;
+            }
+            attempt++;
+            if (VERBOSE) {
+                Log.v(TAG, description + " failed at attempt #" + attempt + "; sleeping for "
+                        + retryMs + "ms before trying again");
+            }
+            SystemClock.sleep(retryMs);
+            retryMs *= mMultiplier;
+        }
+        Log.w(TAG, description + " failed after " + attempt + " attempts and "
+                + (System.currentTimeMillis() - startTime) + "ms: " + this);
+        throw new RetryableException(this, description);
+    }
+
+    @Override
+    public String toString() {
+        return mName + ": [current=" + mCurrentValue + "ms; multiplier=" + mMultiplier + "x; max="
+                + mMaxValue + "ms]";
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
new file mode 100644
index 0000000..69c69e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertFloat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.Callable;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TimeoutTest {
+
+    private static final String NAME = "TIME, Y U NO OUT?";
+    private static final String DESC = "something";
+
+    @Mock
+    private Callable<Object> mJob;
+
+    @Test
+    public void testInvalidConstructor() {
+        // Invalid name
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(null, 1, 2, 2));
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout("", 1, 2, 2));
+        // Invalid initial value
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, -1, 2, 2));
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 0, 2, 2));
+        // Invalid multiplier
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, -1, 2));
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 0, 2));
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 1, 2));
+        // Invalid max value
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, -1));
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, 0));
+        // Max value cannot be less than initial
+        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 2, 2, 1));
+    }
+
+    @Test
+    public void testGetters() {
+        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+        assertThat(timeout.ms()).isEqualTo(1);
+        assertFloat(timeout.getMultiplier(), 2);
+        assertThat(timeout.getMaxValue()).isEqualTo(5);
+        assertThat(timeout.getName()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void testIncrease() {
+        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+        // Pre-maximum
+        assertThat(timeout.increase()).isEqualTo(1);
+        assertThat(timeout.ms()).isEqualTo(2);
+        assertThat(timeout.increase()).isEqualTo(2);
+        assertThat(timeout.ms()).isEqualTo(4);
+        // Post-maximum
+        assertThat(timeout.increase()).isEqualTo(4);
+        assertThat(timeout.ms()).isEqualTo(5);
+        assertThat(timeout.increase()).isEqualTo(5);
+        assertThat(timeout.ms()).isEqualTo(5);
+    }
+
+    @Test
+    public void testRun_invalidArgs() {
+        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+        // Invalid description
+        assertThrows(IllegalArgumentException.class, ()-> timeout.run(null, mJob));
+        assertThrows(IllegalArgumentException.class, ()-> timeout.run("", mJob));
+        // Invalid max attempts
+        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, -1, mJob));
+        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, 0, mJob));
+        // Invalid job
+        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, null));
+    }
+
+    @Test
+    public void testRun_successOnFirstAttempt() throws Exception {
+        final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+        final Object result = new Object();
+        when(mJob.call()).thenReturn(result);
+        assertThat(timeout.run(DESC, 1, mJob)).isSameAs(result);
+    }
+
+    @Test
+    public void testRun_successOnSecondAttempt() throws Exception {
+        final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+        final Object result = new Object();
+        when(mJob.call()).thenReturn((Object) null, result);
+        assertThat(timeout.run(DESC, 10, mJob)).isSameAs(result);
+    }
+
+    @Test
+    public void testRun_allAttemptsFailed() throws Exception {
+        final Timeout timeout = new Timeout(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);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
new file mode 100644
index 0000000..ae12356
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+/**
+ * Timeouts for common tasks.
+ */
+final class Timeouts {
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout until framework unbinds from a service.
+     */
+    static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout to get the expected number of fill events.
+     */
+    static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout for expected autofill requests.
+     */
+    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout for expected save requests.
+     */
+    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * 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;
+
+    /**
+     * Timeout for UI operations. Typically used by {@link UiBot}.
+     */
+    static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout for webview operations. Typically used by {@link UiBot}.
+     */
+    static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 8000, 2F, 16000);
+
+    /**
+     * Timeout for showing the autofill dataset picker UI.
+     *
+     * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
+     * dataset picker UI can be affect by external factors in some low-level devices.
+     *
+     * <p>Typically used by {@link UiBot}.
+     */
+    static final Timeout UI_DATASET_PICKER_TIMEOUT =
+            new Timeout("UI_DATASET_PICKER_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * 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;
+
+    /**
+     * Timeout (in milliseconds) for an activity to be brought out to top.
+     */
+    static final Timeout ACTIVITY_RESURRECTION =
+            new Timeout("ACTIVITY_RESURRECTION", 6000, 3F, 20000);
+
+    /**
+     * Timeout for changing the screen orientation.
+     */
+    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT =
+            new Timeout("UI_SCREEN_ORIENTATION_TIMEOUT", 5000, 2F, 10000);
+
+    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 20dc01e..ec60cc8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -16,10 +16,12 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.NOT_SHOWING_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.UI_RECENTS_SWITCH_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.Timeouts.SAVE_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
+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.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;
@@ -36,29 +38,39 @@
 import android.content.res.Resources;
 import android.os.SystemClock;
 import android.service.autofill.SaveInfo;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+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.Until;
 import android.text.Html;
 import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Helper for UI-related needs.
  */
 final class UiBot {
 
+    private static final String TAG = "AutoFillCtsUiBot";
+
     private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
     private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
     private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
     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_STRING_SAVE_TITLE = "autofill_save_title";
     private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
@@ -70,13 +82,21 @@
     private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
     private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
             "autofill_save_type_email_address";
+    private static final String RESOURCE_STRING_SAVE_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_AUTOFILL = "autofill";
     private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
             "autofill_picker_accessibility_title";
     private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
             "autofill_save_accessibility_title";
 
-    private static final String TAG = "AutoFillCtsUiBot";
+    private static final BySelector DATASET_PICKER_SELECTOR = By.res("android",
+            RESOURCE_ID_DATASET_PICKER);
+    private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+
+    private static final boolean DONT_DUMP_ON_ERROR = false;
+    private static final boolean DUMP_ON_ERROR = true;
 
     /** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
     public static int PORTRAIT = 0;
@@ -84,32 +104,56 @@
     /** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
     public static int LANDSCAPE = 1;
 
-
     private final UiDevice mDevice;
     private final Context mContext;
     private final String mPackageName;
     private final UiAutomation mAutoman;
+    private final Timeout mDefaultTimeout;
 
-    UiBot(Instrumentation instrumentation) throws Exception {
+    private boolean mOkToCallAssertNoDatasets;
+
+    UiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    UiBot(Timeout defaultTimeout) {
+        mDefaultTimeout = defaultTimeout;
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(instrumentation);
         mContext = instrumentation.getContext();
         mPackageName = mContext.getPackageName();
         mAutoman = instrumentation.getUiAutomation();
     }
 
+    void reset() {
+        mOkToCallAssertNoDatasets = false;
+    }
+
     /**
-     * Asserts the dataset chooser is not shown.
+     * Asserts the dataset picker is not shown anymore.
+     *
+     * @throws IllegalStateException if called *before* an assertion was made to make sure the
+     * dataset picker is shown - if that's not the case, call
+     * {@link #assertNoDatasetsEver()} instead.
      */
-    void assertNoDatasets() {
-        final UiObject2 picker;
-        try {
-            picker = findDatasetPicker(NOT_SHOWING_TIMEOUT_MS);
-        } catch (Throwable t) {
-            // Use a more elegant check than catching the expection because it's not showing...
-            return;
+    void assertNoDatasets() throws Exception {
+        if (!mOkToCallAssertNoDatasets) {
+            throw new IllegalStateException(
+                    "Cannot call assertNoDatasets() without calling assertDatasets first");
         }
-        throw new RetryableException(
-                "Should not be showing datasets, but got " + getChildrenAsText(picker));
+        assertNotShowingAnymore("datasets", DATASET_PICKER_SELECTOR, UI_DATASET_PICKER_TIMEOUT);
+        mOkToCallAssertNoDatasets = false;
+    }
+
+    /**
+     * Asserts the dataset picker was never shown.
+     *
+     * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    void assertNoDatasetsEver() throws Exception {
+        assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
     }
 
     /**
@@ -117,10 +161,31 @@
      *
      * @return the dataset picker object.
      */
-    UiObject2 assertDatasets(String...names) {
-        final UiObject2 picker = findDatasetPicker();
+    UiObject2 assertDatasets(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
         assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
-                .containsExactlyElementsIn(Arrays.asList(names));
+                .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+     *
+     * @return the dataset picker object.
+     */
+    UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
+            throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        final List<String> expectedChild = new ArrayList<>();
+        if (header != null) {
+            expectedChild.add(header);
+        }
+        expectedChild.addAll(Arrays.asList(names));
+        if (footer != null) {
+            expectedChild.add(footer);
+        }
+        assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
+                .containsExactlyElementsIn(expectedChild).inOrder();
         return picker;
     }
 
@@ -146,8 +211,8 @@
     /**
      * Selects a dataset that should be visible in the floating UI.
      */
-    void selectDataset(String name) {
-        final UiObject2 picker = findDatasetPicker();
+    void selectDataset(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
         selectDataset(picker, name);
     }
 
@@ -168,7 +233,7 @@
      * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
      * {@link #selectDataset(String)}.
      */
-    void selectByText(String name) {
+    void selectByText(String name) throws Exception {
         Log.v(TAG, "selectByText(): " + name);
 
         final UiObject2 object = waitForObject(By.text(name));
@@ -181,12 +246,12 @@
      * <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
      * {@link #assertDatasets(String...)}.
      */
-    public UiObject2 assertShownByText(String text) {
-        return assertShownByText(text, UI_TIMEOUT_MS);
+    public UiObject2 assertShownByText(String text) throws Exception {
+        return assertShownByText(text, mDefaultTimeout);
     }
 
-    public UiObject2 assertShownByText(String text, int timeoutMs) {
-        final UiObject2 object = waitForObject(By.text(text), timeoutMs);
+    public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
+        final UiObject2 object = waitForObject(By.text(text), timeout);
         assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
         return object;
     }
@@ -195,7 +260,7 @@
      * Asserts a node with the given content description is shown.
      *
      */
-    public UiObject2 assertShownByContentDescription(String contentDescription) {
+    public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
         final UiObject2 object = waitForObject(By.desc(contentDescription));
         assertWithMessage("No node with content description '%s'", contentDescription).that(object)
                 .isNotNull();
@@ -214,63 +279,111 @@
     /**
      * Selects a view by id.
      */
-    void selectById(String id) {
-        Log.v(TAG, "selectById(): " + id);
-
-        final UiObject2 view = waitForObject(By.res(id));
-        view.click();
+    void selectByRelativeId(String id) throws Exception {
+        Log.v(TAG, "selectByRelativeId(): " + id);
+        waitForObject(By.res(mPackageName, id)).click();
     }
 
     /**
      * Asserts the id is shown on the screen.
      */
-    void assertShownById(String id) {
+    void assertShownById(String id) throws Exception {
         assertThat(waitForObject(By.res(id))).isNotNull();
     }
 
     /**
      * Asserts the id is shown on the screen, using a resource id from the test package.
      */
-    UiObject2 assertShownByRelativeId(String id) {
-        final UiObject2 obj = waitForObject(By.res(mPackageName, id));
+    UiObject2 assertShownByRelativeId(String id) throws Exception {
+        return assertShownByRelativeId(id, mDefaultTimeout);
+    }
+
+    UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
+        final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
         assertThat(obj).isNotNull();
         return obj;
     }
+    /**
+     * Asserts the id is not shown on the screen 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(String id, Timeout timeout) {
+        boolean gone = mDevice.wait(Until.gone(By.res(mPackageName, id)), timeout.ms());
+        if (!gone) {
+            final String message = "Object with id '" + id + "' should be gone after "
+                    + timeout + " ms";
+            dumpScreen(message);
+            throw new RetryableException(message);
+        }
+    }
 
     /**
+     * Asserts that a {@code selector} is not showing *anymore* after {@code timeout} milliseconds.
+     *
+     * <p><b>Note:</b> this method should only be called when the object was known to be shown
+     * *before*, otherwise it might pass when it should failed. If the object was *never* expected
+     * to be shown, you should use {@link #assertNeverShown(String, BySelector, long)}.
+     */
+    private void assertNotShowingAnymore(String description, BySelector selector, Timeout timeout)
+            throws Exception {
+        final UiObject2 object;
+        try {
+            object = waitForObject(null, selector, timeout, DONT_DUMP_ON_ERROR);
+        } catch (RetryableException t) {
+            // Not found as expected.
+            return;
+        }
+        throw new RetryableException(timeout, "Should not be showing %s, but got %s",
+                description, getChildrenAsText(object));
+    }
+
+    /**
+     * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
+     */
+    private void assertNeverShown(String description, BySelector selector, long timeout)
+            throws Exception {
+        SystemClock.sleep(timeout);
+        final UiObject2 object = mDevice.findObject(selector);
+        if (object != null) {
+            throw new AssertionError(
+                    String.format("Should not be showing %s after %dms, but got %s",
+                            description, timeout, getChildrenAsText(object)));
+        }
+    }
+    /**
      * Gets the text set on a view.
      */
-    String getTextById(String id) {
-        final UiObject2 obj = waitForObject(By.res(id));
-        return obj.getText();
+    String getTextByRelativeId(String id) throws Exception {
+        return waitForObject(By.res(mPackageName, id)).getText();
     }
 
     /**
      * Focus in the view with the given resource id.
      */
-    void focusByRelativeId(String id) {
+    void focusByRelativeId(String id) throws Exception {
         waitForObject(By.res(mPackageName, id)).click();
     }
 
     /**
      * Sets a new text on a view.
      */
-    void setTextById(String id, String newText) {
-        UiObject2 view = waitForObject(By.res(id));
-        view.setText(newText);
+    void setTextByRelativeId(String id, String newText) throws Exception {
+        waitForObject(By.res(mPackageName, id)).setText(newText);
     }
 
     /**
      * Asserts the save snackbar is showing and returns it.
      */
-    UiObject2 assertSaveShowing(int type) {
-        return assertSaveShowing(SAVE_TIMEOUT_MS, type);
+    UiObject2 assertSaveShowing(int type) throws Exception {
+        return assertSaveShowing(SAVE_TIMEOUT, type);
     }
 
     /**
      * Asserts the save snackbar is showing and returns it.
      */
-    UiObject2 assertSaveShowing(long timeout, int type) {
+    UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
         return assertSaveShowing(null, timeout, type);
     }
 
@@ -291,34 +404,10 @@
     }
 
     /**
-     * Uses the Recents button to switch back to previous activity
+     * Asserts the save snackbar is not showing.
      */
-    void switchAppsUsingRecents() throws Exception {
-        Log.d(TAG, "switchAppsUsingRecents()");
-
-        // Press once to show list of apps...
-        mDevice.pressRecentApps();
-
-        // ...wait until apps are shown...
-        // TODO(b/37566627): figure out a way to wait for a specific UI instead.
-        SystemClock.sleep(UI_RECENTS_SWITCH_TIMEOUT_MS);
-
-        // ...press again to go back to the activity.
-        mDevice.pressRecentApps();
-    }
-
-    /**
-     * Asserts the save snackbar is not showing and returns it.
-     */
-    void assertSaveNotShowing(int type) {
-        try {
-            assertSaveShowing(NOT_SHOWING_TIMEOUT_MS, type);
-        } catch (Throwable t) {
-            // TODO: use a more elegant check than catching the expection because it's not showing
-            // (in which case it wouldn't need a type as parameter).
-            return;
-        }
-        throw new RetryableException("snack bar is showing");
+    void assertSaveNotShowing(int type) throws Exception {
+        assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
     }
 
     private String getSaveTypeString(int type) {
@@ -345,40 +434,40 @@
         return getString(typeResourceName);
     }
 
-    UiObject2 assertSaveShowing(String description, int... types) {
+    UiObject2 assertSaveShowing(String description, int... types) throws Exception {
         return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description,
-                SAVE_TIMEOUT_MS, types);
+                SAVE_TIMEOUT, types);
     }
 
-    UiObject2 assertSaveShowing(String description, long timeout, int... types) {
+    UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
+            throws Exception {
         return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description, timeout,
                 types);
     }
 
     UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
-            int... types) {
-        return assertSaveShowing(negativeButtonStyle, description, SAVE_TIMEOUT_MS, types);
+            int... types) throws Exception {
+        return assertSaveShowing(negativeButtonStyle, description, SAVE_TIMEOUT, types);
     }
 
-    UiObject2 assertSaveShowing(int negativeButtonStyle, String description, long timeout,
-            int... types) {
-        final UiObject2 snackbar = waitForObject(By.res("android", RESOURCE_ID_SAVE_SNACKBAR),
-                timeout);
+    UiObject2 assertSaveShowing(int negativeButtonStyle, String description, Timeout timeout,
+            int... types) throws Exception {
+        final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
 
         final UiObject2 titleView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), UI_TIMEOUT_MS);
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
         assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
                 .isNotNull();
 
         final UiObject2 iconView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), UI_TIMEOUT_MS);
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
         assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
                 .isNotNull();
 
         final String actualTitle = titleView.getText();
         Log.d(TAG, "save title: " + actualTitle);
 
-        final String serviceLabel = InstrumentedAutoFillService.class.getSimpleName();
+        final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
         switch (types.length) {
             case 1:
                 final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
@@ -408,11 +497,15 @@
             assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
         }
 
-        final String negativeButtonText = (negativeButtonStyle
-                == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) ? "NOT NOW" : "NO THANKS";
-        UiObject2 negativeButton = snackbar.findObject(By.text(negativeButtonText));
-        assertWithMessage("negative button (%s)", negativeButtonText)
-                .that(negativeButton).isNotNull();
+        final String negativeButtonStringId =
+                (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT)
+                ? RESOURCE_STRING_SAVE_BUTTON_NOT_NOW
+                : RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+        final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
+        final UiObject2 negativeButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
+        assertWithMessage("wrong text on negative button")
+                .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
 
         final String expectedAccessibilityTitle =
                 getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
@@ -427,7 +520,7 @@
      * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
      * @param types expected types of save info.
      */
-    void saveForAutofill(boolean yesDoIt, int... types) {
+    void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
         final UiObject2 saveSnackBar = assertSaveShowing(
                 SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
         saveForAutofill(saveSnackBar, yesDoIt);
@@ -439,7 +532,7 @@
      * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
      * @param types expected types of save info.
      */
-    void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types) {
+    void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types) throws Exception {
         final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle,null, types);
         saveForAutofill(saveSnackBar, yesDoIt);
     }
@@ -469,14 +562,14 @@
      *
      * @param id resource id of the field.
      */
-    UiObject2 getAutofillMenuOption(String id) {
+    UiObject2 getAutofillMenuOption(String id) 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(
-                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM));
-        final String expectedText = getString(RESOURCE_STRING_AUTOFILL);
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+        final String expectedText = getAutofillContextualMenuTitle();
         final StringBuffer menuNames = new StringBuffer();
         for (UiObject2 menuItem : menuItems) {
             final String menuName = menuItem.getText();
@@ -488,6 +581,10 @@
         throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
     }
 
+    String getAutofillContextualMenuTitle() {
+        return getString(RESOURCE_STRING_AUTOFILL);
+    }
+
     /**
      * Gets a string from the Android resources.
      */
@@ -511,8 +608,8 @@
      *
      * @param selector {@link BySelector} that identifies the object.
      */
-    private UiObject2 waitForObject(BySelector selector) {
-        return waitForObject(selector, UI_TIMEOUT_MS);
+    private UiObject2 waitForObject(BySelector selector) throws Exception {
+        return waitForObject(selector, mDefaultTimeout);
     }
 
     /**
@@ -520,24 +617,30 @@
      *
      * @param parent where to find the object (or {@code null} to use device's root).
      * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms
+     * @param timeout timeout in ms.
+     * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
      */
-    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, long timeout) {
+    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
+            boolean dumpOnError) throws Exception {
         // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        final int maxTries = 5;
-        final long napTime = timeout / maxTries;
-        for (int i = 1; i <= maxTries; i++) {
-            final UiObject2 uiObject = parent != null
-                    ? parent.findObject(selector)
-                    : mDevice.findObject(selector);
-            if (uiObject != null) {
-                return uiObject;
-            }
-            SystemClock.sleep(napTime);
-        }
-        throw new RetryableException("Object with selector '%s' not found in %d ms",
-                selector, UI_TIMEOUT_MS);
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                return parent != null
+                        ? parent.findObject(selector)
+                        : mDevice.findObject(selector);
 
+            });
+        } catch (RetryableException e) {
+            if (dumpOnError) {
+                dumpScreen("waitForObject() for " + selector + "failed");
+            }
+            throw e;
+        }
+    }
+
+    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout)
+            throws Exception {
+        return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
     }
 
     /**
@@ -546,17 +649,25 @@
      * @param selector {@link BySelector} that identifies the object.
      * @param timeout timeout in ms
      */
-    private UiObject2 waitForObject(BySelector selector, long timeout) {
+    private UiObject2 waitForObject(BySelector selector, Timeout timeout) throws Exception {
         return waitForObject(null, selector, timeout);
     }
 
     /**
-     * Waits for and returns a list of objects.
-     *
-     * @param selector {@link BySelector} that identifies the object.
+     * 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)
      */
-    private List<UiObject2> waitForObjects(BySelector selector) {
-        return waitForObjects(selector, UI_TIMEOUT_MS);
+    public void waitForWindowChange(Runnable runnable, long timeoutMillis) throws TimeoutException {
+        mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
+            switch (event.getEventType()) {
+                case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+                    return true;
+            }
+            return false;
+        }, timeoutMillis);
     }
 
     /**
@@ -565,32 +676,34 @@
      * @param selector {@link BySelector} that identifies the object.
      * @param timeout timeout in ms
      */
-    private List<UiObject2> waitForObjects(BySelector selector, long timeout) {
+    private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
         // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        final int maxTries = 5;
-        final long napTime = timeout / maxTries;
-        for (int i = 1; i <= maxTries; i++) {
-            final List<UiObject2> uiObjects = mDevice.findObjects(selector);
-            if (uiObjects != null && !uiObjects.isEmpty()) {
-                return uiObjects;
-            }
-            SystemClock.sleep(napTime);
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                final List<UiObject2> uiObjects = mDevice.findObjects(selector);
+                if (uiObjects != null && !uiObjects.isEmpty()) {
+                    return uiObjects;
+                }
+                return null;
+
+            });
+
+        } catch (RetryableException e) {
+            dumpScreen("waitForObjects() for " + selector + "failed");
+            throw e;
         }
-        throw new RetryableException("Objects with selector '%s' not found in %d ms",
-                selector, UI_TIMEOUT_MS);
     }
 
-    private UiObject2 findDatasetPicker() {
-        return findDatasetPicker(UI_TIMEOUT_MS);
-    }
-
-    private UiObject2 findDatasetPicker(long timeout) {
-        final UiObject2 picker = waitForObject(By.res("android", RESOURCE_ID_DATASET_PICKER),
-                timeout);
+    private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
+        final UiObject2 picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
 
         final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
         assertAccessibilityTitle(picker, expectedTitle);
 
+        if (picker != null) {
+            mOkToCallAssertNoDatasets = true;
+        }
+
         return picker;
     }
 
@@ -616,27 +729,12 @@
      *
      * @throws RetryableException if value didn't change.
      */
-    public void setScreenOrientation(int orientation) {
+    public void setScreenOrientation(int orientation) throws Exception {
         mAutoman.setRotation(orientation);
 
-        long startTime = System.currentTimeMillis();
-
-        while (System.currentTimeMillis() - startTime <= Helper.UI_SCREEN_ORIENTATION_TIMEOUT_MS) {
-            final int actualValue = getScreenOrientation();
-            if (actualValue == orientation) {
-                return;
-            }
-            Log.w(TAG, "setScreenOrientation(): sleeping " + Helper.RETRY_MS
-                    + "ms until orientation is " + orientation
-                    + " (instead of " + actualValue + ")");
-            try {
-                Thread.sleep(Helper.RETRY_MS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-        }
-        throw new RetryableException("Screen orientation didn't change to %d in %d ms", orientation,
-                Helper.UI_SCREEN_ORIENTATION_TIMEOUT_MS);
+        UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
+            return getScreenOrientation() == orientation ? Boolean.TRUE : null;
+        });
     }
 
     /**
@@ -651,7 +749,45 @@
     /**
      * Dumps the current view hierarchy int the output stream.
      */
-    public void dumpScreen() throws IOException {
-        mDevice.dumpWindowHierarchy(System.out);
+    public void dumpScreen(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);
+                }
+            }
+        } catch (IOException e) {
+            // Just ignore it...
+            Log.e(TAG, "exception dumping window hierarchy", e);
+            return;
+        }
+    }
+
+    /**
+     * Asserts the contents of a child element.
+     *
+     * @param parent parent object
+     * @param childId (relative) resource id of the child
+     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
+     * child with it.
+     */
+    public void assertChild(@NonNull UiObject2 parent, @NonNull String childId,
+            @Nullable Visitor<UiObject2> assertion) {
+        final UiObject2 child = parent.findObject(By.res(mPackageName, childId));
+        if (assertion != null) {
+            assertWithMessage("Didn't find child with id '%s'", childId).that(child).isNotNull();
+            try {
+                assertion.visit(child);
+            } catch (Throwable t) {
+                throw new AssertionError("Error on child '" + childId + "'", t);
+            }
+        } else {
+            assertWithMessage("Shouldn't find child with id '%s'", childId).that(child).isNull();
+        }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
new file mode 100644
index 0000000..59f57cf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+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 com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.content.Context;
+import android.service.autofill.UserData;
+import android.support.test.InstrumentationRegistry;
+
+import com.google.common.base.Strings;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataTest {
+
+    private static final Context sContext = InstrumentationRegistry.getContext();
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxCategoriesSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "2");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+
+    private UserData.Builder mBuilder;
+
+    @Before
+    public void setFixtures() {
+        mBuilder = new UserData.Builder(mId, mValue, mCategoryId);
+    }
+
+    @Test
+    public void testBuilder_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(null, mValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder("", mValue, mCategoryId));
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(mId, null, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, "", mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> new UserData.Builder(mId, mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mId, mValue, ""));
+    }
+
+    @Test
+    public void testAdd_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> mBuilder.add(mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mValue, ""));
+    }
+
+    @Test
+    public void testAdd_duplicatedValue() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId));
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
+    }
+
+    @Test
+    public void testAdd_maximumCategoriesReached() {
+        // Max is 2; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue3, mCategoryId3));
+    }
+
+    @Test
+    public void testAdd_maximumUserDataReached() {
+        // Max is 4; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId);
+        mBuilder.add(mValue3, mCategoryId);
+        mBuilder.add(mValue4, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue5, mCategoryId2));
+    }
+
+    @Test
+    public void testSetFcAlgorithm() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThat(userData.getFieldClassificationAlgorithm()).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();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        testBuild_valid();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationAlgorithm("algo_mas", null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
index 21ebac2..829302d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
@@ -18,25 +18,26 @@
 
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.service.autofill.InternalValidator;
 import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.Validator;
-import android.service.autofill.Validators;
-import android.support.annotation.NonNull;
+import android.service.autofill.ValueFinder;
 import android.view.View;
 import android.view.autofill.AutofillId;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-import java.util.function.BiFunction;
-import java.util.regex.Pattern;
-
+/**
+ * Simple integration test to verify that the UI is only shown if the validator passes.
+ */
 public class ValidatorTest extends AutoFillServiceTestCase {
     @Rule
     public final AutofillActivityTestRule<LoginActivity> mActivityRule =
@@ -49,29 +50,30 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
+    @Test
+    public void testShowUiWhenValidatorPass() throws Exception {
+        integrationTest(true);
     }
 
-    /**
-     * Base test
-     *
-     * @param validatorBuilder method to build a validator
-     * @param willSaveBeShown  Whether the save pop-up will be shown
-     */
-    private void testValidator(
-            @NonNull BiFunction<AutofillId, AutofillId, Validator> validatorBuilder,
-            boolean willSaveBeShown) throws Exception {
+    @Test
+    public void testDontShowUiWhenValidatorFails() throws Exception {
+        integrationTest(false);
+    }
+
+    private void integrationTest(boolean willSaveBeShown) throws Exception {
         enableService();
 
-        AutofillId usernameId = mActivity.getUsername().getAutofillId();
-        AutofillId passwordId = mActivity.getPassword().getAutofillId();
+        final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+
+        final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
+        final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
+        // Sanity check to make sure the validator is properly configured
+        assertValidator(validator, usernameId, username, willSaveBeShown);
 
         // Set response
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
-                .setValidator(validatorBuilder.apply(usernameId, passwordId))
+                .setValidator(validator)
                 .build());
 
         // Trigger auto-fill
@@ -81,59 +83,22 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.onUsername((v) -> v.setText("7992739871-3"));
-        mActivity.onPassword((v) -> v.setText("passwd"));
+        mActivity.onUsername((v) -> v.setText(username));
+        mActivity.onPassword((v) -> v.setText("pass"));
         mActivity.tapLogin();
 
         if (willSaveBeShown) {
-            sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
             sReplier.getNextSaveRequest();
         } else {
-            sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
         }
-
-        assertNoDanglingSessions();
     }
 
-    @Test
-    public void checkForInvalidField() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.or(
-                new LuhnChecksumValidator(new AutofillId(-1)),
-                new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
-    }
-
-    @Test
-    public void checkBoth() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.and(
-                new LuhnChecksumValidator(usernameId),
-                new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
-    }
-
-    @Test
-    public void checkEither1() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.or(
-                new RegexValidator(usernameId, Pattern.compile("7.*")),
-                new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
-    }
-
-    @Test
-    public void checkEither2() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.or(
-                new RegexValidator(usernameId, Pattern.compile("invalid")),
-                new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
-    }
-
-    @Test
-    public void checkBothButFail() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.and(
-                new RegexValidator(usernameId, Pattern.compile("7.*")),
-                new RegexValidator(passwordId, Pattern.compile("invalid"))), false);
-    }
-
-    @Test
-    public void checkEitherButFail() throws Exception {
-        testValidator((usernameId, passwordId) -> Validators.or(
-                new RegexValidator(usernameId, Pattern.compile("invalid")),
-                new RegexValidator(passwordId, Pattern.compile("invalid"))), false);
+    private void assertValidator(InternalValidator validator, AutofillId id, String text,
+            boolean valid) {
+        final ValueFinder valueFinder = mock(ValueFinder.class);
+        doReturn(text).when(valueFinder).findByAutofillId(id);
+        assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
new file mode 100644
index 0000000..6c7979d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.service.autofill.Validators.and;
+import static android.service.autofill.Validators.not;
+import static android.service.autofill.Validators.or;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Validator;
+import android.service.autofill.ValueFinder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ValidatorsTest extends AutoFillServiceTestCase {
+
+    @Mock private Validator mInvalidValidator;
+    @Mock private ValueFinder mValueFinder;
+    @Mock private InternalValidator mValidValidator;
+    @Mock private InternalValidator mValidValidator2;
+
+    @Test
+    public void testAnd_null() {
+        assertThrows(NullPointerException.class, () -> and((Validator) null));
+        assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_firstFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testAnd_firstPassedSecondFailed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testAnd_AllPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testOr_null() {
+        assertThrows(NullPointerException.class, () -> or((Validator) null));
+        assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
+    }
+
+    @Test
+    public void testOr_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testOr_AllFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testOr_firstPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testOr_secondPassed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_null() {
+        assertThrows(IllegalArgumentException.class, () -> not(null));
+    }
+
+    @Test
+    public void testNot_invalidClass() {
+        assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
+    }
+
+    @Test
+    public void testNot_falseToTrue() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_trueToFalse() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
index 65f3378..b9ea071 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
@@ -26,6 +26,7 @@
 import android.autofillservice.cts.VirtualContainerView.Line.OneTimeLineWatcher;
 import android.graphics.Canvas;
 import android.os.Bundle;
+import android.widget.EditText;
 
 /**
  * A custom activity that uses {@link Canvas} to draw the following fields:
@@ -39,6 +40,7 @@
 
     static final String BLANK_VALUE = "        ";
 
+    EditText mUrlBar;
     VirtualContainerView mCustomView;
 
     Line mUsername;
@@ -52,8 +54,10 @@
 
         setContentView(R.layout.virtual_container_activity);
 
+        mUrlBar = findViewById(R.id.my_url_bar);
         mCustomView = findViewById(R.id.virtual_container_view);
 
+        mUrlBar.setText("ftp://dev.null/4/8/15/16/23/42");
         mUsername = mCustomView.addLine(ID_USERNAME_LABEL, "Username", ID_USERNAME, BLANK_VALUE);
         mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE);
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
new file mode 100644
index 0000000..54ac252
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+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.common.SettingsHelper.NAMESPACE_GLOBAL;
+import static android.provider.Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.ClassRule;
+
+/**
+ * Test case for an activity containing virtual children but using the A11Y compat mode to implement
+ * the Autofill APIs.
+ */
+public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
+    private static final String TAG = "VirtualContainerActivityCompatModeTest";
+    private static final Context sContext = InstrumentationRegistry.getContext();
+
+    @ClassRule
+    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
+            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_ALLOWED_PACKAGES, SERVICE_PACKAGE);
+
+    public VirtualContainerActivityCompatModeTest() {
+        super(true);
+    }
+
+    @After
+    public void resetCompatMode() {
+        sContext.getApplicationContext().setAutofillCompatibilityEnabled(false);
+    }
+
+    @Override
+    protected void preActivityCreated() {
+        sContext.getApplicationContext().setAutofillCompatibilityEnabled(true);
+    }
+
+    @Override
+    protected void postActivityLaunched(VirtualContainerActivity activity) {
+        // Set our own compat mode as well..
+        activity.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);
+    }
+
+    @Override
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
+        assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
+    }
+
+    // TODO(b/72811561): currently only one test pass at time; remove once they all pass
+    @After
+    public void thereCanBeOnlyOne() {
+        sRanAlready = true;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 31eae24..9d5e5fb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -22,11 +22,14 @@
 import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.assertTextOnly;
 import static android.autofillservice.cts.Helper.dumpStructure;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
 import static android.autofillservice.cts.VirtualContainerView.LABEL_CLASS;
 import static android.autofillservice.cts.VirtualContainerView.TEXT_CLASS;
 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;
@@ -34,41 +37,64 @@
 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.VirtualContainerView.Line;
 import android.content.ComponentName;
 import android.graphics.Rect;
-import android.os.SystemClock;
 import android.service.autofill.SaveInfo;
 import android.support.test.uiautomator.UiObject2;
+import android.view.ViewGroup;
 import android.view.autofill.AutofillManager;
 
-import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
- * Test case for an activity containing virtual children.
+ * Test case for an activity containing virtual children, either using the explicit Autofill APIs
+ * or Compat mode.
  */
 public class VirtualContainerActivityTest extends AutoFillServiceTestCase {
 
     @Rule
     public final AutofillActivityTestRule<VirtualContainerActivity> mActivityRule =
-            new AutofillActivityTestRule<VirtualContainerActivity>(VirtualContainerActivity.class);
+            new AutofillActivityTestRule<VirtualContainerActivity>(VirtualContainerActivity.class) {
+        @Override
+        protected void beforeActivityLaunched() {
+            preActivityCreated();
+        }
+    };
 
+    private final boolean mCompatMode;
     private VirtualContainerActivity mActivity;
 
+    public VirtualContainerActivityTest() {
+        this(false);
+    }
+
+    protected VirtualContainerActivityTest(boolean compatMode) {
+        mCompatMode = compatMode;
+    }
+
     @Before
     public void setActivity() {
         mActivity = mActivityRule.getActivity();
+        postActivityLaunched(mActivity);
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
+    /**
+     * Hook for subclass to customize test before activity is created.
+     */
+    protected void preActivityCreated() {}
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched(
+            @SuppressWarnings("unused") VirtualContainerActivity activity) {
     }
 
     @Test
@@ -78,13 +104,40 @@
 
     @Test
     public void testAutofillAsync() throws Exception {
+        if (mCompatMode) return;
+
         autofillTest(false);
     }
 
     /**
+     * Focus to username and expect window event
+     */
+    void focusToUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true),
+                Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+    /**
+     * 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.
+        mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
+    }
+
+    /**
+     * Focus to password and expect window event
+     */
+    void focusToPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true),
+                Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+    /**
      * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
      */
     private void autofillTest(boolean sync) throws Exception {
+        if (sRanAlready) return;
         // Set service.
         enableService();
 
@@ -98,26 +151,28 @@
         mActivity.mCustomView.setSync(sync);
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         assertDatasetShown(mActivity.mUsername, "The Dude");
 
         // Play around with focus to make sure picker is properly drawn.
-        mActivity.mPassword.changeFocus(true);
+        focusToPassword();
         assertDatasetShown(mActivity.mPassword, "The Dude");
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         assertDatasetShown(mActivity.mUsername, "The Dude");
 
         // Make sure input was sanitized.
         final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
         final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
         final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
         final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
         final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
 
+        assertUrlBarIsSanitized(urlBar);
         assertTextIsSanitized(username);
         assertTextIsSanitized(password);
-        assertTextAndValue(usernameLabel, "Username");
-        assertTextAndValue(passwordLabel, "Password");
+        assertLabel(usernameLabel, "Username");
+        assertLabel(passwordLabel, "Password");
 
         assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
         assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
@@ -127,17 +182,23 @@
         assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
         assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
 
-        // Make sure order is preserved and dupes not removed.
-        assertThat(username.getAutofillHints()).asList()
-                .containsExactly("c", "a", "a", "b", "a", "a")
-                .inOrder();
-
-        try {
-            VirtualContainerView.assertHtmlInfo(username);
-            VirtualContainerView.assertHtmlInfo(password);
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("HtmlInfo failed", request.structure);
-            throw e;
+        final String[] autofillHints = username.getAutofillHints();
+        if (mCompatMode) {
+            assertThat(autofillHints).isNull();
+            assertThat(username.getHtmlInfo()).isNull();
+            assertThat(password.getHtmlInfo()).isNull();
+        } else {
+            // Make sure order is preserved and dupes not removed.
+            assertThat(autofillHints).asList()
+                    .containsExactly("c", "a", "a", "b", "a", "a")
+                    .inOrder();
+            try {
+                VirtualContainerView.assertHtmlInfo(username);
+                VirtualContainerView.assertHtmlInfo(password);
+            } catch (AssertionError | RuntimeException e) {
+                dumpStructure("HtmlInfo failed", request.structure);
+                throw e;
+            }
         }
 
         // Make sure initial focus was properly set.
@@ -145,18 +206,15 @@
         assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
 
         // Auto-fill it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
-
-        // Sanity checks.
-        sReplier.assertNumberUnhandledFillRequests(0);
-        sReplier.assertNumberUnhandledSaveRequests(0);
     }
 
     @Test
     public void testAutofillTwoDatasets() throws Exception {
+        if (sRanAlready) return;
         // Set service.
         enableService();
 
@@ -176,18 +234,18 @@
         mActivity.expectAutoFill("DUDE", "SWEET");
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         sReplier.getNextFillRequest();
         assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
 
         // Play around with focus to make sure picker is properly drawn.
-        mActivity.mPassword.changeFocus(true);
+        focusToPassword();
         assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
 
         // Auto-fill it.
-        sUiBot.selectDataset("THE DUDE");
+        mUiBot.selectDataset("THE DUDE");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -195,12 +253,15 @@
 
     @Test
     public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
+        if (sRanAlready) return;
         mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
         autofillTest(true);
     }
 
     @Test
     public void testAutofillManuallyOneDataset() throws Exception {
+        if (mCompatMode) return; // TODO(b/73557072): not supported yet
+
         // Set service.
         enableService();
 
@@ -217,14 +278,10 @@
         sReplier.getNextFillRequest();
 
         // Select datatest.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
-
-        // Sanity checks.
-        sReplier.assertNumberUnhandledFillRequests(0);
-        sReplier.assertNumberUnhandledSaveRequests(0);
     }
 
     @Test
@@ -238,6 +295,8 @@
     }
 
     private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+        if (mCompatMode) return; // TODO(b/73557072): not supported yet
+
         // Set service.
         enableService();
 
@@ -267,19 +326,16 @@
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
-        sUiBot.selectDataset(picker, pickFirst? "The Dude" : "Jenny");
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
 
         // Check the results.
         mActivity.assertAutoFilled();
-
-        // Sanity checks.
-        sReplier.assertNumberUnhandledFillRequests(0);
-        sReplier.assertNumberUnhandledSaveRequests(0);
     }
 
     @Test
     public void testAutofillCallbacks() throws Exception {
+        if (sRanAlready) return;
         // Set service.
         enableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -293,43 +349,45 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         sReplier.getNextFillRequest();
 
         callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
 
         // Change focus
-        mActivity.mPassword.changeFocus(true);
+        focusToPassword();
         callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
         callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
     }
 
     @Test
-    public void testAutofillCallbackDisabled() throws Exception {
+    public void testAutofillCallbackDisabled() throws Throwable {
+        if (sRanAlready) return;
         // Set service.
         disableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsernameExpectNoWindowEvent();
 
         // Assert callback was called
         callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
     }
 
     @Test
-    public void testAutofillCallbackNoDatasets() throws Exception {
+    public void testAutofillCallbackNoDatasets() throws Throwable {
         callbackUnavailableTest(NO_RESPONSE);
     }
 
     @Test
-    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
+    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
         callbackUnavailableTest(new CannedFillResponse.Builder()
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
                 .build());
     }
 
-    private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
+    private void callbackUnavailableTest(CannedFillResponse response) throws Throwable {
+        if (sRanAlready) return;
         // Set service.
         enableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -338,11 +396,11 @@
         sReplier.addResponse(response);
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsernameExpectNoWindowEvent();
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Assert callback was called
         callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
@@ -350,6 +408,7 @@
 
     @Test
     public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
+        if (sRanAlready) return;
         // Set service.
         enableService();
 
@@ -365,55 +424,140 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         sReplier.getNextFillRequest();
         assertDatasetShown(mActivity.mUsername, "The Dude");
 
-        sUiBot.pressBack();
+        mUiBot.pressBack();
 
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
     }
 
     @Test
-    public void testSaveDialogShownWhenAllVirtualViewsNotVisible() throws Exception {
+    public void testSave_childViewsGone() throws Throwable {
+        saveTest(CommitType.CHILDREN_VIEWS_GONE);
+    }
+
+    @Test
+    @Ignore("Disabled until b/73493342 is fixed")
+    public void testSave_parentViewGone() throws Throwable {
+        saveTest(CommitType.PARENT_VIEW_GONE);
+    }
+
+    @Test
+    public void testSave_appCallsCommit() throws Throwable {
+        saveTest(CommitType.EXPLICIT_COMMIT);
+    }
+
+    @Test
+    public void testSave_submitButtonClicked() throws Throwable {
+        if (mCompatMode) return; // TODO(b/73649008): implement it
+        saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
+    }
+
+    enum CommitType {
+        CHILDREN_VIEWS_GONE,
+        PARENT_VIEW_GONE,
+        EXPLICIT_COMMIT,
+        SUBMIT_BUTTON_CLICKED
+    }
+
+    private void saveTest(CommitType commitType) throws Throwable {
+        if (sRanAlready) return;
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE:
+            case PARENT_VIEW_GONE:
+                response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                response
+                    .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                    .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
+                break;
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE:
+                mActivity.mUsername.changeVisibility(false);
+                mActivity.mPassword.changeVisibility(false);
+                break;
+            case PARENT_VIEW_GONE:
+                mActivity.runOnUiThread(() -> {
+                    final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
+                    parent.removeView(mActivity.mCustomView);
+                });
+                break;
+            case EXPLICIT_COMMIT:
+                mActivity.getAutofillManager().commit();
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                mActivity.mCustomView.clickLogin();
+                break;
+            default:
+                throw new IllegalArgumentException("unknown type: " + commitType);
+        }
+
+        // Trigger save.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+
+        if (mCompatMode) {
+            // TODO(b/73557456): temporarily not checking values as it's not working
+            assertThat(username).isNotNull();
+            assertThat(password).isNotNull();
+            return;
+        }
+
+        assertTextAndValue(username, "foo");
+        assertTextAndValue(password, "bar");
+    }
+
+    @Test
+    public void testSaveNotShownWhenVirtualViewValueChanges() throws Throwable {
+        if (mCompatMode) return; // TODO(b/72811561): figure out why it fails on compat mode
+
         // Set service.
         enableService();
 
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                 .build());
 
-        final CountDownLatch latch = new CountDownLatch(1);
-
         // Trigger auto-fill.
-        mActivity.runOnUiThread(() -> {
-            mActivity.mUsername.changeFocus(true);
-            latch.countDown();
-        });
-        latch.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        focusToUsernameExpectNoWindowEvent();
         sReplier.getNextFillRequest();
 
-        // TODO: 63602573 Should be removed once this bug is fixed
-        SystemClock.sleep(1000);
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
 
-        mActivity.runOnUiThread(() -> {
-            // Fill in some stuff
-            mActivity.mUsername.setText("foo");
-            mActivity.mPassword.setText("bar");
-
-            // Hide all virtual views
-            mActivity.mUsername.changeVisibility(false);
-            mActivity.mPassword.changeVisibility(false);
-        });
-
-        // Make sure save is shown
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+        // Trigger save.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
     }
 
     @Test
     public void testAppCannotFakePackageName() throws Exception {
+        if (sRanAlready) return;
         // Set service.
         enableService();
 
@@ -427,7 +571,7 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         assertDatasetShown(mActivity.mUsername, "The Dude");
 
         // Make sure package name was sanitized.
@@ -439,12 +583,34 @@
     /**
      * Asserts the dataset picker is properly displayed in a give line.
      */
-    private void assertDatasetShown(Line line, String... expectedDatasets) {
-        final Rect pickerBounds = sUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
+    private void assertDatasetShown(Line line, String... expectedDatasets) throws Exception {
+        // TODO(b/73548352): temporary workaround until we figure out why there's a mismatch on the
+        // values reported while running on compat mode.
+        if (mCompatMode) return;
+
+        final Rect pickerBounds = mUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
         final Rect fieldBounds = line.getAbsCoordinates();
         assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
                 fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
         assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s", pickerBounds,
                 fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
     }
+
+    private void assertLabel(ViewNode node, String expectedValue) {
+        if (mCompatMode) {
+            // Compat mode doesn't set AutofillValue of non-editable fields
+            assertTextOnly(node, expectedValue);
+        } else {
+            assertTextAndValue(node, expectedValue);
+        }
+    }
+
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isNull();
+        assertThat(urlBar.getWebScheme()).isNull();
+    }
+
+    // TODO(b/72811561): currently only one test pass at time
+    protected static boolean sRanAlready = false;
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index 8eecc29..625cfc2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
@@ -16,7 +16,7 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -29,6 +29,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
@@ -42,6 +43,11 @@
 import android.view.ViewStructure;
 import android.view.ViewStructure.HtmlInfo;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 
@@ -53,14 +59,16 @@
 class VirtualContainerView extends View {
 
     private static final String TAG = "VirtualContainerView";
+    private static final int LOGIN_BUTTON_VIRTUAL_ID = 666;
 
     static final String LABEL_CLASS = "my.readonly.view";
     static final String TEXT_CLASS = "my.editable.view";
-
+    static final String ID_URL_BAR = "my_url_bar";
 
     private final ArrayList<Line> mLines = new ArrayList<>();
     private final SparseArray<Item> mItems = new SparseArray<>();
     private final AutofillManager mAfm;
+    final AutofillId mLoginButtonId;
 
     private Line mFocusedLine;
 
@@ -74,7 +82,11 @@
     private int mUnfocusedColor;
     private boolean mSync = true;
     private boolean mOverrideDispatchProvideAutofillStructure = false;
-    private ComponentName mFakedComponentName;
+    private ComponentName mFackedComponentName;
+
+    private boolean mCompatMode = false;
+    private AccessibilityDelegate mAccessibilityDelegate;
+    private AccessibilityNodeProvider mAccessibilityNodeProvider;
 
     public VirtualContainerView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -97,28 +109,22 @@
         mLineLength = mTextHeight + mVerticalGap;
         mTextPaint.setTextSize(mTextHeight);
         Log.d(TAG, "Text height: " + mTextHeight);
+        mLoginButtonId = new AutofillId(getAutofillId(), LOGIN_BUTTON_VIRTUAL_ID);
     }
 
     @Override
     public void autofill(SparseArray<AutofillValue> values) {
         Log.d(TAG, "autofill: " + values);
+        if (mCompatMode) {
+            Log.v(TAG, "using super.autofill() on compat mode");
+            super.autofill(values);
+            return;
+        }
         for (int i = 0; i < values.size(); i++) {
             final int id = values.keyAt(i);
             final AutofillValue value = values.valueAt(i);
-            final Item item = mItems.get(id);
-            if (item == null) {
-                Log.w(TAG, "No item for id " + id);
-                return;
-            }
-            if (!item.editable) {
-                Log.w(TAG, "Item for id " + id + " is not editable: " + item);
-                return;
-            }
-            item.text = value.getTextValue();
-            if (item.listener != null) {
-                Log.d(TAG, "Notify listener: " + item.text);
-                item.listener.onTextChanged(item.text, 0, 0, 0);
-            }
+            final Item item = getItem(id);
+            item.autofill(value.getTextValue());
         }
         postInvalidate();
     }
@@ -195,15 +201,21 @@
         Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
         super.onProvideAutofillVirtualStructure(structure, flags);
 
-        if (mFakedComponentName != null) {
-            Log.d(TAG, "Faking package name to " + mFakedComponentName);
+        if (mCompatMode) {
+            Log.v(TAG, "using super.onProvideAutofillVirtualStructure() on compat mode");
+            return;
+        }
+
+
+        if (mFackedComponentName != null) {
+            Log.d(TAG, "Faking package name to " + mFackedComponentName);
             try {
                 final AssistStructure assistStructure = Helper.getField(structure, "mAssist");
                 if (assistStructure != null) {
-                    Helper.setField(assistStructure, "mActivityComponent", mFakedComponentName);
+                    Helper.setField(assistStructure, "mActivityComponent", mFackedComponentName);
                 }
             } catch (Exception e) {
-                Log.e(TAG, "Could not fake package name to " + mFakedComponentName, e);
+                Log.e(TAG, "Could not fake package name to " + mFackedComponentName, e);
             }
         }
 
@@ -221,8 +233,7 @@
             child.setAutofillId(structure.getAutofillId(), item.id);
             child.setDataIsSensitive(item.sensitive);
             index++;
-            final String className = item.editable ? TEXT_CLASS : LABEL_CLASS;
-            child.setClassName(className);
+            child.setClassName(item.className);
             // Must set "fake" idEntry because that's what the test cases use to find nodes.
             child.setId(1000 + index, packageName, "id", item.resourceId);
             child.setText(item.text);
@@ -244,6 +255,54 @@
         }
     }
 
+    @Override
+    public boolean isVisibleToUserForAutofill(int virtualId) {
+        // TODO(b/72811561): implement / add test case that exercises it
+        final boolean isVisible = super.isVisibleToUserForAutofill(virtualId);
+        Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + "): " + isVisible);
+        return isVisible;
+    }
+
+    /**
+     * Emulates clicking the login button.
+     */
+    void clickLogin() {
+        Log.d(TAG, "clickLogin()");
+        if (mCompatMode) {
+            // TODO(b/73649008): implement it
+            throw new IllegalArgumentException("clickLogin() on compat mode not implemented yet");
+        } else {
+            mAfm.notifyViewClicked(this, LOGIN_BUTTON_VIRTUAL_ID);
+        }
+    }
+
+
+
+    private Item getItem(int id) {
+        final Item item = mItems.get(id);
+        assertWithMessage("No item for id %s", id).that(item).isNotNull();
+        return item;
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+        final String packageName = getContext().getPackageName();
+        node.setPackageName(packageName);
+        node.setClassName(getClass().getName());
+
+        final int childrenSize = mItems.size();
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mItems.valueAt(i);
+            final int id = i + 1;
+            Log.d(TAG, "Adding new A11Y child with id " + id + ": " + item);
+
+            node.addChild(this, id);
+        }
+
+        return node;
+    }
+
     static void assertHtmlInfo(ViewNode node) {
         final String name = node.getText().toString();
         final HtmlInfo info = node.getHtmlInfo();
@@ -270,7 +329,48 @@
     }
 
     void fakePackageName(ComponentName name) {
-        mFakedComponentName = name;
+        mFackedComponentName = name;
+    }
+
+    void setCompatMode(boolean compatMode) {
+        mCompatMode = compatMode;
+
+        if (mCompatMode) {
+            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+                @Override
+                public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
+                    if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+                        return onProvideAutofillCompatModeAccessibilityNodeInfo();
+                    }
+                    final Item item = getItem(virtualViewId);
+                    return item.provideAccessibilityNodeInfo(VirtualContainerView.this,
+                            getContext());
+                }
+
+                @Override
+                public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+                    if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                        final CharSequence text = arguments.getCharSequence(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                        final Item item = getItem(virtualViewId);
+                        item.autofill(text);
+                        return true;
+                    }
+
+                    return false;
+                }
+            };
+            mAccessibilityDelegate = new AccessibilityDelegate() {
+                @Override
+                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                    return mAccessibilityNodeProvider;
+                }
+            };
+
+            setAccessibilityDelegate(mAccessibilityDelegate);
+        }
     }
 
     void setOverrideDispatchProvideAutofillStructure(boolean flag) {
@@ -296,6 +396,18 @@
 
         void changeFocus(boolean focused) {
             this.focused = focused;
+            if (mCompatMode) {
+                final AccessibilityEvent event = AccessibilityEvent.obtain();
+                event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                event.setSource(VirtualContainerView.this, text.id);
+                event.setEnabled(true);
+                event.setPackageName(getContext().getPackageName());
+                // TODO(b/72811561): recycle event?
+                getContext().getSystemService(AccessibilityManager.class)
+                    .sendAccessibilityEvent(event);
+                return;
+            }
+
             if (focused) {
                 final Rect absBounds = getAbsCoordinates();
                 Log.d(TAG, "focus gained on " + text.id + "; absBounds=" + absBounds);
@@ -372,8 +484,8 @@
             }
 
             void assertAutoFilled() throws Exception {
-                final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT_MS, label)
+                final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
                         .that(set).isTrue();
                 final String actual = text.text.toString();
                 assertWithMessage("Wrong auto-fill value on Line %s", label)
@@ -389,6 +501,7 @@
         private CharSequence text;
         private final boolean editable;
         private final boolean sensitive;
+        private final String className;
         private TextWatcher listener;
 
         Item(Line line, int id, String resourceId, CharSequence text, boolean editable,
@@ -399,6 +512,32 @@
             this.text = text;
             this.editable = editable;
             this.sensitive = sensitive;
+            this.className = editable ? TEXT_CLASS : LABEL_CLASS;
+        }
+
+        AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
+            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+            node.setSource(parent, id);
+            node.setPackageName(context.getPackageName());
+            node.setClassName(className);
+            node.setEditable(editable);
+            node.setViewIdResourceName(resourceId);
+            // TODO(b/73548352): ideally item should have its own bounds
+            node.setBoundsInScreen(line.bounds);
+            node.setText(text);
+            return node;
+        }
+
+        private void autofill(CharSequence value) {
+            if (!editable) {
+                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
+                return;
+            }
+            text = value;
+            if (listener != null) {
+                Log.d(TAG, "Notify listener: " + text);
+                listener.onTextChanged(text, 0, 0, 0);
+            }
         }
 
         @Override
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
index 96ee370..a8e0314 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
@@ -28,7 +28,7 @@
  * }
  * </code></pre>
  */
-interface Visitor<T>{
+interface Visitor<T> {
 
     void visit(T view);
 }
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
index 9bc3df0..1273bf9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
@@ -16,13 +16,17 @@
 package android.autofillservice.cts;
 
 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;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.EditText;
+import android.widget.LinearLayout;
 
 import java.io.IOException;
 
@@ -33,7 +37,18 @@
     private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
     static final String ID_WEBVIEW = "webview";
 
-    WebView mWebView;
+    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";
+
+    private MyWebView mWebView;
+
+    private LinearLayout mOutsideContainer1;
+    private LinearLayout mOutsideContainer2;
+    EditText mOutside1;
+    EditText mOutside2;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -64,36 +79,50 @@
                 }
             }
         });
-        mWebView.loadUrl(FAKE_URL);
+
+        mOutsideContainer1 = findViewById(R.id.outsideContainer1);
+        mOutsideContainer2 = findViewById(R.id.outsideContainer2);
+        mOutside1 = findViewById(R.id.outside1);
+        mOutside2 = findViewById(R.id.outside2);
     }
 
-    public UiObject2 getUsernameLabel(UiBot uiBot) {
+    public MyWebView loadWebView() {
+        syncRunOnUiThread(() -> mWebView.loadUrl(FAKE_URL));
+        return mWebView;
+    }
+
+    public void loadOutsideViews() {
+        syncRunOnUiThread(() -> {
+            mOutsideContainer1.setVisibility(View.VISIBLE);
+            mOutsideContainer2.setVisibility(View.VISIBLE);
+        });
+    }
+
+    public UiObject2 getUsernameLabel(UiBot uiBot) throws Exception {
         return getLabel(uiBot, "Username: ");
     }
 
-    public UiObject2 getPasswordLabel(UiBot uiBot) {
+    public UiObject2 getPasswordLabel(UiBot uiBot) throws Exception {
         return getLabel(uiBot, "Password: ");
     }
 
-
-    public UiObject2 getUsernameInput(UiBot uiBot) {
+    public UiObject2 getUsernameInput(UiBot uiBot) throws Exception {
         return getInput(uiBot, "Username: ");
     }
 
-    public UiObject2 getPasswordInput(UiBot uiBot) {
+    public UiObject2 getPasswordInput(UiBot uiBot) throws Exception {
         return getInput(uiBot, "Password: ");
     }
 
-    public UiObject2 getLoginButton(UiBot uiBot) {
-        return uiBot.assertShownByContentDescription("Login");
+    public UiObject2 getLoginButton(UiBot uiBot) throws Exception {
+        return getLabel(uiBot, "Login");
     }
 
-    private UiObject2 getLabel(UiBot uiBot, String contentDescription) {
-        final UiObject2 label = uiBot.assertShownByContentDescription(contentDescription);
-        return label;
+    private UiObject2 getLabel(UiBot uiBot, String label) throws Exception {
+        return uiBot.assertShownByText(label, Timeouts.WEBVIEW_TIMEOUT);
     }
 
-    private UiObject2 getInput(UiBot uiBot, String contentDescription) {
+    private UiObject2 getInput(UiBot uiBot, String contentDescription) throws Exception {
         // First get the label..
         final UiObject2 label = getLabel(uiBot, contentDescription);
 
@@ -105,10 +134,23 @@
                 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/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
index 7eb73d2..732962a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
@@ -15,7 +15,10 @@
  */
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE1;
+import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE2;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -25,6 +28,7 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
 import android.view.ViewStructure.HtmlInfo;
 
 import org.junit.AfterClass;
@@ -65,15 +69,18 @@
         // Set service.
         enableService();
 
+        // Load WebView
+        mActivity.loadWebView();
+
         // Set expectations.
         sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
 
         // Trigger autofill.
-        mActivity.getUsernameInput(sUiBot).click();
+        mActivity.getUsernameInput(mUiBot).click();
         sReplier.getNextFillRequest();
 
         // Assert not shown.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
     }
 
     @Test
@@ -81,57 +88,56 @@
         // Set service.
         enableService();
 
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView();
+
         // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
         final MyAutofillCallback callback = mActivity.registerCallback();
         sReplier.addResponse(new CannedDataset.Builder()
-                .setField("username", "dude")
-                .setField("password", "sweet")
+                .setField(HTML_NAME_USERNAME, "dude")
+                .setField(HTML_NAME_PASSWORD, "sweet")
                 .setPresentation(createPresentation("The Dude"))
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(sUiBot).click();
+        mActivity.getUsernameInput(mUiBot).click();
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("The Dude");
+        mUiBot.assertDatasets("The Dude");
 
         // Change focus around.
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
-        mActivity.getUsernameLabel(sUiBot).click();
-        callback.assertUiHiddenEvent(mActivity.mWebView, usernameChildId);
-        sUiBot.assertNoDatasets();
-        mActivity.getPasswordInput(sUiBot).click();
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
-        final UiObject2 datasetPicker = sUiBot.assertDatasets("The Dude");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        mActivity.getUsernameLabel(mUiBot).click();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertNoDatasets();
+        mActivity.getPasswordInput(mUiBot).click();
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
 
         // Now Autofill it.
-        sUiBot.selectDataset(datasetPicker, "The Dude");
-        sUiBot.assertNoDatasets();
-        callback.assertUiHiddenEvent(mActivity.mWebView, passwordChildId);
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+        myWebView.assertAutofilled();
+        mUiBot.assertNoDatasets();
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
 
         // Make sure screen was autofilled.
-        assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
+        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(sUiBot).getText();
+        final String password = mActivity.getPasswordInput(mUiBot).getText();
         assertThat(password).isNotEqualTo("sweet");
         assertThat(password).hasLength(5);
 
         // Assert structure passed to service.
         try {
-            final ViewNode webViewNode = Helper.findWebViewNode(fillRequest.structure, "FORM AM I");
-            // TODO(b/66953802): class name should be android.webkit.WebView, and form name should
-            // be inside HtmlInfo, but Chromium 61 does not implement that.
-            if (webViewNode.getClassName().equals("android.webkit.WebView")) {
-                final HtmlInfo htmlInfo = Helper.assertHasHtmlTag(webViewNode, "form");
-                Helper.assertHasAttribute(htmlInfo, "name", "FORM AM I");
-            } else {
-                assertThat(webViewNode.getClassName()).isEqualTo("FORM AM I");
-                assertThat(webViewNode.getHtmlInfo()).isNull();
-            }
+            final ViewNode webViewNode =
+                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
+            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
             assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
+            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
 
             final ViewNode usernameNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, "username");
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
             Helper.assertTextIsSanitized(usernameNode);
             final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
             Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
@@ -141,7 +147,7 @@
             assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
 
             final ViewNode passwordNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, "password");
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
             Helper.assertTextIsSanitized(passwordNode);
             final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
             Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
@@ -161,37 +167,43 @@
         // Set service.
         enableService();
 
+        // Load WebView
+        mActivity.loadWebView();
+
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, "username", "password")
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(sUiBot).click();
+        mActivity.getUsernameInput(mUiBot).click();
         sReplier.getNextFillRequest();
 
         // Assert not shown.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
 
         // Trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(sUiBot).click();
-            runShellCommand("input keyevent KEYCODE_U");
-            mActivity.getPasswordInput(sUiBot).click();
-            runShellCommand("input keyevent KEYCODE_P");
+            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(sUiBot).setText("DUDE");
-            mActivity.getPasswordInput(sUiBot).setText("SWEET");
+            mActivity.getUsernameInput(mUiBot).setText("DUDE");
+            mActivity.getPasswordInput(mUiBot).setText("SWEET");
         }
-        mActivity.getLoginButton(sUiBot).click();
+        mActivity.getLoginButton(mUiBot).click();
 
         // Assert save UI shown.
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure, "username");
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure, "password");
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
         if (INJECT_EVENTS) {
             Helper.assertTextAndValue(usernameNode, "u");
             Helper.assertTextAndValue(passwordNode, "p");
@@ -206,64 +218,74 @@
         // Set service.
         enableService();
 
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView();
+
         // Set expectations.
         final MyAutofillCallback callback = mActivity.registerCallback();
+        myWebView.expectAutofill("dude", "sweet");
         sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, "username", "password")
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
                 .addDataset(new CannedDataset.Builder()
-                        .setField("username", "dude")
-                        .setField("password", "sweet")
+                        .setField(HTML_NAME_USERNAME, "dude")
+                        .setField(HTML_NAME_PASSWORD, "sweet")
                         .setPresentation(createPresentation("The Dude"))
                         .build())
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(sUiBot).click();
+        mActivity.getUsernameInput(mUiBot).click();
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        sUiBot.assertDatasets("The Dude");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
+        mUiBot.assertDatasets("The Dude");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
 
         // Assert structure passed to service.
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure, "username");
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
         Helper.assertTextIsSanitized(usernameNode);
         assertThat(usernameNode.isFocused()).isTrue();
         assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure, "password");
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
         Helper.assertTextIsSanitized(passwordNode);
         assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
         assertThat(passwordNode.isFocused()).isFalse();
 
         // Autofill it.
-        sUiBot.selectDataset("The Dude");
-        callback.assertUiHiddenEvent(mActivity.mWebView, usernameChildId);
+        mUiBot.selectDataset("The Dude");
+        myWebView.assertAutofilled();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
 
         // Make sure screen was autofilled.
-        assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
+        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(sUiBot).getText();
+        final String password = mActivity.getPasswordInput(mUiBot).getText();
         assertThat(password).isNotEqualTo("sweet");
         assertThat(password).hasLength(5);
 
         // Now trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(sUiBot).click();
-            runShellCommand("input keyevent KEYCODE_U");
-            mActivity.getPasswordInput(sUiBot).click();
-            runShellCommand("input keyevent KEYCODE_P");
+            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(sUiBot).setText("DUDE");
-            mActivity.getPasswordInput(sUiBot).setText("SWEET");
+            mActivity.getUsernameInput(mUiBot).setText("DUDE");
+            mActivity.getPasswordInput(mUiBot).setText("SWEET");
         }
-        mActivity.getLoginButton(sUiBot).click();
+        mActivity.getLoginButton(mUiBot).click();
 
         // Assert save UI shown.
-        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure, "username");
-        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure, "password");
+        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
         if (INJECT_EVENTS) {
             Helper.assertTextAndValue(usernameNode2, "dudeu");
             Helper.assertTextAndValue(passwordNode2, "sweetp");
@@ -272,4 +294,285 @@
             Helper.assertTextAndValue(passwordNode2, "SWEET");
         }
     }
+
+    @Test
+    public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load views
+        final MyWebView myWebView = mActivity.loadWebView();
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput(mUiBot).click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.getPasswordInput(mUiBot).click();
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Autofill it.
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+
+        myWebView.assertAutofilled();
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput(mUiBot).setText("DUDE");
+            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton(mUiBot).click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
+
+
+    @Test
+    public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
+        if (true) return; // TODO(b/69461853): re-enable once WebView fixes it
+
+        // Set service.
+        enableService();
+
+        // Load outside views
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Now load Webiew
+        final MyWebView myWebView = mActivity.loadWebView();
+
+        // Set expectations
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput(mUiBot).click();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        mActivity.getPasswordInput(mUiBot).click();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        // Autofill external views (2nd partition)
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Autofill Webview (1st partition)
+        mActivity.getUsernameInput(mUiBot).click();
+        callback.assertUiShownEventForVirtualChild(myWebView);
+        mUiBot.selectDataset("USER");
+        myWebView.assertAutofilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput(mUiBot).setText("DUDE");
+            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton(mUiBot).click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
index 5d3baca..993951d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
@@ -17,7 +17,10 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.test.uiautomator.UiObject2;
@@ -35,9 +38,12 @@
     private static final String TAG = "WelcomeActivity";
 
     static final String EXTRA_MESSAGE = "message";
-    static final String ID_OUTPUT = "output";
+    static final String ID_WELCOME = "welcome";
 
-    private TextView mOutput;
+    private static int sPendingIntentId;
+    private static PendingIntent sPendingIntent;
+
+    private TextView mWelcome;
 
     public WelcomeActivity() {
         sInstance = this;
@@ -49,22 +55,34 @@
 
         setContentView(R.layout.welcome_activity);
 
-        mOutput = (TextView) findViewById(R.id.output);
+        mWelcome = (TextView) findViewById(R.id.welcome);
 
         final Intent intent = getIntent();
         final String message = intent.getStringExtra(EXTRA_MESSAGE);
 
         if (!TextUtils.isEmpty(message)) {
-            mOutput.setText(message);
+            mWelcome.setText(message);
         }
 
-        Log.d(TAG, "Output: " + mOutput.getText());
+        Log.d(TAG, "Message: " + message);
     }
 
-    static void finishIt() {
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        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);
+        }
+        if (sPendingIntent != null) {
+            sPendingIntent.cancel();
         }
     }
 
@@ -75,11 +93,25 @@
 
     // TODO: reuse in other places
     static void assertShowing(UiBot uiBot, @Nullable String expectedMessage) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_OUTPUT);
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
         if (expectedMessage == null) {
             expectedMessage = "Welcome to the jungle!";
         }
         assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
                 .isEqualTo(expectedMessage);
     }
+
+    public static IntentSender createSender(Context context, String message) {
+        if (sPendingIntent != null) {
+            throw new IllegalArgumentException("Already have pending intent (id="
+                    + sPendingIntentId + "): " + sPendingIntent);
+        }
+        ++sPendingIntentId;
+        Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
+        final Intent intent = new Intent(context, WelcomeActivity.class)
+                .putExtra(EXTRA_MESSAGE, message)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent, 0);
+        return sPendingIntent.getIntentSender();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
new file mode 100644
index 0000000..cbdcf44
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
@@ -0,0 +1,67 @@
+/*
+ * 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.autofillservice.cts.common;
+
+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.provider.Settings;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper used to block tests until a secure settings value has been updated.
+ */
+public final class OneTimeSettingsListener extends ContentObserver {
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private final ContentResolver mResolver;
+    private final String mKey;
+
+    public OneTimeSettingsListener(Context context, String key) {
+        super(new Handler(Looper.getMainLooper()));
+        mKey = key;
+        mResolver = context.getContentResolver();
+        mResolver.registerContentObserver(Settings.Secure.getUriFor(key), false, this);
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        mResolver.unregisterContentObserver(this);
+        mLatch.countDown();
+    }
+
+    /**
+     * Blocks for a few seconds until it's called.
+     *
+     * @throws IllegalStateException if it's not called.
+     */
+    public void assertCalled() {
+        try {
+            final boolean updated = mLatch.await(5, TimeUnit.SECONDS);
+            if (!updated) {
+                throw new IllegalStateException("Settings " + mKey + " not called in 5s");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted", e);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/README.txt b/tests/autofillservice/src/android/autofillservice/cts/common/README.txt
new file mode 100644
index 0000000..97d3b48
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/README.txt
@@ -0,0 +1,2 @@
+This package contains utilities that are not tied to Autofill and might eventually move to
+a common CTS package.
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
new file mode 100644
index 0000000..cff2499
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * Provides utilities to interact with the device's {@link Settings}.
+ */
+public final class SettingsHelper {
+
+    public static final String NAMESPACE_SECURE = "secure";
+    public static final String NAMESPACE_GLOBAL = "global";
+
+    /**
+     * Uses a Shell command to set the given preference.
+     */
+    public static void set(@NonNull String namespace, @NonNull String key, @Nullable String value) {
+        if (value == null) {
+            delete(namespace, key);
+            return;
+        }
+        runShellCommand("settings put %s %s %s default", namespace, key, value);
+    }
+
+    public static void set(@NonNull String key, @Nullable String value) {
+        set(NAMESPACE_SECURE, key, value);
+    }
+
+    /**
+     * Uses a Shell command to set the given preference, and verifies it was correctly set.
+     */
+    public static void syncSet(@NonNull Context context, @NonNull String namespace,
+            @NonNull String key, @Nullable String value) {
+        if (value == null) {
+            syncDelete(context, namespace, key);
+            return;
+        }
+
+        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+        set(namespace, key, value);
+        observer.assertCalled();
+
+        final String newValue = get(namespace, key);
+        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo(value);
+    }
+
+    public static void syncSet(@NonNull Context context, @NonNull String key,
+            @Nullable String value) {
+        syncSet(context, NAMESPACE_SECURE, key, value);
+    }
+
+    /**
+     * Uses a Shell command to delete the given preference.
+     */
+    public static void delete(@NonNull String namespace, @NonNull String key) {
+        runShellCommand("settings delete %s %s", namespace, key);
+    }
+
+    public static void delete(@NonNull String key) {
+        delete(NAMESPACE_SECURE, key);
+    }
+
+    /**
+     * Uses a Shell command to delete the given preference, and verifies it was correctly deleted.
+     */
+    public static void syncDelete(@NonNull Context context, @NonNull String namespace,
+            @NonNull String key) {
+
+        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+        delete(namespace, key);
+        observer.assertCalled();
+
+        final String newValue = get(namespace, key);
+        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo("null");
+    }
+
+    public static void syncDelete(@NonNull Context context, @NonNull String key) {
+        syncDelete(context, NAMESPACE_SECURE, key);
+    }
+
+    /**
+     * Gets the value of a given preference using Shell command.
+     */
+    @NonNull
+    public static String get(@NonNull String namespace, @NonNull String key) {
+        return runShellCommand("settings get %s %s", namespace, key);
+    }
+
+    @NonNull
+    public static String get(@NonNull String key) {
+        return get(NAMESPACE_SECURE, key);
+    }
+
+    private SettingsHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java
new file mode 100644
index 0000000..f61d319
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java
@@ -0,0 +1,55 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * JUnit rule used to set a {@link Settings} preference before the test is run.
+ *
+ * <p>It stores the current value before the test, changes it (if necessary), then restores it after
+ * the test (if necessary).
+ */
+public class SettingsStateChangerRule extends StateChangerRule<String> {
+
+    /**
+     * Default constructor for the 'secure' context.
+     *
+     * @param context context used to retrieve the {@link Settings} provider.
+     * @param key prefence key.
+     * @param value value to be set before the test is run.
+     */
+    public SettingsStateChangerRule(@NonNull Context context, @NonNull String key,
+            @Nullable String value) {
+        this(context, SettingsHelper.NAMESPACE_SECURE, key, value);
+    }
+
+    /**
+     * Default constructor.
+     *
+     * @param context context used to retrieve the {@link Settings} provider.
+     * @param namespace settings namespace.
+     * @param key prefence key.
+     * @param value value to be set before the test is run.
+     */
+    public SettingsStateChangerRule(@NonNull Context context, @NonNull String namespace,
+            @NonNull String key, @Nullable String value) {
+        super(new SettingsStateManager(context, namespace, key), value);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java
new file mode 100644
index 0000000..d4c40ad
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+
+/**
+ * JUnit rule used to restore a {@link Settings} preference after the test is run.
+ *
+ * <p>It stores the current value before the test, and restores it after the test (if necessary).
+ */
+public class SettingsStateKeeperRule extends StateKeeperRule<String> {
+
+    /**
+     * Default constructor.
+     *
+     * @param context context used to retrieve the {@link Settings} provider.
+     * @param key prefence key.
+     */
+    public SettingsStateKeeperRule(@NonNull Context context, @NonNull String key) {
+        super(new SettingsStateManager(context, SettingsHelper.NAMESPACE_SECURE, key));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java
new file mode 100644
index 0000000..ddfdc32
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java
@@ -0,0 +1,59 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Manages the state of a preference backed by {@link Settings}.
+ */
+public class SettingsStateManager implements StateManager<String> {
+
+    private final Context mContext;
+    private final String mNamespace;
+    private final String mKey;
+
+    /**
+     * Default constructor.
+     *
+     * @param context context used to retrieve the {@link Settings} provider.
+     * @param namespace settings namespace.
+     * @param key prefence key.
+     */
+    public SettingsStateManager(@NonNull Context context, @NonNull String namespace,
+            @NonNull String key) {
+        mContext = Preconditions.checkNotNull(context);
+        mNamespace = Preconditions.checkNotNull(namespace);
+        mKey = Preconditions.checkNotNull(key);
+    }
+
+    @Override
+    public void set(@Nullable String value) {
+        SettingsHelper.syncSet(mContext, mNamespace, mKey, value);
+    }
+
+    @Override
+    @Nullable
+    public String get() {
+        return SettingsHelper.get(mNamespace, mKey);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
new file mode 100644
index 0000000..9e0730b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+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);
+        }
+    }
+
+    /**
+     * Tap on the view center, it may change window focus.
+     */
+    public static void tap(View view) {
+        final int[] xy = new int[2];
+        view.getLocationOnScreen(xy);
+        final int viewWidth = view.getWidth();
+        final int viewHeight = view.getHeight();
+        final int x = (int) (xy[0] + (viewWidth / 2.0f));
+        final int y = (int) (xy[1] + (viewHeight / 2.0f));
+
+        runShellCommand("input touchscreen tap %d %d", x, y);
+    }
+
+
+    private ShellHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java
new file mode 100644
index 0000000..8e94e2d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java
@@ -0,0 +1,73 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Objects;
+
+/**
+ * JUnit rule used to prepare a state before the test is run.
+ *
+ * <p>It stores the current state before the test, changes it (if necessary), then restores it after
+ * the test (if necessary).
+ */
+public class StateChangerRule<T> implements TestRule {
+
+    private final StateManager<T> mStateManager;
+    private final T mValue;
+
+    /**
+     * Default constructor.
+     *
+     * @param stateManager abstraction used to mange the state.
+     * @param value value to be set before the test is run.
+     */
+    public StateChangerRule(@NonNull StateManager<T> stateManager, @Nullable T value) {
+        mStateManager = Preconditions.checkNotNull(stateManager);
+        mValue = value;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final T previousValue = mStateManager.get();
+                if (!Objects.equals(previousValue, mValue)) {
+                    mStateManager.set(mValue);
+                }
+                try {
+                    base.evaluate();
+                } finally {
+                    final T currentValue = mStateManager.get();
+                    if (!Objects.equals(currentValue, previousValue)) {
+                        mStateManager.set(previousValue);
+                    }
+                }
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java
new file mode 100644
index 0000000..9c2018e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StateChangerRuleTest {
+
+    private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
+    private final Description mDescription = Description.createSuiteDescription("Whatever");
+
+    @Mock
+    private StateManager<String> mStateManager;
+
+    @Mock
+    private Statement mStatement;
+
+    @Test
+    public void testInvalidConstructor() {
+        assertThrows(NullPointerException.class,
+                () -> new StateChangerRule<Object>(null, "value"));
+    }
+
+    @Test
+    public void testSetAndRestoreOnSuccess() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "newValue");
+        when(mStateManager.get()).thenReturn("before", "changed");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("newValue");
+        verify(mStateManager, times(1)).set("before");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testDontSetIfSameValueOnSuccess() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "sameValue");
+        when(mStateManager.get()).thenReturn("sameValue");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, never()).set(anyString());
+    }
+
+    @Test
+    public void testSetButDontRestoreIfSameValueOnSuccess() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "newValue");
+        when(mStateManager.get()).thenReturn("before", "before");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("newValue");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testDontSetButRestoreIfValueChangedOnSuccess() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "sameValue");
+        when(mStateManager.get()).thenReturn("sameValue", "changed");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, times(1)).set("sameValue");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testSetAndRestoreOnFailure() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "newValue");
+        when(mStateManager.get()).thenReturn("before", "changed");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("newValue");
+        verify(mStateManager, times(1)).set("before");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testDontSetIfSameValueOnFailure() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "sameValue");
+        when(mStateManager.get()).thenReturn("sameValue");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+
+        verify(mStateManager, never()).set(anyString());
+    }
+
+    @Test
+    public void testSetButDontRestoreIfSameValueOnFailure() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "newValue");
+        when(mStateManager.get()).thenReturn("before", "before");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("newValue");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testDontSetButRestoreIfValueChangedOnFailure() throws Throwable {
+        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+                "sameValue");
+        when(mStateManager.get()).thenReturn("sameValue", "changed");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+        assertThat(actualException).isSameAs(mRuntimeException);
+
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("sameValue");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
new file mode 100644
index 0000000..4d5ced7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
@@ -0,0 +1,65 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Objects;
+
+/**
+ * JUnit rule used to restore a state after the test is run.
+ *
+ * <p>It stores the current state before the test, and restores it after the test (if necessary).
+ */
+public class StateKeeperRule<T> implements TestRule {
+
+    private final StateManager<T> mStateManager;
+
+    /**
+     * Default constructor.
+     *
+     * @param stateManager abstraction used to mange the state.
+     */
+    public StateKeeperRule(@NonNull StateManager<T> stateManager) {
+        mStateManager = Preconditions.checkNotNull(stateManager);
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final T previousValue = mStateManager.get();
+                try {
+                    base.evaluate();
+                } finally {
+                    final T currentValue = mStateManager.get();
+                    if (!Objects.equals(previousValue, currentValue)) {
+                        mStateManager.set(previousValue);
+                    }
+                }
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java
new file mode 100644
index 0000000..6424d3c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StateKeeperRuleTest {
+
+    private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
+    private final Description mDescription = Description.createSuiteDescription("Whatever");
+
+    @Mock
+    private StateManager<String> mStateManager;
+
+    @Mock
+    private Statement mStatement;
+
+    @Test
+    public void testInvalidConstructor() {
+        assertThrows(NullPointerException.class, () -> new StateKeeperRule<Object>(null));
+    }
+
+    @Test
+    public void testRestoreOnSuccess() throws Throwable {
+        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+        when(mStateManager.get()).thenReturn("before", "changed");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("before");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testRestoreOnFailure() throws Throwable {
+        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+        when(mStateManager.get()).thenReturn("before", "changed");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+
+        assertThat(actualException).isSameAs(mRuntimeException);
+        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+        verify(mStateManager, times(1)).set("before");
+        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+    }
+
+    @Test
+    public void testDoNotRestoreWhenNotChanged() throws Throwable {
+        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+        when(mStateManager.get()).thenReturn("not_changed");
+
+        rule.apply(mStatement, mDescription).evaluate();
+
+        verify(mStatement, times(1)).evaluate();
+        verify(mStateManager, never()).set(anyString());
+    }
+
+    @Test
+    public void testDoNotRestoreOnFailure() throws Throwable {
+        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+        when(mStateManager.get()).thenReturn("not_changed");
+        doThrow(mRuntimeException).when(mStatement).evaluate();
+
+        final RuntimeException actualException = expectThrows(RuntimeException.class,
+                () -> rule.apply(mStatement, mDescription).evaluate());
+
+        assertThat(actualException).isSameAs(mRuntimeException);
+
+        verify(mStateManager, never()).set(anyString());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java
new file mode 100644
index 0000000..376a555
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java
@@ -0,0 +1,34 @@
+/*
+ * 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.autofillservice.cts.common;
+
+import android.support.annotation.Nullable;
+
+/**
+ * Abstraction for a state that is managed somewhere, like Android Settings.
+ */
+public interface StateManager<T> {
+
+    /**
+     * Sets a new state.
+     */
+    void set(@Nullable T value);
+
+    /**
+     * Gets the current state.
+     */
+    @Nullable T get();
+}
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index bb1f8fc..e12f2c4 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -21,7 +21,11 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ctstestserver mockito-target-minus-junit4
 
diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml
index 333045a..28745f3 100644
--- a/tests/backup/AndroidManifest.xml
+++ b/tests/backup/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <uses-library android:name="org.apache.http.legacy" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index 015f595..8e0bfa0 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License
   -->
 <configuration description="Config for CTS Backup test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="backup" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 8161a95..138c774 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -23,6 +23,8 @@
         android:backupAgent="FullBackupBackupAgent"
         android:label="Android Backup CTS App"
         android:fullBackupOnly="true">
+        <uses-library android:name="android.test.runner" />
+
 
         <activity
             android:name=".MainActivity"
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index c99b4b4..3ed302d 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -22,6 +22,8 @@
         android:allowBackup="true"
         android:backupAgent="android.backup.app.KeyValueBackupAgent"
         android:label="Android Key Value Backup CTS App">
+        <uses-library android:name="android.test.runner" />
+
         <activity
             android:name="android.backup.app.MainActivity"
             android:label="Android Key Value Backup CTS App" >
diff --git a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
index bbd0d26..343f2d9 100644
--- a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
+++ b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
@@ -21,14 +21,14 @@
 import android.os.ParcelFileDescriptor;
 import android.test.InstrumentationTestCase;
 
+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;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Base class for backup instrumentation tests.
@@ -41,9 +41,14 @@
     private static final String LOCAL_TRANSPORT =
             "android/com.android.internal.backup.LocalTransport";
 
-    private static final int SMALL_LOGCAT_DELAY = 1000;
-
     private boolean isBackupSupported;
+    private LogcatInspector mLogcatInspector =
+            new LogcatInspector() {
+                @Override
+                protected InputStream executeShellCommand(String command) throws IOException {
+                    return executeStreamedShellCommand(getInstrumentation(), command);
+                }
+            };
 
     @Override
     protected void setUp() throws Exception {
@@ -73,55 +78,16 @@
         return output.contains("* " + LOCAL_TRANSPORT);
     }
 
-    /**
-     * Attempts to clear logcat.
-     *
-     * Clearing logcat is known to be unreliable, so this methods also output a unique separator
-     * that can be used to find this point in the log even if clearing failed.
-     * @return a unique separator string
-     * @throws Exception
-     */
-    protected String clearLogcat() throws Exception {
-        exec("logcat -c");
-        String uniqueString = ":::" + UUID.randomUUID().toString();
-        exec("log -t " + APP_LOG_TAG + " " + uniqueString);
-        return uniqueString;
+    /** See {@link LogcatInspector#mark(String)}. */
+    protected String markLogcat() throws Exception {
+        return mLogcatInspector.mark(APP_LOG_TAG);
     }
 
-    /**
-     * Wait for up to maxTimeoutInSeconds for the given strings to appear in the logcat in the given order.
-     * By passing the separator returned by {@link #clearLogcat} as the first string you can ensure that only
-     * logs emitted after that call to clearLogcat are found.
-     *
-     * @throws AssertionError if the strings are not found in the given time.
-     */
+    /** See {@link LogcatInspector#assertLogcatContainsInOrder(String, int, String...)}. */
     protected void waitForLogcat(int maxTimeoutInSeconds, String... logcatStrings)
-        throws Exception {
-        long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(maxTimeoutInSeconds);
-        int stringIndex = 0;
-        while (timeout >= System.currentTimeMillis()) {
-            FileInputStream fis = executeStreamedShellCommand(getInstrumentation(),
-                    "logcat -v brief -d " + APP_LOG_TAG + ":* *:S");
-            BufferedReader log = new BufferedReader(new InputStreamReader(fis));
-            String line;
-            stringIndex = 0;
-            while ((line = log.readLine()) != null) {
-                if (line.contains(logcatStrings[stringIndex])) {
-                    stringIndex++;
-                    if (stringIndex >= logcatStrings.length) {
-                        drainAndClose(log);
-                        return;
-                    }
-                }
-            }
-            closeQuietly(log);
-            // In case the key has not been found, wait for the log to update before
-            // performing the next search.
-            Thread.sleep(SMALL_LOGCAT_DELAY);
-        }
-        fail("Couldn't find " + logcatStrings[stringIndex] +
-            (stringIndex > 0 ? " after " + logcatStrings[stringIndex - 1] : "") +
-            " within " + maxTimeoutInSeconds + " seconds ");
+            throws Exception {
+        mLogcatInspector.assertLogcatContainsInOrder(
+                APP_LOG_TAG + ":* *:S", maxTimeoutInSeconds, logcatStrings);
     }
 
     protected void createTestFileOfSize(String packageName, int size) throws Exception {
@@ -145,8 +111,8 @@
         }
     }
 
-    private static FileInputStream executeStreamedShellCommand(Instrumentation instrumentation,
-                                                               String command) throws Exception {
+    private static FileInputStream executeStreamedShellCommand(
+            Instrumentation instrumentation, String command) throws IOException {
         final ParcelFileDescriptor pfd =
                 instrumentation.getUiAutomation().executeShellCommand(command);
         return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
diff --git a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
index beefa01..9f81ecd 100644
--- a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
@@ -31,7 +31,7 @@
         if (!isBackupSupported()) {
             return;
         }
-        String backupSeparator = clearLogcat();
+        String backupSeparator = markLogcat();
 
         // Make sure there's something to backup
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
@@ -45,7 +45,7 @@
             "Full backup requested",
             "onDestroy");
 
-        String restoreSeparator = clearLogcat();
+        String restoreSeparator = markLogcat();
 
         // Now request restore and wait for it to complete
         exec("bmgr restore " + BACKUP_APP_NAME);
diff --git a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
index 3924e87..56d489d 100644
--- a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
@@ -36,7 +36,7 @@
         if (!isBackupSupported()) {
             return;
         }
-        String separator = clearLogcat();
+        String separator = markLogcat();
         // Launch test app and create file exceeding limit for local transport
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
 
@@ -59,7 +59,7 @@
             Thread.sleep(3000);
         } catch (InterruptedException e) {}
 
-        String separator = clearLogcat();
+        String separator = markLogcat();
         exec("bmgr backupnow " + 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 d957bbc..0bb5243 100644
--- a/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
@@ -31,7 +31,7 @@
         if (!isBackupSupported()) {
             return;
         }
-        String backupSeparator = clearLogcat();
+        String backupSeparator = markLogcat();
 
         // Make sure there's something to backup
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
@@ -44,7 +44,7 @@
             "Backup requested",
             "onDestroy");
 
-        String restoreSeparator = clearLogcat();
+        String restoreSeparator = markLogcat();
 
         // Now request restore and wait for it to complete
         exec("bmgr restore " + BACKUP_APP_NAME);
diff --git a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
index c29f810..6fca9ad 100644
--- a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
@@ -36,7 +36,7 @@
         if (!isBackupSupported()) {
             return;
         }
-        String separator = clearLogcat();
+        String separator = markLogcat();
         // Launch test app and create file exceeding limit for local transport
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
 
@@ -50,7 +50,7 @@
         if (!isBackupSupported()) {
             return;
         }
-        String separator = clearLogcat();
+        String separator = markLogcat();
         exec("bmgr backupnow " + BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS, separator,
             "quota is " + LOCAL_TRANSPORT_BACKUP_QUOTA);
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index c8ba9cd..5ca2315 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -45,7 +45,8 @@
 	ctstestrunner \
 	mockito-target-minus-junit4 \
 	android-ex-camera2 \
-	CtsCameraUtils
+	CtsCameraUtils \
+	truth-prebuilt
 
 LOCAL_JNI_SHARED_LIBRARIES := \
 	libctscamera2_jni \
@@ -64,7 +65,7 @@
 
 LOCAL_SDK_VERSION := test_current
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 cts_runtime_hint := 120
 
diff --git a/tests/camera/AndroidTest.xml b/tests/camera/AndroidTest.xml
index c046a59..2a2cdc9 100644
--- a/tests/camera/AndroidTest.xml
+++ b/tests/camera/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Camera test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="camera" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/camera/api25test/Android.mk b/tests/camera/api25test/Android.mk
index a848031..8a2c43e 100644
--- a/tests/camera/api25test/Android.mk
+++ b/tests/camera/api25test/Android.mk
@@ -35,7 +35,7 @@
 
 LOCAL_SDK_VERSION := 25
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts
diff --git a/tests/camera/api25test/AndroidTest.xml b/tests/camera/api25test/AndroidTest.xml
index a7b2ae7..45f5d28 100644
--- a/tests/camera/api25test/AndroidTest.xml
+++ b/tests/camera/api25test/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Camera API 25 test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="camera" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/camera/libctscamera2jni/Android.mk b/tests/camera/libctscamera2jni/Android.mk
index 649908d..2cd4f64 100644
--- a/tests/camera/libctscamera2jni/Android.mk
+++ b/tests/camera/libctscamera2jni/Android.mk
@@ -44,7 +44,7 @@
     libz \
 
 # NDK build, shared C++ runtime
-LOCAL_SDK_VERSION := 24
+LOCAL_SDK_VERSION := current
 LOCAL_NDK_STL_VARIANT := c++_shared
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b2124ce..1a35a9f 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -18,9 +18,12 @@
 #define LOG_TAG "NativeCamera"
 #include <log/log.h>
 
+#include <chrono>
+#include <condition_variable>
 #include <string>
 #include <map>
 #include <mutex>
+#include <vector>
 #include <unistd.h>
 #include <assert.h>
 #include <jni.h>
@@ -238,6 +241,173 @@
     int mOnActive = 0;
 };
 
+class CaptureResultListener {
+  public:
+    ~CaptureResultListener() {
+        std::unique_lock<std::mutex> l(mMutex);
+        clearSavedRequestsLocked();
+    }
+
+    static void onCaptureStart(void* /*obj*/, ACameraCaptureSession* /*session*/,
+            const ACaptureRequest* /*request*/, int64_t /*timestamp*/) {
+        //Not used for now
+    }
+
+    static void onCaptureProgressed(void* /*obj*/, ACameraCaptureSession* /*session*/,
+            ACaptureRequest* /*request*/, const ACameraMetadata* /*result*/) {
+        //Not used for now
+    }
+
+    static void onCaptureCompleted(void* obj, ACameraCaptureSession* /*session*/,
+            ACaptureRequest* request, const ACameraMetadata* result) {
+        ALOGV("%s", __FUNCTION__);
+        if ((obj == nullptr) || (result == nullptr)) {
+            return;
+        }
+        CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+        ACameraMetadata_const_entry entry;
+        auto ret = ACameraMetadata_getConstEntry(result, ACAMERA_SYNC_FRAME_NUMBER, &entry);
+        if (ret != ACAMERA_OK) {
+            ALOGE("Error: Sync frame number missing from result!");
+            return;
+        }
+
+        if (thiz->mSaveCompletedRequests) {
+            thiz->mCompletedRequests.push_back(ACaptureRequest_copy(request));
+        }
+
+        thiz->mLastCompletedFrameNumber = entry.data.i64[0];
+        thiz->mResultCondition.notify_one();
+    }
+
+    static void onCaptureFailed(void* obj, ACameraCaptureSession* /*session*/,
+            ACaptureRequest* /*request*/, ACameraCaptureFailure* failure) {
+        ALOGV("%s", __FUNCTION__);
+        if ((obj == nullptr) || (failure == nullptr)) {
+            return;
+        }
+        CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+        thiz->mLastFailedFrameNumber = failure->frameNumber;
+        thiz->mResultCondition.notify_one();
+    }
+
+    static void onCaptureSequenceCompleted(void* obj, ACameraCaptureSession* /*session*/,
+            int sequenceId, int64_t frameNumber) {
+        ALOGV("%s", __FUNCTION__);
+        if (obj == nullptr) {
+            return;
+        }
+        CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+        thiz->mLastSequenceIdCompleted = sequenceId;
+        thiz->mLastSequenceFrameNumber = frameNumber;
+        thiz->mResultCondition.notify_one();
+    }
+
+    static void onCaptureSequenceAborted(void* /*obj*/, ACameraCaptureSession* /*session*/,
+            int /*sequenceId*/) {
+        //Not used for now
+    }
+
+    static void onCaptureBufferLost(void* obj, ACameraCaptureSession* /*session*/,
+            ACaptureRequest* /*request*/, ANativeWindow* /*window*/, int64_t frameNumber) {
+        ALOGV("%s", __FUNCTION__);
+        if (obj == nullptr) {
+            return;
+        }
+        CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+        thiz->mLastLostFrameNumber = frameNumber;
+        thiz->mResultCondition.notify_one();
+    }
+
+    int64_t getCaptureSequenceLastFrameNumber(int64_t sequenceId, uint32_t timeoutSec) {
+        int64_t ret = -1;
+        std::unique_lock<std::mutex> l(mMutex);
+
+        while (mLastSequenceIdCompleted != sequenceId) {
+            auto timeout = std::chrono::system_clock::now() +
+                           std::chrono::seconds(timeoutSec);
+            if (std::cv_status::timeout == mResultCondition.wait_until(l, timeout)) {
+                break;
+            }
+        }
+
+        if (mLastSequenceIdCompleted == sequenceId) {
+            ret = mLastSequenceFrameNumber;
+        }
+
+        return ret;
+    }
+
+    bool waitForFrameNumber(int64_t frameNumber, uint32_t timeoutSec) {
+        bool ret = false;
+        std::unique_lock<std::mutex> l(mMutex);
+
+        while ((mLastCompletedFrameNumber != frameNumber) &&
+                (mLastLostFrameNumber != frameNumber) &&
+                (mLastFailedFrameNumber != frameNumber)) {
+            auto timeout = std::chrono::system_clock::now() +
+                           std::chrono::seconds(timeoutSec);
+            if (std::cv_status::timeout == mResultCondition.wait_until(l, timeout)) {
+                break;
+            }
+        }
+
+        if ((mLastCompletedFrameNumber == frameNumber) ||
+                (mLastLostFrameNumber == frameNumber) ||
+                (mLastFailedFrameNumber == frameNumber)) {
+            ret = true;
+        }
+
+        return ret;
+    }
+
+    void setRequestSave(bool enable) {
+        std::unique_lock<std::mutex> l(mMutex);
+        if (!enable) {
+            clearSavedRequestsLocked();
+        }
+        mSaveCompletedRequests = enable;
+    }
+
+    // The lifecycle of returned ACaptureRequest* is still managed by CaptureResultListener
+    void getCompletedRequests(std::vector<ACaptureRequest*>* out) {
+        std::unique_lock<std::mutex> l(mMutex);
+        *out = mCompletedRequests;
+    }
+
+    void reset() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mLastSequenceIdCompleted = -1;
+        mLastSequenceFrameNumber = -1;
+        mLastCompletedFrameNumber = -1;
+        mLastLostFrameNumber = -1;
+        mLastFailedFrameNumber = -1;
+        mSaveCompletedRequests = false;
+        clearSavedRequestsLocked();
+    }
+
+  private:
+    std::mutex mMutex;
+    std::condition_variable mResultCondition;
+    int mLastSequenceIdCompleted = -1;
+    int64_t mLastSequenceFrameNumber = -1;
+    int64_t mLastCompletedFrameNumber = -1;
+    int64_t mLastLostFrameNumber = -1;
+    int64_t mLastFailedFrameNumber = -1;
+    bool    mSaveCompletedRequests = false;
+    std::vector<ACaptureRequest*> mCompletedRequests;
+
+    void clearSavedRequestsLocked() {
+        for (ACaptureRequest* req : mCompletedRequests) {
+            ACaptureRequest_free(req);
+        }
+        mCompletedRequests.clear();
+    }
+};
 
 class ImageReaderListener {
   public:
@@ -401,6 +571,7 @@
     // Free all resources except camera manager
     void resetCamera() {
         mSessionListener.reset();
+        mResultListener.reset();
         if (mSession) {
             ACameraCaptureSession_close(mSession);
             mSession = nullptr;
@@ -502,6 +673,16 @@
         return mCameraIdList->cameraIds[idx];
     }
 
+    camera_status_t updateOutput(JNIEnv* env, ACaptureSessionOutput *output) {
+        if (mSession == nullptr) {
+            ALOGE("Testcase cannot update output configuration session %p",
+                    mSession);
+            return ACAMERA_ERROR_UNKNOWN;
+        }
+
+        return ACameraCaptureSession_updateSharedOutput(mSession, output);
+    }
+
     camera_status_t openCamera(const char* cameraId) {
         if (mDevice) {
             ALOGE("Cannot open camera before closing previously open one");
@@ -573,7 +754,8 @@
         return mPreviewAnw;
     }
 
-    camera_status_t createCaptureSessionWithLog() {
+    camera_status_t createCaptureSessionWithLog(bool isPreviewShared = false,
+            ACaptureRequest *sessionParameters = nullptr) {
         if (mSession) {
             LOG_ERROR(errorString, "Cannot create session before closing existing one");
             return ACAMERA_ERROR_UNKNOWN;
@@ -611,7 +793,11 @@
         }
 
         if (mPreviewInited) {
-            ret = ACaptureSessionOutput_create(mPreviewAnw, &mPreviewOutput);
+            if (isPreviewShared) {
+                ret = ACaptureSessionSharedOutput_create(mPreviewAnw, &mPreviewOutput);
+            } else {
+                ret = ACaptureSessionOutput_create(mPreviewAnw, &mPreviewOutput);
+            }
             if (ret != ACAMERA_OK || mPreviewOutput == nullptr) {
                 LOG_ERROR(errorString,
                         "Sesssion preview output create fail! ret %d output %p",
@@ -629,8 +815,8 @@
             }
         }
 
-        ret = ACameraDevice_createCaptureSession(
-                mDevice, mOutputs, &mSessionCb, &mSession);
+        ret = ACameraDevice_createCaptureSessionWithSessionParameters(
+                mDevice, mOutputs, sessionParameters, &mSessionCb, &mSession);
         if (ret != ACAMERA_OK || mSession == nullptr) {
             LOG_ERROR(errorString, "Create session for camera %s failed. ret %d session %p",
                     mCameraId, ret, mSession);
@@ -743,15 +929,69 @@
         return ACAMERA_OK;
     }
 
-    camera_status_t startPreview() {
+    // The output ACaptureRequest* is still managed by testcase class
+    camera_status_t getStillRequest(ACaptureRequest** out) {
+        if (mStillRequest == nullptr) {
+            ALOGE("Camera %s Still capture request hasn't been created", mCameraId);
+            return ACAMERA_ERROR_INVALID_PARAMETER;
+        }
+        *out = mStillRequest;
+        return ACAMERA_OK;
+    }
+
+    camera_status_t getPreviewRequest(ACaptureRequest** out) {
+        if (mPreviewRequest == nullptr) {
+            ALOGE("Camera %s Preview capture request hasn't been created", mCameraId);
+            return ACAMERA_ERROR_INVALID_PARAMETER;
+        }
+        *out = mPreviewRequest;
+        return ACAMERA_OK;
+    }
+
+    camera_status_t startPreview(int *sequenceId = nullptr) {
         if (mSession == nullptr || mPreviewRequest == nullptr) {
             ALOGE("Testcase cannot start preview: session %p, preview request %p",
                     mSession, mPreviewRequest);
             return ACAMERA_ERROR_UNKNOWN;
         }
         int previewSeqId;
-        return ACameraCaptureSession_setRepeatingRequest(
-                mSession, nullptr, 1, &mPreviewRequest, &previewSeqId);
+        camera_status_t ret;
+        if (sequenceId == nullptr) {
+            ret = ACameraCaptureSession_setRepeatingRequest(
+                   mSession, nullptr, 1, &mPreviewRequest, &previewSeqId);
+        } else {
+            ret = ACameraCaptureSession_setRepeatingRequest(
+                   mSession, &mResultCb, 1, &mPreviewRequest, sequenceId);
+        }
+        return ret;
+    }
+
+    camera_status_t updateRepeatingRequest(ACaptureRequest *updatedRequest,
+            int *sequenceId = nullptr) {
+        if (mSession == nullptr || updatedRequest == nullptr) {
+            ALOGE("Testcase cannot update repeating request: session %p, updated request %p",
+                    mSession, updatedRequest);
+            return ACAMERA_ERROR_UNKNOWN;
+        }
+
+        int previewSeqId;
+        camera_status_t ret;
+        if (sequenceId == nullptr) {
+            ret = ACameraCaptureSession_setRepeatingRequest(
+                    mSession, nullptr, 1, &updatedRequest, &previewSeqId);
+        } else {
+            ret = ACameraCaptureSession_setRepeatingRequest(
+                    mSession, &mResultCb, 1, &updatedRequest, sequenceId);
+        }
+        return ret;
+    }
+
+    int64_t getCaptureSequenceLastFrameNumber(int64_t sequenceId, uint32_t timeoutSec) {
+        return mResultListener.getCaptureSequenceLastFrameNumber(sequenceId, timeoutSec);
+    }
+
+    bool waitForFrameNumber(int64_t frameNumber, uint32_t timeoutSec) {
+        return mResultListener.waitForFrameNumber(frameNumber, timeoutSec);
     }
 
     camera_status_t takePicture() {
@@ -765,6 +1005,18 @@
                 mSession, nullptr, 1, &mStillRequest, &seqId);
     }
 
+    camera_status_t capture(ACaptureRequest* request,
+            ACameraCaptureSession_captureCallbacks* listener,
+            /*out*/int* seqId) {
+        if (mSession == nullptr || request == nullptr) {
+            ALOGE("Testcase cannot capture session: session %p, request %p",
+                    mSession, request);
+            return ACAMERA_ERROR_UNKNOWN;
+        }
+        return ACameraCaptureSession_capture(
+                mSession, listener, 1, &request, seqId);
+    }
+
     camera_status_t resetWithErrorLog() {
         camera_status_t ret;
 
@@ -785,6 +1037,7 @@
             return ACAMERA_ERROR_UNKNOWN;
         }
         mSessionListener.reset();
+        mResultListener.reset();
 
         ret = closeCamera();
         if (ret != ACAMERA_OK) {
@@ -800,6 +1053,14 @@
         return &mSessionListener;
     }
 
+    ACameraDevice* getCameraDevice() {
+        return mDevice;
+    }
+
+    ACaptureSessionOutput *getPreviewOutput() {
+        return mPreviewOutput;
+    }
+
   private:
     ACameraManager* createManager() {
         if (!mCameraManager) {
@@ -828,6 +1089,18 @@
         CaptureSessionListener::onActive
     };
 
+    CaptureResultListener mResultListener;
+    ACameraCaptureSession_captureCallbacks mResultCb {
+        &mResultListener,
+        CaptureResultListener::onCaptureStart,
+        CaptureResultListener::onCaptureProgressed,
+        CaptureResultListener::onCaptureCompleted,
+        CaptureResultListener::onCaptureFailed,
+        CaptureResultListener::onCaptureSequenceCompleted,
+        CaptureResultListener::onCaptureSequenceAborted,
+        CaptureResultListener::onCaptureBufferLost
+    };
+
     ACameraIdList* mCameraIdList = nullptr;
     ACameraDevice* mDevice = nullptr;
     AImageReader* mImgReader = nullptr;
@@ -1241,6 +1514,34 @@
                 }
             }
 
+            void* context = nullptr;
+            ret = ACaptureRequest_getUserContext(request, &context);
+            if (ret != ACAMERA_OK) {
+                LOG_ERROR(errorString, "Get capture request context failed: ret %d", ret);
+                goto cleanup;
+            }
+            if (context != nullptr) {
+                LOG_ERROR(errorString, "Capture request context is not null: %p", context);
+                goto cleanup;
+            }
+
+            intptr_t magic_num = 0xBEEF;
+            ret = ACaptureRequest_setUserContext(request, (void*) magic_num);
+            if (ret != ACAMERA_OK) {
+                LOG_ERROR(errorString, "Set capture request context failed: ret %d", ret);
+                goto cleanup;
+            }
+
+            ret = ACaptureRequest_getUserContext(request, &context);
+            if (ret != ACAMERA_OK) {
+                LOG_ERROR(errorString, "Get capture request context failed: ret %d", ret);
+                goto cleanup;
+            }
+            if (context != (void*) magic_num) {
+                LOG_ERROR(errorString, "Capture request context is wrong: %p", context);
+                goto cleanup;
+            }
+
             // try get/set capture request fields
             ACameraMetadata_const_entry entry;
             ret = ACaptureRequest_getConstEntry(request, ACAMERA_CONTROL_AE_MODE, &entry);
@@ -1483,6 +1784,263 @@
 
 extern "C" jboolean
 Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
+testCameraDeviceSharedOutputUpdate(
+        JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface, jobject jSharedSurface) {
+    ALOGV("%s", __FUNCTION__);
+    int numCameras = 0;
+    bool pass = false;
+    PreviewTestCase testCase;
+    int sequenceId = -1;
+    int64_t lastFrameNumber = 0;
+    bool frameArrived = false;
+    ANativeWindow* previewAnw = nullptr;
+    ANativeWindow* sharedAnw = ANativeWindow_fromSurface(env, jSharedSurface);
+    ACaptureRequest* updatedRequest = nullptr;
+    ACameraOutputTarget* reqPreviewOutput = nullptr;
+    ACameraOutputTarget* reqSharedOutput = nullptr;
+    ACaptureSessionOutput *previewOutput = nullptr;
+    uint32_t timeoutSec = 1;
+    uint32_t runPreviewSec = 2;
+
+    camera_status_t ret = testCase.initWithErrorLog();
+    if (ret != ACAMERA_OK) {
+        // Don't log error here. testcase did it
+        goto cleanup;
+    }
+
+    numCameras = testCase.getNumCameras();
+    if (numCameras < 0) {
+        LOG_ERROR(errorString, "Testcase returned negavtive number of cameras: %d", numCameras);
+        goto cleanup;
+    }
+
+    for (int i = 0; i < numCameras; i++) {
+        const char* cameraId = testCase.getCameraId(i);
+        if (cameraId == nullptr) {
+            LOG_ERROR(errorString, "Testcase returned null camera id for camera %d", i);
+            goto cleanup;
+        }
+
+        ret = testCase.openCamera(cameraId);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Open camera device %s failure. ret %d", cameraId, ret);
+            goto cleanup;
+        }
+
+        usleep(100000); // sleep to give some time for callbacks to happen
+
+        if (testCase.isCameraAvailable(cameraId)) {
+            LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
+            goto cleanup;
+        }
+
+        previewAnw = testCase.initPreviewAnw(env, jPreviewSurface);
+        if (previewAnw == nullptr) {
+            LOG_ERROR(errorString, "Null ANW from preview surface!");
+            goto cleanup;
+        }
+
+        ret = testCase.createCaptureSessionWithLog(true);
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ret = testCase.createRequestsWithErrorLog();
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ret = testCase.startPreview();
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Start preview failed!");
+            goto cleanup;
+        }
+
+        sleep(runPreviewSec);
+
+        previewOutput = testCase.getPreviewOutput();
+        //Try some bad input
+        ret = ACaptureSessionSharedOutput_add(previewOutput, previewAnw);
+        if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+            LOG_ERROR(errorString, "ACaptureSessionSharedOutput_add should return invalid "
+                    "parameter! %d", ret);
+            goto cleanup;
+        }
+
+        ret = ACaptureSessionSharedOutput_remove(previewOutput, previewAnw);
+        if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+            LOG_ERROR(errorString, "ACaptureSessionSharedOutput_remove should return invalid "
+                    "parameter! %d", ret);
+            goto cleanup;
+        }
+
+        //Now try with valid input
+        ret = ACaptureSessionSharedOutput_add(previewOutput, sharedAnw);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "ACaptureSessionSharedOutput_add failed!")
+            goto cleanup;
+        }
+
+        ret = testCase.updateOutput(env, previewOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Failed to update output configuration!")
+            goto cleanup;
+        }
+
+        ret = ACameraDevice_createCaptureRequest(
+                testCase.getCameraDevice(), TEMPLATE_PREVIEW, &updatedRequest);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s create preview request failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = ACameraOutputTarget_create(previewAnw, &reqPreviewOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString,
+                    "Camera %s create request preview output target failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = ACaptureRequest_addTarget(updatedRequest, reqPreviewOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s add preview request output failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = ACameraOutputTarget_create(sharedAnw, &reqSharedOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString,
+                    "Camera %s create request preview output target failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = ACaptureRequest_addTarget(updatedRequest, reqSharedOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s add preview request output failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = testCase.updateRepeatingRequest(updatedRequest, &sequenceId);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s failed to update repeated request. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        sleep(runPreviewSec);
+
+        ret = ACaptureSessionSharedOutput_remove(previewOutput, sharedAnw);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "ACaptureSessionSharedOutput_remove failed!");
+            goto cleanup;
+        }
+
+        //Try removing shared output which still has pending camera requests
+        ret = testCase.updateOutput(env, previewOutput);
+        if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+            LOG_ERROR(errorString, "updateOutput should fail!");
+            goto cleanup;
+        }
+
+        //Remove the shared output correctly by updating the repeating request
+        //first
+        ret = ACaptureRequest_removeTarget(updatedRequest, reqSharedOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s remove target output failed. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        ret = testCase.updateRepeatingRequest(updatedRequest);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Camera %s failed to update repeated request. ret %d",
+                    cameraId, ret);
+            goto cleanup;
+        }
+
+        //Then wait for all old requests to flush
+        lastFrameNumber = testCase.getCaptureSequenceLastFrameNumber(sequenceId, timeoutSec);
+        if (lastFrameNumber < 0) {
+            LOG_ERROR(errorString, "Camera %s failed to acquire last frame number!",
+                    cameraId);
+            goto cleanup;
+        }
+
+        frameArrived = testCase.waitForFrameNumber(lastFrameNumber, timeoutSec);
+        if (!frameArrived) {
+            LOG_ERROR(errorString, "Camera %s timed out waiting on last frame number!",
+                    cameraId);
+            goto cleanup;
+        }
+
+        ret = testCase.updateOutput(env, previewOutput);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "updateOutput failed!");
+            goto cleanup;
+        }
+
+        sleep(runPreviewSec);
+
+        ret = testCase.resetWithErrorLog();
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        usleep(100000); // sleep to give some time for callbacks to happen
+
+        if (!testCase.isCameraAvailable(cameraId)) {
+            LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
+            goto cleanup;
+        }
+    }
+
+    ret = testCase.deInit();
+    if (ret != ACAMERA_OK) {
+        LOG_ERROR(errorString, "Testcase deInit failed: ret %d", ret);
+        goto cleanup;
+    }
+
+    pass = true;
+
+cleanup:
+
+    if (updatedRequest != nullptr) {
+        ACaptureRequest_free(updatedRequest);
+        updatedRequest = nullptr;
+    }
+
+    if (reqPreviewOutput != nullptr) {
+        ACameraOutputTarget_free(reqPreviewOutput);
+        reqPreviewOutput = nullptr;
+    }
+
+    if (reqSharedOutput != nullptr) {
+        ACameraOutputTarget_free(reqSharedOutput);
+        reqSharedOutput = nullptr;
+    }
+
+    if (sharedAnw) {
+        ANativeWindow_release(sharedAnw);
+        sharedAnw = nullptr;
+    }
+
+    ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
+    if (!pass) {
+        throwAssertionError(env, errorString);
+    }
+    return pass;
+}
+
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
 testCameraDeviceSimplePreviewNative(
         JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface) {
     ALOGV("%s", __FUNCTION__);
@@ -1577,6 +2135,134 @@
     return pass;
 }
 
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
+testCameraDevicePreviewWithSessionParametersNative(
+        JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface) {
+    ALOGV("%s", __FUNCTION__);
+    int numCameras = 0;
+    bool pass = false;
+    ACameraManager* mgr = ACameraManager_create();
+    ACameraMetadata* chars = nullptr;
+    PreviewTestCase testCase;
+
+    camera_status_t ret = testCase.initWithErrorLog();
+    if (ret != ACAMERA_OK) {
+        // Don't log error here. testcase did it
+        goto cleanup;
+    }
+
+    numCameras = testCase.getNumCameras();
+    if (numCameras < 0) {
+        LOG_ERROR(errorString, "Testcase returned negavtive number of cameras: %d", numCameras);
+        goto cleanup;
+    }
+
+    for (int i = 0; i < numCameras; i++) {
+        const char* cameraId = testCase.getCameraId(i);
+        if (cameraId == nullptr) {
+            LOG_ERROR(errorString, "Testcase returned null camera id for camera %d", i);
+            goto cleanup;
+        }
+
+        ret = ACameraManager_getCameraCharacteristics(
+                mgr, cameraId, &chars);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Get camera characteristics failed: ret %d", ret);
+            goto cleanup;
+        }
+
+        ACameraMetadata_const_entry sessionParamKeys{};
+        ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
+                &sessionParamKeys);
+        if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+            ACameraMetadata_free(chars);
+            chars = nullptr;
+            continue;
+        }
+
+        ret = testCase.openCamera(cameraId);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Open camera device %s failure. ret %d", cameraId, ret);
+            goto cleanup;
+        }
+
+        usleep(100000); // sleep to give some time for callbacks to happen
+
+        if (testCase.isCameraAvailable(cameraId)) {
+            LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
+            goto cleanup;
+        }
+
+        ANativeWindow* previewAnw = testCase.initPreviewAnw(env, jPreviewSurface);
+        if (previewAnw == nullptr) {
+            LOG_ERROR(errorString, "Null ANW from preview surface!");
+            goto cleanup;
+        }
+
+        ret = testCase.createRequestsWithErrorLog();
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ACaptureRequest *previewRequest = nullptr;
+        ret = testCase.getPreviewRequest(&previewRequest);
+        if ((ret != ACAMERA_OK) || (previewRequest == nullptr)) {
+            LOG_ERROR(errorString, "Preview request query failed!");
+            goto cleanup;
+        }
+
+        ret = testCase.createCaptureSessionWithLog(/*isPreviewShared*/ false, previewRequest);
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ret = testCase.startPreview();
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Start preview failed!");
+            goto cleanup;
+        }
+
+        sleep(3);
+
+        ret = testCase.resetWithErrorLog();
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        usleep(100000); // sleep to give some time for callbacks to happen
+
+        if (!testCase.isCameraAvailable(cameraId)) {
+            LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
+            goto cleanup;
+        }
+
+        ACameraMetadata_free(chars);
+        chars = nullptr;
+    }
+
+    ret = testCase.deInit();
+    if (ret != ACAMERA_OK) {
+        LOG_ERROR(errorString, "Testcase deInit failed: ret %d", ret);
+        goto cleanup;
+    }
+
+    pass = true;
+cleanup:
+    if (chars) {
+        ACameraMetadata_free(chars);
+    }
+    ACameraManager_delete(mgr);
+    ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
+    if (!pass) {
+        throwAssertionError(env, errorString);
+    }
+    return pass;
+}
+
 bool nativeImageReaderTestBase(
         JNIEnv* env, jstring jOutPath, AImageReader_ImageCallback cb) {
     const int NUM_TEST_IMAGES = 10;
@@ -1649,14 +2335,68 @@
             goto cleanup;
         }
 
+        CaptureResultListener resultListener;
+        ACameraCaptureSession_captureCallbacks resultCb {
+            &resultListener,
+            CaptureResultListener::onCaptureStart,
+            CaptureResultListener::onCaptureProgressed,
+            CaptureResultListener::onCaptureCompleted,
+            CaptureResultListener::onCaptureFailed,
+            CaptureResultListener::onCaptureSequenceCompleted,
+            CaptureResultListener::onCaptureSequenceAborted,
+            CaptureResultListener::onCaptureBufferLost
+        };
+        resultListener.setRequestSave(true);
+        ACaptureRequest* requestTemplate = nullptr;
+        ret = testCase.getStillRequest(&requestTemplate);
+        if (ret != ACAMERA_OK || requestTemplate == nullptr) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
         // Do some still capture
-        for (int capture = 0; capture < NUM_TEST_IMAGES; capture++) {
-            ret = testCase.takePicture();
+        int lastSeqId = -1;
+        for (intptr_t capture = 0; capture < NUM_TEST_IMAGES; capture++) {
+            ACaptureRequest* req = ACaptureRequest_copy(requestTemplate);
+            ACaptureRequest_setUserContext(req, (void*) capture);
+            int seqId;
+            ret = testCase.capture(req, &resultCb, &seqId);
             if (ret != ACAMERA_OK) {
-                LOG_ERROR(errorString, "Camera %s capture(%d) failed. ret %d",
+                LOG_ERROR(errorString, "Camera %s capture(%" PRIdPTR ") failed. ret %d",
                         cameraId, capture, ret);
                 goto cleanup;
             }
+            if (capture == NUM_TEST_IMAGES - 1) {
+                lastSeqId = seqId;
+            }
+            ACaptureRequest_free(req);
+        }
+
+        // wait until last sequence complete
+        resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 3);
+
+        std::vector<ACaptureRequest*> completedRequests;
+        resultListener.getCompletedRequests(&completedRequests);
+
+        if (completedRequests.size() != NUM_TEST_IMAGES) {
+            LOG_ERROR(errorString, "Camera %s fails to capture %d capture results. Got %zu",
+                    cameraId, NUM_TEST_IMAGES, completedRequests.size());
+            goto cleanup;
+        }
+
+        for (intptr_t i = 0; i < NUM_TEST_IMAGES; i++) {
+            intptr_t userContext = -1;
+            ret = ACaptureRequest_getUserContext(completedRequests[i], (void**) &userContext);
+            if (ret != ACAMERA_OK) {
+                LOG_ERROR(errorString, "Camera %s fails to get request user context", cameraId);
+                goto cleanup;
+            }
+
+            if (userContext != i) {
+                LOG_ERROR(errorString, "Camera %s fails to return matching user context. "
+                        "Expect %" PRIdPTR ", got %" PRIdPTR, cameraId, i, userContext);
+                goto cleanup;
+            }
         }
 
         // wait until all capture finished
diff --git a/tests/camera/res/layout/multi_view.xml b/tests/camera/res/layout/multi_view.xml
index 4f335d3..e7c5508 100644
--- a/tests/camera/res/layout/multi_view.xml
+++ b/tests/camera/res/layout/multi_view.xml
@@ -29,6 +29,18 @@
         android:id="@+id/texture_view_2"
         android:layout_width="160dp"
         android:layout_height="120dp"/>
+    <TextureView
+        android:id="@+id/texture_view_3"
+        android:layout_width="160dp"
+        android:layout_height="120dp"/>
+    <TextureView
+        android:id="@+id/texture_view_4"
+        android:layout_width="160dp"
+        android:layout_height="120dp"/>
+    <TextureView
+        android:id="@+id/texture_view_5"
+        android:layout_width="160dp"
+        android:layout_height="120dp"/>
     <SurfaceView
         android:id="@+id/surface_view_1"
         android:layout_width="160dp"
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
index d6350fc..1f4265d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
@@ -27,7 +27,8 @@
 
 public class Camera2MultiViewCtsActivity extends Activity {
     private final static String TAG = "Camera2MultiViewCtsActivity";
-    private TextureView[] mTextureView = new TextureView[2];
+    public final static int MAX_TEXTURE_VIEWS = 5;
+    private TextureView[] mTextureView = new TextureView[MAX_TEXTURE_VIEWS];
     private SurfaceView[] mSurfaceView = new SurfaceView[2];
 
     @Override
@@ -36,6 +37,9 @@
         setContentView(R.layout.multi_view);
         mTextureView[0] = (TextureView) findViewById(R.id.texture_view_1);
         mTextureView[1] = (TextureView) findViewById(R.id.texture_view_2);
+        mTextureView[2] = (TextureView) findViewById(R.id.texture_view_3);
+        mTextureView[3] = (TextureView) findViewById(R.id.texture_view_4);
+        mTextureView[4] = (TextureView) findViewById(R.id.texture_view_5);
         mSurfaceView[0] = (SurfaceView) findViewById(R.id.surface_view_1);
         mSurfaceView[1] = (SurfaceView) findViewById(R.id.surface_view_2);
 
@@ -44,8 +48,9 @@
     }
 
     public TextureView getTextureView(int index) {
-        if (index < 0 || index > 1) {
-            throw new IllegalArgumentException("Texture view index must be 0 or 1");
+        if (index < 0 || index > (MAX_TEXTURE_VIEWS - 1)) {
+            throw new IllegalArgumentException("Texture view index must be between 0 and " +
+                    MAX_TEXTURE_VIEWS);
         }
         return mTextureView[index];
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
index e1dfbc1..4bd6186 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
@@ -73,12 +73,18 @@
             changeSucceeded = surfaceChangedDone.block(waitTimeMs);
             if (!changeSucceeded) {
                 Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
-                return changeSucceeded;
+                return false;
             } else {
                 // Get a surface change callback, need to check if the size is expected.
                 surfaceChangedDone.close();
-                if (currentWidth == expectWidth && currentHeight == expectHeight) {
-                    return changeSucceeded;
+                synchronized(surfaceLock) {
+                    if (expectWidth == currentWidth && expectHeight == currentHeight) {
+                        return true;
+                    } else {
+                        Log.i(TAG, "Wait for surface changed to " + expectWidth + "x" +
+                                "expectHeight. Got " + currentWidth + "x" + currentHeight +
+                                ". Keep waiting");
+                    }
                 }
                 // Do a further iteration surface change check as surfaceChanged could be called
                 // again.
@@ -88,6 +94,7 @@
         }
 
         // Couldn't get expected surface size change.
+        Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
         return false;
     }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 3d9c683..ffa1043 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -35,10 +35,15 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.ImageReader;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Log;
@@ -47,6 +52,7 @@
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
 import com.android.ex.camera2.utils.StateWaiter;
 
 import java.util.ArrayList;
@@ -90,7 +96,7 @@
             CameraDevice.TEMPLATE_PREVIEW,
             CameraDevice.TEMPLATE_RECORD,
             CameraDevice.TEMPLATE_STILL_CAPTURE,
-            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT
+            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
     };
 
     private static int[] sInvalidTemplates = new int[] {
@@ -783,6 +789,381 @@
         }
     }
 
+    /**
+     * Test session configuration.
+     */
+    public void testSessionConfiguration() throws Exception {
+        ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> ();
+        outConfigs.add(new OutputConfiguration(new Size(1, 1), SurfaceTexture.class));
+        outConfigs.add(new OutputConfiguration(new Size(2, 2), SurfaceTexture.class));
+        mSessionMockListener = spy(new BlockingSessionCallback());
+        InputConfiguration inputConfig = new InputConfiguration(1, 1, ImageFormat.PRIVATE);
+
+        SessionConfiguration regularSessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_REGULAR, outConfigs, mSessionMockListener, null);
+
+        SessionConfiguration highspeedSessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_HIGH_SPEED, outConfigs, mSessionMockListener, null);
+
+        assertEquals("Session configuration output doesn't match",
+                regularSessionConfig.getOutputConfigurations(), outConfigs);
+
+        assertEquals("Session configuration output doesn't match",
+                regularSessionConfig.getOutputConfigurations(),
+                highspeedSessionConfig.getOutputConfigurations());
+
+        assertEquals("Session configuration callback doesn't match",
+                regularSessionConfig.getStateCallback(), mSessionMockListener);
+
+        assertEquals("Session configuration callback doesn't match",
+                regularSessionConfig.getStateCallback(),
+                highspeedSessionConfig.getStateCallback());
+
+        assertEquals("Session configuration handler doesn't match",
+                regularSessionConfig.getHandler(), null);
+
+        assertEquals("Session configuration handler doesn't match",
+                regularSessionConfig.getHandler(), highspeedSessionConfig.getHandler());
+
+        regularSessionConfig.setInputConfiguration(inputConfig);
+        assertEquals("Session configuration input doesn't match",
+                regularSessionConfig.getInputConfiguration(), inputConfig);
+
+        try {
+            highspeedSessionConfig.setInputConfiguration(inputConfig);
+            fail("No exception for valid input configuration in hight speed session configuration");
+        } catch (UnsupportedOperationException e) {
+            //expected
+        }
+
+        assertEquals("Session configuration input doesn't match",
+                highspeedSessionConfig.getInputConfiguration(), null);
+
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+                CaptureRequest.Builder builder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+                CaptureRequest request = builder.build();
+
+                regularSessionConfig.setSessionParameters(request);
+                highspeedSessionConfig.setSessionParameters(request);
+
+                assertEquals("Session configuration parameters doesn't match",
+                        regularSessionConfig.getSessionParameters(), request);
+
+                assertEquals("Session configuration parameters doesn't match",
+                        regularSessionConfig.getSessionParameters(),
+                        highspeedSessionConfig.getSessionParameters());
+            }
+            finally {
+                closeDevice(mCameraIds[i], mCameraMockListener);
+            }
+        }
+    }
+
+    /**
+     * Check for any state leakage in case of internal re-configure
+     */
+    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()) {
+                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                testSessionParametersStateLeakByCamera(mCameraIds[i]);
+            }
+            finally {
+                closeDevice(mCameraIds[i], mCameraMockListener);
+            }
+        }
+    }
+
+    /**
+     * Check for any state leakage in case of internal re-configure
+     */
+    private void testSessionParametersStateLeakByCamera(String cameraId)
+            throws Exception {
+        int outputFormat = ImageFormat.YUV_420_888;
+        Size outputSize = mOrderedPreviewSizes.get(0);
+
+        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
+        StreamConfigurationMap config = characteristics.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        List <CaptureRequest.Key<?>> sessionKeys = characteristics.getAvailableSessionKeys();
+        if (sessionKeys == null) {
+            return;
+        }
+
+        if (config.isOutputSupportedFor(outputFormat)) {
+            outputSize = config.getOutputSizes(outputFormat)[0];
+        } else {
+            return;
+        }
+
+        ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(),
+                outputSize.getHeight(), outputFormat, /*maxImages*/3);
+
+        class OnReadyCaptureStateCallback extends CameraCaptureSession.StateCallback {
+            private ConditionVariable onReadyTriggeredCond = new ConditionVariable();
+            private boolean onReadyTriggered = false;
+
+            @Override
+            public void onConfigured(CameraCaptureSession session) {
+            }
+
+            @Override
+            public void onConfigureFailed(CameraCaptureSession session) {
+            }
+
+            @Override
+            public synchronized void onReady(CameraCaptureSession session) {
+                onReadyTriggered = true;
+                onReadyTriggeredCond.open();
+            }
+
+            public void waitForOnReady(long timeout) {
+                synchronized (this) {
+                    if (onReadyTriggered) {
+                        onReadyTriggered = false;
+                        onReadyTriggeredCond.close();
+                        return;
+                    }
+                }
+
+                if (onReadyTriggeredCond.block(timeout)) {
+                    synchronized (this) {
+                        onReadyTriggered = false;
+                        onReadyTriggeredCond.close();
+                    }
+                } else {
+                    throw new TimeoutRuntimeException("Unable to receive onReady after "
+                        + timeout + "ms");
+                }
+            }
+        }
+
+        OnReadyCaptureStateCallback sessionListener = new OnReadyCaptureStateCallback();
+
+        try {
+            mSessionMockListener = spy(new BlockingSessionCallback(sessionListener));
+            mSessionWaiter = mSessionMockListener.getStateWaiter();
+            List<OutputConfiguration> outputs = new ArrayList<>();
+            outputs.add(new OutputConfiguration(imageReader.getSurface()));
+            SessionConfiguration sessionConfig = new SessionConfiguration(
+                    SessionConfiguration.SESSION_REGULAR, outputs, mSessionMockListener, mHandler);
+
+            CaptureRequest.Builder builder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            builder.addTarget(imageReader.getSurface());
+            CaptureRequest request = builder.build();
+
+            sessionConfig.setSessionParameters(request);
+            mCamera.createCaptureSession(sessionConfig);
+
+            mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+            sessionListener.waitForOnReady(SESSION_CONFIGURE_TIMEOUT_MS);
+
+            SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
+            ImageDropperListener imageListener = new ImageDropperListener();
+            imageReader.setOnImageAvailableListener(imageListener, mHandler);
+
+            // To check the state leak condition, we need a capture request that has
+            // at least one session pararameter value difference from the initial session
+            // parameters configured above. Scan all available template types for the
+            // required delta.
+            CaptureRequest.Builder requestBuilder = null;
+            ArrayList<CaptureRequest.Builder> builders = new ArrayList<CaptureRequest.Builder> ();
+            if (mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                builders.add(mCamera.createCaptureRequest(CameraDevice.TEMPLATE_MANUAL));
+            }
+            if (mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)
+                    || mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING)) {
+                builders.add(mCamera.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG));
+            }
+            builders.add(mCamera.createCaptureRequest(CameraDevice.TEMPLATE_VIDEO_SNAPSHOT));
+            builders.add(mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW));
+            builders.add(mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD));
+            for (CaptureRequest.Key<?> key : sessionKeys) {
+                Object sessionValue = builder.get(key);
+                for (CaptureRequest.Builder newBuilder : builders) {
+                    Object currentValue = newBuilder.get(key);
+                    if ((sessionValue == null) && (currentValue == null)) {
+                        continue;
+                    }
+
+                    if (((sessionValue == null) && (currentValue != null)) ||
+                            ((sessionValue != null) && (currentValue == null)) ||
+                            (!sessionValue.equals(currentValue))) {
+                        requestBuilder = newBuilder;
+                        break;
+                    }
+                }
+
+                if (requestBuilder != null) {
+                    break;
+                }
+            }
+
+            if (requestBuilder != null) {
+                requestBuilder.addTarget(imageReader.getSurface());
+                request = requestBuilder.build();
+                mSession.setRepeatingRequest(request, captureListener, mHandler);
+                try {
+                    sessionListener.waitForOnReady(SESSION_CONFIGURE_TIMEOUT_MS);
+                    fail("Camera shouldn't switch to ready state when session parameters are " +
+                            "modified");
+                } catch (TimeoutRuntimeException e) {
+                    //expected
+                }
+            }
+        } finally {
+            imageReader.close();
+            mSession.close();
+        }
+    }
+
+    /**
+     * Verify creating a session with additional parameters.
+     */
+    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()) {
+                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+
+                testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false);
+                testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true);
+            }
+            finally {
+                closeDevice(mCameraIds[i], mCameraMockListener);
+            }
+        }
+    }
+
+    /**
+     * Verify creating a session with additional parameters works
+     */
+    private void testCreateSessionWithParametersByCamera(String cameraId, boolean reprocessable)
+            throws Exception {
+        final int SESSION_TIMEOUT_MS = 1000;
+        final int CAPTURE_TIMEOUT_MS = 3000;
+        int inputFormat = ImageFormat.YUV_420_888;
+        int outputFormat = inputFormat;
+        Size outputSize = mOrderedPreviewSizes.get(0);
+        Size inputSize = outputSize;
+        InputConfiguration inputConfig = null;
+
+        if (VERBOSE) {
+            Log.v(TAG, "Testing creating session with parameters for camera " + cameraId);
+        }
+
+        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
+        StreamConfigurationMap config = characteristics.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (reprocessable) {
+            //Pick a supported i/o format and size combination.
+            //Ideally the input format should match the output.
+            boolean found = false;
+            int inputFormats [] = config.getInputFormats();
+            if (inputFormats.length == 0) {
+                return;
+            }
+
+            for (int inFormat : inputFormats) {
+                int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat);
+                for (int outFormat : outputFormats) {
+                    if (inFormat == outFormat) {
+                        inputFormat = inFormat;
+                        outputFormat = outFormat;
+                        found = true;
+                        break;
+                    }
+                }
+                if (found) {
+                    break;
+                }
+            }
+
+            //In case the above combination doesn't exist, pick the first first supported
+            //pair.
+            if (!found) {
+                inputFormat = inputFormats[0];
+                int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat);
+                assertTrue("No output formats supported for input format: " + inputFormat,
+                        (outputFormats.length > 0));
+                outputFormat = outputFormats[0];
+            }
+
+            Size inputSizes[] = config.getInputSizes(inputFormat);
+            Size outputSizes[] = config.getOutputSizes(outputFormat);
+            assertTrue("No valid sizes supported for input format: " + inputFormat,
+                    (inputSizes.length > 0));
+            assertTrue("No valid sizes supported for output format: " + outputFormat,
+                    (outputSizes.length > 0));
+
+            inputSize = inputSizes[0];
+            outputSize = outputSizes[0];
+            inputConfig = new InputConfiguration(inputSize.getWidth(),
+                    inputSize.getHeight(), inputFormat);
+        } else {
+            if (config.isOutputSupportedFor(outputFormat)) {
+                outputSize = config.getOutputSizes(outputFormat)[0];
+            } else {
+                return;
+            }
+        }
+
+        ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(),
+                outputSize.getHeight(), outputFormat, /*maxImages*/1);
+
+        try {
+            mSessionMockListener = spy(new BlockingSessionCallback());
+            mSessionWaiter = mSessionMockListener.getStateWaiter();
+            List<OutputConfiguration> outputs = new ArrayList<>();
+            outputs.add(new OutputConfiguration(imageReader.getSurface()));
+            SessionConfiguration sessionConfig = new SessionConfiguration(
+                    SessionConfiguration.SESSION_REGULAR, outputs, mSessionMockListener, mHandler);
+
+            CaptureRequest.Builder builder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            builder.addTarget(imageReader.getSurface());
+            CaptureRequest request = builder.build();
+
+            sessionConfig.setInputConfiguration(inputConfig);
+            sessionConfig.setSessionParameters(request);
+            mCamera.createCaptureSession(sessionConfig);
+
+            mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+
+            // Verify we can capture a frame with the session.
+            SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
+            SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+            imageReader.setOnImageAvailableListener(imageListener, mHandler);
+
+            mSession.capture(request, captureListener, mHandler);
+            captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS);
+            imageListener.getImage(CAPTURE_TIMEOUT_MS).close();
+        } finally {
+            imageReader.close();
+            mSession.close();
+        }
+    }
 
     /**
      * Verify creating sessions back to back and only the last one is valid for
@@ -869,9 +1250,15 @@
         SurfaceTexture output2 = new SurfaceTexture(2);
         Surface output2Surface = new Surface(output2);
 
-        List<Surface> outputSurfaces = new ArrayList<>(
-            Arrays.asList(output1Surface, output2Surface));
-        mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler);
+        ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> ();
+        outConfigs.add(new OutputConfiguration(output1Surface));
+        outConfigs.add(new OutputConfiguration(output2Surface));
+        SessionConfiguration sessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_REGULAR, outConfigs, mSessionMockListener,
+                mHandler);
+        CaptureRequest.Builder r = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        sessionConfig.setSessionParameters(r.build());
+        mCamera.createCaptureSession(sessionConfig);
 
         mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
 
@@ -912,7 +1299,7 @@
 
         // Use output1
 
-        CaptureRequest.Builder r = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        r = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
         r.addTarget(output1Surface);
 
         mSession.capture(r.build(), null, null);
@@ -932,7 +1319,7 @@
 
         mSessionMockListener = spy(new BlockingSessionCallback());
 
-        outputSurfaces = new ArrayList<>(
+        ArrayList<Surface> outputSurfaces = new ArrayList<Surface>(
             Arrays.asList(output1Surface, output3Surface));
         mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler);
 
@@ -1198,6 +1585,7 @@
                                 sTemplates[j] != CameraDevice.TEMPLATE_PREVIEW) {
                             continue;
                         }
+
                         captureSingleShot(mCameraIds[i], sTemplates[j], repeating, abort);
                     }
                 }
@@ -1219,9 +1607,11 @@
                     // Test: burst of 5 shots of the same template type
                     captureBurstShot(mCameraIds[i], templates, templates.length, repeating, abort);
 
-                    // Test: burst of 5 shots of different template types
-                    captureBurstShot(
+                    if (mStaticInfo.isColorOutputSupported()) {
+                        // Test: burst of 6 shots of different template types
+                        captureBurstShot(
                             mCameraIds[i], sTemplates, sTemplates.length, repeating, abort);
+                    }
                 }
                 verify(mCameraMockListener, never())
                         .onError(
@@ -1313,6 +1703,7 @@
                     templates[i] != CameraDevice.TEMPLATE_PREVIEW) {
                 continue;
             }
+
             CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(templates[i]);
             assertNotNull("Failed to create capture request", requestBuilder);
             requestBuilder.addTarget(mReaderSurface);
@@ -1581,12 +1972,6 @@
      */
     private void checkRequestForTemplate(CaptureRequest.Builder request, int template,
             CameraCharacteristics props) {
-        // 3A settings--control.mode.
-        if (template != CameraDevice.TEMPLATE_MANUAL) {
-            mCollector.expectKeyValueEquals(request, CONTROL_MODE,
-                    CaptureRequest.CONTROL_MODE_AUTO);
-        }
-
         // 3A settings--AE/AWB/AF.
         Integer maxRegionsAeVal = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
         int maxRegionsAe = maxRegionsAeVal != null ? maxRegionsAeVal : 0;
@@ -1607,6 +1992,8 @@
             mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE,
                     CaptureRequest.CONTROL_AWB_MODE_OFF);
         } else {
+            mCollector.expectKeyValueEquals(request, CONTROL_MODE,
+                    CaptureRequest.CONTROL_MODE_AUTO);
             if (mStaticInfo.isColorOutputSupported()) {
                 mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE,
                         CaptureRequest.CONTROL_AE_MODE_ON);
@@ -1699,6 +2086,11 @@
             }
         }
 
+        if (mStaticInfo.areKeysAvailable(SENSOR_TEST_PATTERN_MODE)) {
+            mCollector.expectKeyValueEquals(request, SENSOR_TEST_PATTERN_MODE,
+                    CaptureRequest.SENSOR_TEST_PATTERN_MODE_OFF);
+        }
+
         if (mStaticInfo.areKeysAvailable(BLACK_LEVEL_LOCK)) {
             mCollector.expectKeyValueEquals(request, BLACK_LEVEL_LOCK, false);
         }
@@ -1737,9 +2129,10 @@
                 availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING) ||
                 availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
 
+
         if (template == CameraDevice.TEMPLATE_STILL_CAPTURE) {
-            // Not enforce high quality here, as some devices may not effectively have high quality
-            // mode.
+
+            // Ok with either FAST or HIGH_QUALITY
             if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_MODE)) {
                 mCollector.expectKeyValueNotEquals(
                         request, COLOR_CORRECTION_MODE,
@@ -1764,6 +2157,12 @@
                             CaptureRequest.EDGE_MODE_OFF);
                 }
             }
+            if (mStaticInfo.areKeysAvailable(SHADING_MODE)) {
+                List<Integer> availableShadingModes =
+                        Arrays.asList(toObject(mStaticInfo.getAvailableShadingModesChecked()));
+                mCollector.expectKeyValueEquals(request, SHADING_MODE,
+                        CaptureRequest.SHADING_MODE_HIGH_QUALITY);
+            }
 
             mCollector.expectEquals("Noise reduction mode must be present in request if " +
                             "available noise reductions are present in metadata, and vice-versa.",
@@ -1786,6 +2185,27 @@
                 }
             }
 
+            mCollector.expectEquals("Hot pixel mode must be present in request if " +
+                            "available hot pixel modes are present in metadata, and vice-versa.",
+                    mStaticInfo.areKeysAvailable(CameraCharacteristics.
+                            HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES),
+                    mStaticInfo.areKeysAvailable(CaptureRequest.HOT_PIXEL_MODE));
+
+            if (mStaticInfo.areKeysAvailable(HOT_PIXEL_MODE)) {
+                List<Integer> availableHotPixelModes =
+                        Arrays.asList(toObject(
+                                mStaticInfo.getAvailableHotPixelModesChecked()));
+                if (availableHotPixelModes
+                        .contains(CaptureRequest.HOT_PIXEL_MODE_HIGH_QUALITY)) {
+                    mCollector.expectKeyValueEquals(
+                            request, HOT_PIXEL_MODE,
+                            CaptureRequest.HOT_PIXEL_MODE_HIGH_QUALITY);
+                } else {
+                    mCollector.expectKeyValueEquals(
+                            request, HOT_PIXEL_MODE, CaptureRequest.HOT_PIXEL_MODE_OFF);
+                }
+            }
+
             boolean supportAvailableAberrationModes = mStaticInfo.areKeysAvailable(
                     CameraCharacteristics.COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES);
             boolean supportAberrationRequestKey = mStaticInfo.areKeysAvailable(
@@ -1814,7 +2234,15 @@
             mCollector.expectKeyValueEquals(request, NOISE_REDUCTION_MODE,
                     CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG);
         } else if (template == CameraDevice.TEMPLATE_PREVIEW ||
-                template == CameraDevice.TEMPLATE_RECORD){
+                template == CameraDevice.TEMPLATE_RECORD) {
+
+            // Ok with either FAST or HIGH_QUALITY
+            if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_MODE)) {
+                mCollector.expectKeyValueNotEquals(
+                        request, COLOR_CORRECTION_MODE,
+                        CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);
+            }
+
             if (mStaticInfo.areKeysAvailable(EDGE_MODE)) {
                 List<Integer> availableEdgeModes =
                         Arrays.asList(toObject(mStaticInfo.getAvailableEdgeModesChecked()));
@@ -1827,6 +2255,13 @@
                 }
             }
 
+            if (mStaticInfo.areKeysAvailable(SHADING_MODE)) {
+                List<Integer> availableShadingModes =
+                        Arrays.asList(toObject(mStaticInfo.getAvailableShadingModesChecked()));
+                mCollector.expectKeyValueEquals(request, SHADING_MODE,
+                        CaptureRequest.SHADING_MODE_FAST);
+            }
+
             if (mStaticInfo.areKeysAvailable(NOISE_REDUCTION_MODE)) {
                 List<Integer> availableNoiseReductionModes =
                         Arrays.asList(toObject(
@@ -1842,6 +2277,21 @@
                 }
             }
 
+            if (mStaticInfo.areKeysAvailable(HOT_PIXEL_MODE)) {
+                List<Integer> availableHotPixelModes =
+                        Arrays.asList(toObject(
+                                mStaticInfo.getAvailableHotPixelModesChecked()));
+                if (availableHotPixelModes
+                        .contains(CaptureRequest.HOT_PIXEL_MODE_FAST)) {
+                    mCollector.expectKeyValueEquals(
+                            request, HOT_PIXEL_MODE,
+                            CaptureRequest.HOT_PIXEL_MODE_FAST);
+                } else {
+                    mCollector.expectKeyValueEquals(
+                            request, HOT_PIXEL_MODE, CaptureRequest.HOT_PIXEL_MODE_OFF);
+                }
+            }
+
             if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_ABERRATION_MODE)) {
                 List<Integer> availableAberrationModes = Arrays.asList(
                         toObject(mStaticInfo.getAvailableColorAberrationModesChecked()));
@@ -1907,7 +2357,12 @@
                         CaptureRequest.TONEMAP_MODE_PRESET_CURVE);
             }
             if (mStaticInfo.areKeysAvailable(STATISTICS_LENS_SHADING_MAP_MODE)) {
-                mCollector.expectKeyValueNotNull(request, STATISTICS_LENS_SHADING_MAP_MODE);
+                mCollector.expectKeyValueEquals(request, STATISTICS_LENS_SHADING_MAP_MODE,
+                        CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_OFF);
+            }
+            if (mStaticInfo.areKeysAvailable(STATISTICS_HOT_PIXEL_MAP_MODE)) {
+                mCollector.expectKeyValueEquals(request, STATISTICS_HOT_PIXEL_MAP_MODE,
+                        false);
             }
         }
 
@@ -1934,7 +2389,34 @@
                     DEFAULT_POST_RAW_SENSITIVITY_BOOST);
         }
 
-        mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT, template);
+        switch(template) {
+            case CameraDevice.TEMPLATE_PREVIEW:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_PREVIEW);
+                break;
+            case CameraDevice.TEMPLATE_STILL_CAPTURE:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+                break;
+            case CameraDevice.TEMPLATE_RECORD:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
+                break;
+            case CameraDevice.TEMPLATE_VIDEO_SNAPSHOT:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT);
+                break;
+            case CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG);
+                break;
+            case CameraDevice.TEMPLATE_MANUAL:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_MANUAL);
+                break;
+            default:
+                // Skip unknown templates here
+        }
 
         // TODO: use the list of keys from CameraCharacteristics to avoid expecting
         //       keys which are not available by this CameraDevice.
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 11b7b5f..deb9fca 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -22,6 +22,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraMetadata;
@@ -37,10 +38,13 @@
 import android.hardware.camera2.params.RggbChannelVector;
 import android.hardware.camera2.params.TonemapCurve;
 import android.media.Image;
+import android.os.Parcel;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Range;
 import android.util.Rational;
 import android.util.Size;
+import android.view.Surface;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -129,6 +133,84 @@
     }
 
     /**
+     * Test CaptureRequest settings parcelling.
+     */
+    public void testSettingsBinderParcel() throws Exception {
+        SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
+        Surface surface = new Surface(outputTexture);
+
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                openDevice(mCameraIds[i]);
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                requestBuilder.addTarget(surface);
+
+                // Check regular/default case
+                CaptureRequest captureRequestOriginal = requestBuilder.build();
+                Parcel p;
+                p = Parcel.obtain();
+                captureRequestOriginal.writeToParcel(p, 0);
+                p.setDataPosition(0);
+                CaptureRequest captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                assertEquals("Parcelled camera settings should match",
+                        captureRequestParcelled.get(CaptureRequest.CONTROL_CAPTURE_INTENT),
+                        new Integer(CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW));
+                p.recycle();
+
+                // Check capture request with additional physical camera settings
+                String physicalId = new String(Integer.toString(i + 1));
+                ArraySet<String> physicalIds = new ArraySet<String> ();
+                physicalIds.add(physicalId);
+
+                requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW,
+                        physicalIds);
+                requestBuilder.addTarget(surface);
+                captureRequestOriginal = requestBuilder.build();
+                captureRequestOriginal.writeToParcel(p, 0);
+                p.setDataPosition(0);
+                captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                assertEquals("Parcelled camera settings should match",
+                        captureRequestParcelled.get(CaptureRequest.CONTROL_CAPTURE_INTENT),
+                        new Integer(CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW));
+                p.recycle();
+
+                // Check various invalid cases
+                p.writeInt(-1);
+                p.setDataPosition(0);
+                try {
+                    captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                    fail("should get RuntimeException due to invalid number of settings");
+                } catch (RuntimeException e) {
+                    // Expected
+                }
+                p.recycle();
+
+                p.writeInt(0);
+                p.setDataPosition(0);
+                try {
+                    captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                    fail("should get RuntimeException due to invalid number of settings");
+                } catch (RuntimeException e) {
+                    // Expected
+                }
+                p.recycle();
+
+                p.writeInt(1);
+                p.setDataPosition(0);
+                try {
+                    captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                    fail("should get RuntimeException due to absent settings");
+                } catch (RuntimeException e) {
+                    // Expected
+                }
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
      * Test black level lock when exposure value change.
      * <p>
      * When {@link CaptureRequest#BLACK_LEVEL_LOCK} is true in a request, the
@@ -1506,6 +1588,7 @@
             case CONTROL_AE_MODE_ON_AUTO_FLASH:
             case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
             case CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+            case CONTROL_AE_MODE_ON_EXTERNAL_FLASH:
                 // Test AE lock for above AUTO modes.
                 aeAutoModeTestLock(mode);
                 break;
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index b29fd57..d653f25 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -29,6 +29,7 @@
 import android.os.SystemClock;
 import android.util.Pair;
 import android.util.Size;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 
@@ -43,6 +44,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -54,14 +56,11 @@
     private static final int NUM_FRAMES_VERIFIED = 30;
     private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
 
-    // List that includes all public keys from CaptureResult
-    List<CaptureResult.Key<?>> mAllKeys;
 
     // List tracking the failed test keys.
 
     @Override
     public void setContext(Context context) {
-        mAllKeys = getAllCaptureResultKeys();
         super.setContext(context);
 
         /**
@@ -123,12 +122,9 @@
                 SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
                 startCapture(requestBuilder.build(), /*repeating*/true, captureListener, mHandler);
 
-                // Get the waived keys for current camera device
-                List<CaptureResult.Key<?>> waiverkeys = getWaiverKeysForCamera();
-
                 // Verify results
-                validateCaptureResult(captureListener, waiverkeys, requestBuilder,
-                        NUM_FRAMES_VERIFIED);
+                validateCaptureResult(mCollector, captureListener, mStaticInfo, mAllStaticInfo,
+                        null/*requestedPhysicalIds*/, requestBuilder, NUM_FRAMES_VERIFIED);
 
                 stopCapture(/*fast*/false);
             } finally {
@@ -379,72 +375,111 @@
                 resultImage.getTimestamp(), captureTime);
     }
 
-    private void validateCaptureResult(SimpleCaptureCallback captureListener,
-            List<CaptureResult.Key<?>> skippedKeys, CaptureRequest.Builder requestBuilder,
-            int numFramesVerified) throws Exception {
-        CaptureResult result = null;
+    public static void validateCaptureResult(CameraErrorCollector errorCollector,
+            SimpleCaptureCallback captureListener, StaticMetadata staticInfo,
+            Map<String, StaticMetadata> allStaticInfo, List<String> requestedPhysicalIds,
+            CaptureRequest.Builder requestBuilder, int numFramesVerified) throws Exception {
+        // List that includes all public keys from CaptureResult
+        List<CaptureResult.Key<?>> allKeys = getAllCaptureResultKeys();
+        // Get the waived keys for current camera device
+        List<CaptureResult.Key<?>> waiverKeys = getWaiverKeysForCamera(staticInfo);
+        if (requestedPhysicalIds == null) {
+            requestedPhysicalIds = new ArrayList<String>();
+        }
+
+        HashMap<String, List<CaptureResult.Key<?>>> physicalWaiverKeys = new HashMap<>();
+        for (String physicalId : requestedPhysicalIds) {
+            StaticMetadata physicalStaticInfo = allStaticInfo.get(physicalId);
+            physicalWaiverKeys.put(physicalId, getWaiverKeysForCamera(physicalStaticInfo));
+        }
+
+        TotalCaptureResult result = null;
         for (int i = 0; i < numFramesVerified; i++) {
-            String failMsg = "Failed capture result " + i + " test ";
-            result = captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+            result = captureListener.getTotalCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+            Map<String, CaptureResult> physicalCaptureResults = result.getPhysicalCameraResults();
+            errorCollector.expectEquals("Number of physical result metadata doesn't match " +
+                    physicalCaptureResults.size() + " vs " + requestedPhysicalIds.size(),
+                    physicalCaptureResults.size(), requestedPhysicalIds.size());
 
-            for (CaptureResult.Key<?> key : mAllKeys) {
-                if (!skippedKeys.contains(key)) {
-                    /**
-                     * Check the critical tags here.
-                     * TODO: Can use the same key for request and result when request/result
-                     * becomes symmetric (b/14059883). Then below check can be wrapped into
-                     * a generic function.
-                     */
-                    String msg = failMsg + "for key " + key.getName();
-                    if (key.equals(CaptureResult.CONTROL_AE_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.CONTROL_AE_MODE),
-                                result.get(CaptureResult.CONTROL_AE_MODE));
-                    } else if (key.equals(CaptureResult.CONTROL_AF_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.CONTROL_AF_MODE),
-                                result.get(CaptureResult.CONTROL_AF_MODE));
-                    } else if (key.equals(CaptureResult.CONTROL_AWB_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.CONTROL_AWB_MODE),
-                                result.get(CaptureResult.CONTROL_AWB_MODE));
-                    } else if (key.equals(CaptureResult.CONTROL_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.CONTROL_MODE),
-                                result.get(CaptureResult.CONTROL_MODE));
-                    } else if (key.equals(CaptureResult.STATISTICS_FACE_DETECT_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.STATISTICS_FACE_DETECT_MODE),
-                                result.get(CaptureResult.STATISTICS_FACE_DETECT_MODE));
-                    } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
-                                result.get(CaptureResult.NOISE_REDUCTION_MODE));
-                    } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
-                        mCollector.expectEquals(msg,
-                                requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
-                                result.get(CaptureResult.NOISE_REDUCTION_MODE));
-                    } else if (key.equals(CaptureResult.REQUEST_PIPELINE_DEPTH)) {
+            validateOneCaptureResult(errorCollector, waiverKeys, allKeys, requestBuilder, result,
+                    null/*cameraId*/, i);
+            for (String physicalId : requestedPhysicalIds) {
+                validateOneCaptureResult(errorCollector, physicalWaiverKeys.get(physicalId),
+                        allKeys, requestBuilder, physicalCaptureResults.get(physicalId),
+                        physicalId, i);
+            }
+        }
+    }
 
-                    } else {
-                        // Only do non-null check for the rest of keys.
-                        mCollector.expectKeyValueNotNull(failMsg, result, key);
-                    }
+    private static void validateOneCaptureResult(CameraErrorCollector errorCollector,
+            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 ";
+        if (cameraId != null) {
+            failMsg += "for camera " + cameraId + " ";
+        }
+        for (CaptureResult.Key<?> key : allKeys) {
+            if (!skippedKeys.contains(key)) {
+                /**
+                 * Check the critical tags here.
+                 * TODO: Can use the same key for request and result when request/result
+                 * becomes symmetric (b/14059883). Then below check can be wrapped into
+                 * a generic function.
+                 */
+                String msg = failMsg + "for key " + key.getName();
+                if (key.equals(CaptureResult.CONTROL_AE_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.CONTROL_AE_MODE),
+                            result.get(CaptureResult.CONTROL_AE_MODE));
+                } else if (key.equals(CaptureResult.CONTROL_AF_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.CONTROL_AF_MODE),
+                            result.get(CaptureResult.CONTROL_AF_MODE));
+                } else if (key.equals(CaptureResult.CONTROL_AWB_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.CONTROL_AWB_MODE),
+                            result.get(CaptureResult.CONTROL_AWB_MODE));
+                } else if (key.equals(CaptureResult.CONTROL_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.CONTROL_MODE),
+                            result.get(CaptureResult.CONTROL_MODE));
+                } else if (key.equals(CaptureResult.STATISTICS_FACE_DETECT_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.STATISTICS_FACE_DETECT_MODE),
+                            result.get(CaptureResult.STATISTICS_FACE_DETECT_MODE));
+                } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
+                            result.get(CaptureResult.NOISE_REDUCTION_MODE));
+                } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
+                            result.get(CaptureResult.NOISE_REDUCTION_MODE));
+                } else if (key.equals(CaptureResult.REQUEST_PIPELINE_DEPTH)) {
+
+                } else if (key.equals(CaptureResult.STATISTICS_OIS_DATA_MODE)) {
+                    errorCollector.expectEquals(msg,
+                            requestBuilder.get(CaptureRequest.STATISTICS_OIS_DATA_MODE),
+                            result.get(CaptureResult.STATISTICS_OIS_DATA_MODE));
                 } else {
-                    // These keys should always be null
-                    if (key.equals(CaptureResult.CONTROL_AE_REGIONS)) {
-                        mCollector.expectNull(
-                                "Capture result contains AE regions but aeMaxRegions is 0",
-                                result.get(CaptureResult.CONTROL_AE_REGIONS));
-                    } else if (key.equals(CaptureResult.CONTROL_AWB_REGIONS)) {
-                        mCollector.expectNull(
-                                "Capture result contains AWB regions but awbMaxRegions is 0",
-                                result.get(CaptureResult.CONTROL_AWB_REGIONS));
-                    } else if (key.equals(CaptureResult.CONTROL_AF_REGIONS)) {
-                        mCollector.expectNull(
-                                "Capture result contains AF regions but afMaxRegions is 0",
-                                result.get(CaptureResult.CONTROL_AF_REGIONS));
-                    }
+                    // Only do non-null check for the rest of keys.
+                    errorCollector.expectKeyValueNotNull(failMsg, result, key);
+                }
+            } else {
+                // These keys should always be null
+                if (key.equals(CaptureResult.CONTROL_AE_REGIONS)) {
+                    errorCollector.expectNull(
+                            "Capture result contains AE regions but aeMaxRegions is 0",
+                            result.get(CaptureResult.CONTROL_AE_REGIONS));
+                } else if (key.equals(CaptureResult.CONTROL_AWB_REGIONS)) {
+                    errorCollector.expectNull(
+                            "Capture result contains AWB regions but awbMaxRegions is 0",
+                            result.get(CaptureResult.CONTROL_AWB_REGIONS));
+                } else if (key.equals(CaptureResult.CONTROL_AF_REGIONS)) {
+                    errorCollector.expectNull(
+                            "Capture result contains AF regions but afMaxRegions is 0",
+                            result.get(CaptureResult.CONTROL_AF_REGIONS));
                 }
             }
         }
@@ -455,7 +490,7 @@
      *
      * Must be called after camera device is opened.
      */
-    private List<CaptureResult.Key<?>> getWaiverKeysForCamera() {
+    private static List<CaptureResult.Key<?>> getWaiverKeysForCamera(StaticMetadata staticInfo) {
         List<CaptureResult.Key<?>> waiverKeys = new ArrayList<>();
 
         // Global waiver keys
@@ -484,7 +519,7 @@
         waiverKeys.add(CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR);
 
         //Keys not required if RAW is not supported
-        if (!mStaticInfo.isCapabilitySupported(
+        if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
             waiverKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
             waiverKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
@@ -492,7 +527,7 @@
         }
 
         //Keys for depth output capability
-        if (!mStaticInfo.isCapabilitySupported(
+        if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT)) {
             waiverKeys.add(CaptureResult.LENS_POSE_ROTATION);
             waiverKeys.add(CaptureResult.LENS_POSE_TRANSLATION);
@@ -501,7 +536,7 @@
         }
 
         // Waived if RAW output is not supported
-        int[] outputFormats = mStaticInfo.getAvailableFormats(
+        int[] outputFormats = staticInfo.getAvailableFormats(
                 StaticMetadata.StreamDirection.Output);
         boolean supportRaw = false;
         for (int format : outputFormats) {
@@ -515,27 +550,36 @@
             waiverKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
         }
 
-        if (mStaticInfo.getAeMaxRegionsChecked() == 0) {
+        if (staticInfo.getAeMaxRegionsChecked() == 0) {
             waiverKeys.add(CaptureResult.CONTROL_AE_REGIONS);
         }
-        if (mStaticInfo.getAwbMaxRegionsChecked() == 0) {
+        if (staticInfo.getAwbMaxRegionsChecked() == 0) {
             waiverKeys.add(CaptureResult.CONTROL_AWB_REGIONS);
         }
-        if (mStaticInfo.getAfMaxRegionsChecked() == 0) {
+        if (staticInfo.getAfMaxRegionsChecked() == 0) {
             waiverKeys.add(CaptureResult.CONTROL_AF_REGIONS);
         }
 
         // Keys for dynamic black/white levels
-        if (!mStaticInfo.isOpticalBlackRegionSupported()) {
+        if (!staticInfo.isOpticalBlackRegionSupported()) {
             waiverKeys.add(CaptureResult.SENSOR_DYNAMIC_BLACK_LEVEL);
             waiverKeys.add(CaptureResult.SENSOR_DYNAMIC_WHITE_LEVEL);
         }
 
-        if (!mStaticInfo.isEnableZslSupported()) {
+        if (!staticInfo.isEnableZslSupported()) {
             waiverKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
         }
 
-        if (mStaticInfo.isHardwareLevelAtLeastFull()) {
+        if (!staticInfo.isAfSceneChangeSupported()) {
+            waiverKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+        }
+
+        if (!staticInfo.isOisDataModeSupported()) {
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_DATA_MODE);
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_SAMPLES);
+        }
+
+        if (staticInfo.isHardwareLevelAtLeastFull()) {
             return waiverKeys;
         }
 
@@ -543,40 +587,40 @@
          * Hardware Level = LIMITED or LEGACY
          */
         // Key not present if certain control is not supported
-        if (!mStaticInfo.isColorCorrectionSupported()) {
+        if (!staticInfo.isColorCorrectionSupported()) {
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_MODE);
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
         }
 
-        if (!mStaticInfo.isManualColorAberrationControlSupported()) {
+        if (!staticInfo.isManualColorAberrationControlSupported()) {
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_ABERRATION_MODE);
         }
 
-        if (!mStaticInfo.isManualToneMapSupported()) {
+        if (!staticInfo.isManualToneMapSupported()) {
             waiverKeys.add(CaptureResult.TONEMAP_MODE);
         }
 
-        if (!mStaticInfo.isEdgeModeControlSupported()) {
+        if (!staticInfo.isEdgeModeControlSupported()) {
             waiverKeys.add(CaptureResult.EDGE_MODE);
         }
 
-        if (!mStaticInfo.isHotPixelMapModeControlSupported()) {
+        if (!staticInfo.isHotPixelMapModeControlSupported()) {
             waiverKeys.add(CaptureResult.HOT_PIXEL_MODE);
         }
 
-        if (!mStaticInfo.isNoiseReductionModeControlSupported()) {
+        if (!staticInfo.isNoiseReductionModeControlSupported()) {
             waiverKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
         }
 
-        if (!mStaticInfo.isManualLensShadingMapSupported()) {
+        if (!staticInfo.isManualLensShadingMapSupported()) {
             waiverKeys.add(CaptureResult.SHADING_MODE);
         }
 
         //Keys not required if neither MANUAL_SENSOR nor READ_SENSOR_SETTINGS is supported
-        if (!mStaticInfo.isCapabilitySupported(
+        if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) &&
-            !mStaticInfo.isCapabilitySupported(
+            !staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS)) {
             waiverKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
             waiverKeys.add(CaptureResult.SENSOR_SENSITIVITY);
@@ -584,7 +628,7 @@
             waiverKeys.add(CaptureResult.LENS_APERTURE);
         }
 
-        if (!mStaticInfo.isCapabilitySupported(
+        if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
             waiverKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
             waiverKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
@@ -593,7 +637,7 @@
             waiverKeys.add(CaptureResult.LENS_FILTER_DENSITY);
         }
 
-        if (mStaticInfo.isHardwareLevelLimited() && mStaticInfo.isColorOutputSupported()) {
+        if (staticInfo.isHardwareLevelLimited() && staticInfo.isColorOutputSupported()) {
             return waiverKeys;
         }
 
@@ -612,7 +656,7 @@
         waiverKeys.add(CaptureResult.CONTROL_AE_TARGET_FPS_RANGE);
         waiverKeys.add(CaptureResult.CONTROL_AF_TRIGGER);
 
-        if (mStaticInfo.isHardwareLevelLegacy()) {
+        if (staticInfo.isHardwareLevelLegacy()) {
             return waiverKeys;
         }
 
@@ -770,6 +814,7 @@
         resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
         resultKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
         resultKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
+        resultKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
         resultKeys.add(CaptureResult.EDGE_MODE);
         resultKeys.add(CaptureResult.FLASH_MODE);
         resultKeys.add(CaptureResult.FLASH_STATE);
@@ -813,6 +858,8 @@
         resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
         resultKeys.add(CaptureResult.STATISTICS_HOT_PIXEL_MAP);
         resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP_MODE);
+        resultKeys.add(CaptureResult.STATISTICS_OIS_DATA_MODE);
+        resultKeys.add(CaptureResult.STATISTICS_OIS_SAMPLES);
         resultKeys.add(CaptureResult.TONEMAP_CURVE);
         resultKeys.add(CaptureResult.TONEMAP_MODE);
         resultKeys.add(CaptureResult.TONEMAP_GAMMA);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 000edcd..0514987 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -38,6 +38,7 @@
 import android.util.Size;
 import android.util.Patterns;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -73,6 +74,7 @@
     private static final Size VGA = new Size(640, 480);
     private static final Size QVGA = new Size(320, 240);
 
+    private static final long FRAME_DURATION_30FPS_NSEC = (long) 1e9 / 30;
     /*
      * HW Levels short hand
      */
@@ -334,6 +336,7 @@
                 expectKeyAvailable(c, CameraCharacteristics.FLASH_INFO_AVAILABLE                            , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES             , OPT      ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL                   , OPT      ,   BC                   );
+                expectKeyAvailable(c, CameraCharacteristics.INFO_VERSION                                    , OPT      ,   NONE                 );
                 expectKeyAvailable(c, CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES                  , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.LENS_FACING                                     , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES                   , FULL     ,   MANUAL_SENSOR        );
@@ -406,6 +409,19 @@
                 expectKeyAvailable(c,
                         CameraCharacteristics.CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE, OPT, BC);
             }
+
+            // Verify version is a short text string.
+            if (allKeys.contains(CameraCharacteristics.INFO_VERSION)) {
+                final String TEXT_REGEX = "[\\p{Alnum}\\p{Punct}\\p{Space}]*";
+                final int MAX_VERSION_LENGTH = 256;
+
+                String version = c.get(CameraCharacteristics.INFO_VERSION);
+                mCollector.expectTrue("Version contains non-text characters: " + version,
+                        version.matches(TEXT_REGEX));
+                mCollector.expectLessOrEqual("Version too long: " + version, MAX_VERSION_LENGTH,
+                        version.length());
+            }
+
             counter++;
         }
     }
@@ -497,6 +513,25 @@
     }
 
     /**
+     * Test values for the available session keys.
+     */
+    public void testStaticSessionKeys() throws Exception {
+        for (CameraCharacteristics c : mCharacteristics) {
+            List<CaptureRequest.Key<?>> availableSessionKeys = c.getAvailableSessionKeys();
+            if (availableSessionKeys == null) {
+                continue;
+            }
+            List<CaptureRequest.Key<?>> availableRequestKeys = c.getAvailableCaptureRequestKeys();
+
+            //Every session key should be part of the available request keys
+            for (CaptureRequest.Key<?> key : availableSessionKeys) {
+                assertTrue("Session key:" + key.getName() + " not present in the available capture "
+                        + "request keys!", availableRequestKeys.contains(key));
+            }
+        }
+    }
+
+    /**
      * Test values for static metadata used by the BURST capability.
      */
     public void testStaticBurstCharacteristics() throws Exception {
@@ -812,8 +847,11 @@
 
             float[] poseRotation = c.get(CameraCharacteristics.LENS_POSE_ROTATION);
             float[] poseTranslation = c.get(CameraCharacteristics.LENS_POSE_TRANSLATION);
+            Integer poseReference = c.get(CameraCharacteristics.LENS_POSE_REFERENCE);
             float[] cameraIntrinsics = c.get(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION);
             float[] radialDistortion = c.get(CameraCharacteristics.LENS_RADIAL_DISTORTION);
+            Rect precorrectionArray = c.get(
+                CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
 
             if (supportDepth) {
                 mCollector.expectTrue("Supports DEPTH_OUTPUT but does not support DEPTH16",
@@ -867,64 +905,8 @@
                 mCollector.expectTrue("Supports DEPTH_OUTPUT but DEPTH_IS_EXCLUSIVE is not defined",
                         depthIsExclusive != null);
 
-                mCollector.expectTrue(
-                        "Supports DEPTH_OUTPUT but LENS_POSE_ROTATION not right size",
-                        poseRotation != null && poseRotation.length == 4);
-                mCollector.expectTrue(
-                        "Supports DEPTH_OUTPUT but LENS_POSE_TRANSLATION not right size",
-                        poseTranslation != null && poseTranslation.length == 3);
-                mCollector.expectTrue(
-                        "Supports DEPTH_OUTPUT but LENS_INTRINSIC_CALIBRATION not right size",
-                        cameraIntrinsics != null && cameraIntrinsics.length == 5);
-                mCollector.expectTrue(
-                        "Supports DEPTH_OUTPUT but LENS_RADIAL_DISTORTION not right size",
-                        radialDistortion != null && radialDistortion.length == 6);
-
-                if (poseRotation != null && poseRotation.length == 4) {
-                    float normSq =
-                        poseRotation[0] * poseRotation[0] +
-                        poseRotation[1] * poseRotation[1] +
-                        poseRotation[2] * poseRotation[2] +
-                        poseRotation[3] * poseRotation[3];
-                    mCollector.expectTrue(
-                            "LENS_POSE_ROTATION quarternion must be unit-length",
-                            0.9999f < normSq && normSq < 1.0001f);
-
-                    // TODO: Cross-validate orientation/facing and poseRotation
-                    Integer orientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
-                    Integer facing = c.get(CameraCharacteristics.LENS_FACING);
-                }
-
-                if (poseTranslation != null && poseTranslation.length == 3) {
-                    float normSq =
-                        poseTranslation[0] * poseTranslation[0] +
-                        poseTranslation[1] * poseTranslation[1] +
-                        poseTranslation[2] * poseTranslation[2];
-                    mCollector.expectTrue("Pose translation is larger than 1 m",
-                            normSq < 1.f);
-                }
-
-                Rect precorrectionArray =
-                    c.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
-                mCollector.expectTrue("Supports DEPTH_OUTPUT but does not have " +
-                        "precorrection active array defined", precorrectionArray != null);
-
-                if (cameraIntrinsics != null && precorrectionArray != null) {
-                    float fx = cameraIntrinsics[0];
-                    float fy = cameraIntrinsics[1];
-                    float cx = cameraIntrinsics[2];
-                    float cy = cameraIntrinsics[3];
-                    float s = cameraIntrinsics[4];
-                    mCollector.expectTrue("Optical center expected to be within precorrection array",
-                            0 <= cx && cx < precorrectionArray.width() &&
-                            0 <= cy && cy < precorrectionArray.height());
-
-                    // TODO: Verify focal lengths and skew are reasonable
-                }
-
-                if (radialDistortion != null) {
-                    // TODO: Verify radial distortion
-                }
+                verifyLensCalibration(poseRotation, poseTranslation, poseReference,
+                        cameraIntrinsics, radialDistortion, precorrectionArray);
 
             } else {
                 boolean hasFields =
@@ -940,6 +922,84 @@
         }
     }
 
+    private void verifyLensCalibration(float[] poseRotation, float[] poseTranslation,
+            Integer poseReference, float[] cameraIntrinsics, float[] radialDistortion,
+            Rect precorrectionArray) {
+
+        mCollector.expectTrue(
+            "LENS_POSE_ROTATION not right size",
+            poseRotation != null && poseRotation.length == 4);
+        mCollector.expectTrue(
+            "LENS_POSE_TRANSLATION not right size",
+            poseTranslation != null && poseTranslation.length == 3);
+        mCollector.expectTrue(
+            "LENS_POSE_REFERENCE is not defined",
+            poseReference != null);
+        mCollector.expectTrue(
+            "LENS_INTRINSIC_CALIBRATION not right size",
+            cameraIntrinsics != null && cameraIntrinsics.length == 5);
+        mCollector.expectTrue(
+            "LENS_RADIAL_DISTORTION not right size",
+            radialDistortion != null && radialDistortion.length == 6);
+
+        if (poseRotation != null && poseRotation.length == 4) {
+            float normSq =
+                    poseRotation[0] * poseRotation[0] +
+                    poseRotation[1] * poseRotation[1] +
+                    poseRotation[2] * poseRotation[2] +
+                    poseRotation[3] * poseRotation[3];
+            mCollector.expectTrue(
+                "LENS_POSE_ROTATION quarternion must be unit-length",
+                0.9999f < normSq && normSq < 1.0001f);
+
+            // TODO: Cross-validate orientation/facing and poseRotation
+        }
+
+        if (poseTranslation != null && poseTranslation.length == 3) {
+            float normSq =
+                    poseTranslation[0] * poseTranslation[0] +
+                    poseTranslation[1] * poseTranslation[1] +
+                    poseTranslation[2] * poseTranslation[2];
+            mCollector.expectTrue("Pose translation is larger than 1 m",
+                    normSq < 1.f);
+        }
+
+        if (poseReference != null) {
+            int ref = poseReference;
+            boolean validReference = false;
+            switch (ref) {
+                case CameraCharacteristics.LENS_POSE_REFERENCE_PRIMARY_CAMERA:
+                case CameraCharacteristics.LENS_POSE_REFERENCE_GYROSCOPE:
+                    // Allowed values
+                    validReference = true;
+                    break;
+                default:
+            }
+            mCollector.expectTrue("POSE_REFERENCE has unknown value", validReference);
+        }
+
+        mCollector.expectTrue("Does not have precorrection active array defined",
+                precorrectionArray != null);
+
+        if (cameraIntrinsics != null && precorrectionArray != null) {
+            float fx = cameraIntrinsics[0];
+            float fy = cameraIntrinsics[1];
+            float cx = cameraIntrinsics[2];
+            float cy = cameraIntrinsics[3];
+            float s = cameraIntrinsics[4];
+            mCollector.expectTrue("Optical center expected to be within precorrection array",
+                    0 <= cx && cx < precorrectionArray.width() &&
+                    0 <= cy && cy < precorrectionArray.height());
+
+            // TODO: Verify focal lengths and skew are reasonable
+        }
+
+        if (radialDistortion != null) {
+            // TODO: Verify radial distortion
+        }
+
+    }
+
     /**
      * Cross-check StreamConfigurationMap output
      */
@@ -1303,6 +1363,69 @@
             counter++;
         }
     }
+
+    /**
+     * Check Logical camera capability
+     */
+    public void testLogicalCameraCharacteristics() throws Exception {
+        int counter = 0;
+        List<String> cameraIdList = Arrays.asList(mIds);
+
+        for (CameraCharacteristics c : mCharacteristics) {
+            int[] capabilities = CameraTestUtils.getValueNotNull(
+                    c, CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            boolean supportLogicalCamera = arrayContains(capabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA);
+            if (supportLogicalCamera) {
+                List<String> physicalCameraIds = c.getPhysicalCameraIds();
+                assertNotNull("android.logicalCam.physicalCameraIds shouldn't be null",
+                    physicalCameraIds);
+                assertTrue("Logical camera must contain at least 2 physical camera ids",
+                    physicalCameraIds.size() >= 2);
+
+                mCollector.expectKeyValueInRange(c,
+                        CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE,
+                        CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE,
+                        CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED);
+
+                Integer timestampSource = c.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+                for (String physicalCameraId : physicalCameraIds) {
+                    assertNotNull("Physical camera id shouldn't be null", physicalCameraId);
+                    assertTrue(
+                            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 =
+                            mCameraManager.getCameraCharacteristics(physicalCameraId);
+
+                    float[] poseRotation = pc.get(CameraCharacteristics.LENS_POSE_ROTATION);
+                    float[] poseTranslation = pc.get(CameraCharacteristics.LENS_POSE_TRANSLATION);
+                    Integer poseReference = pc.get(CameraCharacteristics.LENS_POSE_REFERENCE);
+                    float[] cameraIntrinsics = pc.get(
+                            CameraCharacteristics.LENS_INTRINSIC_CALIBRATION);
+                    float[] radialDistortion = pc.get(CameraCharacteristics.LENS_RADIAL_DISTORTION);
+                    Rect precorrectionArray = pc.get(
+                            CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+
+                    verifyLensCalibration(poseRotation, poseTranslation, poseReference,
+                            cameraIntrinsics, radialDistortion, precorrectionArray);
+
+                    Integer timestampSourcePhysical =
+                            pc.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+                    mCollector.expectEquals("Logical camera and physical cameras must have same " +
+                            "timestamp source", timestampSource, timestampSourcePhysical);
+                }
+            }
+            counter++;
+        }
+    }
+
     /**
      * Create an invalid size that's close to one of the good sizes in the list, but not one of them
      */
diff --git a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
index c4ae61b..488c0f3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
@@ -48,7 +48,6 @@
  * May not take more than a few seconds to run, to be suitable for quick
  * testing.
  */
-@Presubmit
 public class FastBasicsTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "FastBasicsTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -58,6 +57,7 @@
     private static final int WAIT_FOR_PICTURE_TIMEOUT_MS = 5000;
     private static final int FRAMES_TO_WAIT_FOR_CAPTURE = 100;
 
+    @Presubmit
     public void testCamera2() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
@@ -178,6 +178,7 @@
         }
     }
 
+    @Presubmit
     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/IdleUidTest.java b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
new file mode 100644
index 0000000..dd7053b
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.hardware.camera2.cts;
+
+import static org.junit.Assert.fail;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.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 org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+
+/**
+ * Test for validating behaviors related to idle UIDs. Idle UIDs cannot
+ * access camera. If the UID has a camera handle and becomes idle it would
+ * get an error callback losing the camera handle. Similarly if the UID is
+ * already idle it cannot obtain a camera handle.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class IdleUidTest {
+    private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec
+
+    private static final HandlerThread sCallbackThread = new HandlerThread("Callback thread");
+
+    @BeforeClass
+    public static void startHandlerThread() {
+        sCallbackThread.start();
+    }
+
+    @AfterClass
+    public static void stopHandlerThread() {
+        sCallbackThread.quit();
+    }
+
+    /**
+     * Tests that a UID has access to the camera only in active state.
+     */
+    @Test
+    public void testCameraAccessForIdleUid() throws Exception {
+        final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(CameraManager.class);
+        for (String cameraId : cameraManager.getCameraIdList()) {
+            testCameraAccessForIdleUidByCamera(cameraManager, cameraId,
+                    new Handler(sCallbackThread.getLooper()));
+        }
+    }
+
+    /**
+     * Tests that a UID loses access to the camera if it becomes inactive.
+     */
+    @Test
+    public void testCameraAccessBecomingInactiveUid() throws Exception {
+        final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(CameraManager.class);
+        for (String cameraId : cameraManager.getCameraIdList()) {
+            testCameraAccessBecomingInactiveUidByCamera(cameraManager, cameraId,
+                    new Handler(sCallbackThread.getLooper()));
+        }
+
+    }
+
+    private void testCameraAccessForIdleUidByCamera(CameraManager cameraManager,
+            String cameraId, Handler handler) throws Exception {
+        // Can access camera from an active UID.
+        assertCameraAccess(cameraManager, cameraId, true, handler);
+
+        // Make our UID idle
+        makeMyPackageIdle();
+        try {
+            // Can not access camera from an idle UID.
+            assertCameraAccess(cameraManager, cameraId, false, handler);
+        } finally {
+            // Restore our UID as active
+            makeMyPackageActive();
+        }
+
+        // Can access camera from an active UID.
+        assertCameraAccess(cameraManager, cameraId, true, handler);
+    }
+
+    private static void assertCameraAccess(CameraManager cameraManager,
+            String cameraId, boolean hasAccess, Handler handler) {
+        // Mock the callback used to observe camera state.
+        final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
+
+        // Open the camera
+        try {
+            cameraManager.openCamera(cameraId, callback, handler);
+        } catch (CameraAccessException e) {
+            if (hasAccess) {
+                fail("Unexpected exception" + e);
+            } else {
+                assertThat(e.getReason()).isSameAs(CameraAccessException.CAMERA_DISABLED);
+            }
+        }
+
+        // Verify access
+        final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class);
+        try {
+            if (hasAccess) {
+                // The camera should open fine as we are in the foreground
+                verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS)
+                        .times(1)).onOpened(captor.capture());
+                verifyNoMoreInteractions(callback);
+            } else {
+                // The camera should not open as we are in the background
+                verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS)
+                        .times(1)).onError(captor.capture(),
+                        eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED));
+                verifyNoMoreInteractions(callback);
+            }
+        } finally {
+            final CameraDevice cameraDevice = captor.getValue();
+            assertThat(cameraDevice).isNotNull();
+            cameraDevice.close();
+        }
+    }
+
+    private void testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager,
+            String cameraId, Handler handler) throws Exception {
+        // Mock the callback used to observe camera state.
+        final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
+
+        // Open the camera
+        try {
+            cameraManager.openCamera(cameraId, callback, handler);
+        } catch (CameraAccessException e) {
+            fail("Unexpected exception" + e);
+        }
+
+        // Verify access
+        final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class);
+        try {
+            // The camera should open fine as we are in the foreground
+            verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS)
+                    .times(1)).onOpened(captor.capture());
+            verifyNoMoreInteractions(callback);
+
+            // Ready for a new verification
+            reset(callback);
+
+            // Now we are moving in the background
+            makeMyPackageIdle();
+
+            // The camera should be closed if the UID became idle
+            verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS)
+                    .times(1)).onError(captor.capture(),
+                    eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED));
+            verifyNoMoreInteractions(callback);
+        } finally {
+            // Restore to active state
+            makeMyPackageActive();
+
+            final CameraDevice cameraDevice = captor.getValue();
+            assertThat(cameraDevice).isNotNull();
+            cameraDevice.close();
+        }
+    }
+
+    private static void makeMyPackageActive() throws IOException {
+        final String command = "cmd media.camera reset-uid-state "
+                +  InstrumentationRegistry.getTargetContext().getPackageName();
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static void makeMyPackageIdle() throws IOException {
+        final String command = "cmd media.camera set-uid-state "
+                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle";
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index bd01d21..6aa76a8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -25,6 +25,7 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.hardware.HardwareBuffer;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
@@ -553,6 +554,21 @@
     }
 
     /**
+     * Test that images captured after discarding free buffers are valid.
+     */
+    public void testDiscardFreeBuffers() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "Testing jpeg capture for Camera " + id);
+                openDevice(id);
+                discardFreeBuffersTestByCamera();
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
      * Convert a rectangular patch in a YUV image to an ARGB color array.
      *
      * @param w width of the patch.
@@ -762,6 +778,38 @@
         imageInvalidAccessTestAfterClose(img, firstPlane, buffer);
     }
 
+    /**
+     * Test that images captured after discarding free buffers are valid.
+     */
+    private void discardFreeBuffersTestByCamera() throws Exception {
+        final int FORMAT = mStaticInfo.isColorOutputSupported() ?
+            ImageFormat.YUV_420_888 : ImageFormat.DEPTH16;
+
+        final Size SIZE = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
+                StaticMetadata.StreamDirection.Output)[0];
+        Image img = null;
+        // Create ImageReader.
+        mListener = new SimpleImageListener();
+        createDefaultImageReader(SIZE, FORMAT, MAX_NUM_IMAGES, mListener);
+
+        // Start capture.
+        final boolean REPEATING = true;
+        CaptureRequest request = prepareCaptureRequest();
+        SimpleCaptureCallback listener = new SimpleCaptureCallback();
+        startCapture(request, REPEATING, listener, mHandler);
+
+        // Validate images and capture results.
+        validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
+        validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+
+        // Discard free buffers.
+        mReader.discardFreeBuffers();
+
+        // Validate images and capture resulst again.
+        validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
+        validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+    }
+
     private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
 
         Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
@@ -995,6 +1043,8 @@
             if (VERBOSE) Log.v(TAG, "Got the latest image");
             CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
                     DEBUG_FILE_NAME_BASE);
+            HardwareBuffer hwb = img.getHardwareBuffer();
+            assertNotNull("Unable to retrieve the Image's HardwareBuffer", hwb);
             if (VERBOSE) Log.v(TAG, "finish validation of image " + numImageVerified);
             img.close();
             numImageVerified++;
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
new file mode 100644
index 0000000..0a9f052
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.cts.CaptureResultTest;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.CamcorderProfile;
+import android.media.ImageReader;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+
+import com.android.compatibility.common.util.Stat;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests exercising logical camera setup, configuration, and usage.
+ */
+public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase {
+    private static final String TAG = "LogicalCameraTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int CONFIGURE_TIMEOUT = 5000; //ms
+
+    private static final double NS_PER_MS = 1000000.0;
+    private static final int MAX_IMAGE_COUNT = 3;
+    private static final int NUM_FRAMES_CHECKED = 30;
+
+    private static final double FRAME_DURATION_THRESHOLD = 0.03;
+
+    private HashMap<String, StaticMetadata> mAllStaticInfo;
+
+    @Override
+    protected 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.
+     */
+    public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                openDevice(id);
+
+                Size yuvSize = mOrderedPreviewSizes.get(0);
+                // Create a YUV image reader.
+                ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(),
+                        yuvSize.getHeight(), ImageFormat.YUV_420_888, /*maxImages*/1);
+
+                CameraCaptureSession.StateCallback sessionListener =
+                        mock(CameraCaptureSession.StateCallback.class);
+                List<OutputConfiguration> outputs = new ArrayList<>();
+                OutputConfiguration outputConfig = new OutputConfiguration(
+                        imageReader.getSurface());
+                outputConfig.setPhysicalCameraId(id);
+
+                // Regardless of logical camera or non-logical camera, create a session of an
+                // output configuration with invalid physical camera id, verify that the
+                // createCaptureSession fails.
+                outputs.add(outputConfig);
+                CameraCaptureSession session =
+                        CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputs,
+                                sessionListener, mHandler);
+                verify(sessionListener, timeout(CONFIGURE_TIMEOUT).atLeastOnce()).
+                        onConfigureFailed(any(CameraCaptureSession.class));
+                verify(sessionListener, never()).onConfigured(any(CameraCaptureSession.class));
+                verify(sessionListener, never()).onReady(any(CameraCaptureSession.class));
+                verify(sessionListener, never()).onActive(any(CameraCaptureSession.class));
+                verify(sessionListener, never()).onClosed(any(CameraCaptureSession.class));
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Test for making sure that streaming from physical streams work as expected, and
+     * FPS isn't slowed down.
+     */
+    public void testBasicPhysicalStreaming() throws Exception {
+
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                openDevice(id);
+
+                if (!mStaticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                if (!mStaticInfo.isLogicalMultiCamera()) {
+                    Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
+                    continue;
+                }
+
+                assertTrue("Logical multi-camera must be LIMITED or higher",
+                        mStaticInfo.isHardwareLevelAtLeastLimited());
+
+                // Figure out preview size and physical cameras to use.
+                ArrayList<String> dualPhysicalCameraIds = new ArrayList<String>();
+                Size previewSize= findCommonPreviewSize(id, dualPhysicalCameraIds);
+                if (previewSize == null) {
+                    Log.i(TAG, "Camera " + id + ": No matching physical preview streams, skipping");
+                }
+
+                testBasicPhysicalStreamingForCamera(
+                        id, dualPhysicalCameraIds, previewSize);
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Test for making sure that multiple requests for physical cameras work as expected.
+     */
+    public void testBasicPhysicalRequests() throws Exception {
+
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                openDevice(id);
+
+                if (!mStaticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                if (!mStaticInfo.isLogicalMultiCamera()) {
+                    Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
+                    continue;
+                }
+
+                assertTrue("Logical multi-camera must be LIMITED or higher",
+                        mStaticInfo.isHardwareLevelAtLeastLimited());
+
+                // Figure out yuv size and physical cameras to use.
+                List<String> dualPhysicalCameraIds = new ArrayList<String>();
+                Size yuvSize= findCommonPreviewSize(id, dualPhysicalCameraIds);
+                if (yuvSize == null) {
+                    Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
+                    continue;
+                }
+                ArraySet<String> physicalIdSet = new ArraySet<String>(dualPhysicalCameraIds.size());
+                physicalIdSet.addAll(dualPhysicalCameraIds);
+
+                if (VERBOSE) {
+                    Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() +
+                        " x " + yuvSize.getHeight());
+                }
+                List<CaptureRequest.Key<?>> physicalRequestKeys =
+                    mStaticInfo.getCharacteristics().getAvailablePhysicalCameraRequestKeys();
+                if (physicalRequestKeys == null) {
+                    Log.i(TAG, "Camera " + id + ": no available physical request keys, skipping");
+                    continue;
+                }
+
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                List<ImageReader> imageReaders = new ArrayList<>();
+                List<SimpleImageReaderListener> imageReaderListeners = new ArrayList<>();
+                for (String physicalCameraId : dualPhysicalCameraIds) {
+                    SimpleImageReaderListener readerListener = new SimpleImageReaderListener();
+                    ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
+                            ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                            readerListener, mHandler);
+                    OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
+                    config.setPhysicalCameraId(physicalCameraId);
+                    outputConfigs.add(config);
+                    imageReaders.add(yuvTarget);
+                    imageReaderListeners.add(readerListener);
+                }
+
+                CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
+                for (OutputConfiguration c : outputConfigs) {
+                    requestBuilder.addTarget(c.getSurface());
+                }
+
+                mSessionListener = new BlockingSessionCallback();
+                mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                        mSessionListener, mHandler);
+
+                for (int i = 0; i < MAX_IMAGE_COUNT; i++) {
+                    mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler);
+                    for (SimpleImageReaderListener readerListener : imageReaderListeners) {
+                        readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
+                    }
+                }
+
+                // Test streaming requests
+                imageReaders.clear();
+                outputConfigs.clear();
+
+                // Add physical YUV streams
+                List<ImageReader> physicalTargets = new ArrayList<>();
+                for (String physicalCameraId : dualPhysicalCameraIds) {
+                    ImageReader physicalTarget = CameraTestUtils.makeImageReader(yuvSize,
+                            ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                            new ImageDropperListener(), mHandler);
+                    OutputConfiguration config = new OutputConfiguration(
+                            physicalTarget.getSurface());
+                    config.setPhysicalCameraId(physicalCameraId);
+                    outputConfigs.add(config);
+                    physicalTargets.add(physicalTarget);
+                }
+
+                mSessionListener = new BlockingSessionCallback();
+                mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                        mSessionListener, mHandler);
+
+                requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW,
+                            physicalIdSet);
+                for (OutputConfiguration c : outputConfigs) {
+                    requestBuilder.addTarget(c.getSurface());
+                }
+
+                SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback();
+                mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
+                        mHandler);
+
+                // Converge AE
+                waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+                if (mSession != null) {
+                    mSession.close();
+                }
+
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Tests invalid/incorrect multiple physical capture request cases.
+     */
+    public void testInvalidPhysicalCameraRequests() throws Exception {
+
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                openDevice(id);
+
+                if (!mStaticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                assertTrue("Logical multi-camera must be LIMITED or higher",
+                        mStaticInfo.isHardwareLevelAtLeastLimited());
+
+                Size yuvSize = mOrderedPreviewSizes.get(0);
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                List<ImageReader> imageReaders = new ArrayList<>();
+                SimpleImageReaderListener readerListener = new SimpleImageReaderListener();
+                ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
+                        ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                        readerListener, mHandler);
+                imageReaders.add(yuvTarget);
+                OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
+                outputConfigs.add(new OutputConfiguration(yuvTarget.getSurface()));
+
+                ArraySet<String> physicalIdSet = new ArraySet<String>();
+                // Invalid physical id
+                physicalIdSet.add("-2");
+
+                CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
+                requestBuilder.addTarget(config.getSurface());
+
+                // Check for invalid setting get/set
+                try {
+                    requestBuilder.getPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT, "-1");
+                    fail("No exception for invalid physical camera id");
+                } catch (IllegalArgumentException e) {
+                    //expected
+                }
+
+                try {
+                    requestBuilder.setPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT,
+                            new Integer(0), "-1");
+                    fail("No exception for invalid physical camera id");
+                } catch (IllegalArgumentException e) {
+                    //expected
+                }
+
+                mSessionListener = new BlockingSessionCallback();
+                mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                        mSessionListener, mHandler);
+
+                try {
+                    mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(),
+                            mHandler);
+                    fail("No exception for invalid physical camera id");
+                } catch (IllegalArgumentException e) {
+                    //expected
+                }
+
+                if (mStaticInfo.isLogicalMultiCamera()) {
+                    // Figure out yuv size to use.
+                    List<String> dualPhysicalCameraIds = new ArrayList<String>();
+                    Size sharedSize= findCommonPreviewSize(id, dualPhysicalCameraIds);
+                    if (sharedSize == null) {
+                        Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
+                        continue;
+                    }
+
+                    physicalIdSet.clear();
+                    physicalIdSet.addAll(dualPhysicalCameraIds);
+                    requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
+                    requestBuilder.addTarget(config.getSurface());
+                    CaptureRequest request = requestBuilder.build();
+
+                    try {
+                        mSession.capture(request, new SimpleCaptureCallback(), mHandler);
+                        fail("Capture requests that don't configure outputs with corresponding " +
+                                "physical camera id should fail");
+                    } catch (IllegalArgumentException e) {
+                        //expected
+                    }
+                }
+
+                if (mSession != null) {
+                    mSession.close();
+                }
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Find a common preview size that's supported by both the logical camera and
+     * two of the underlying physical cameras.
+     */
+    private Size findCommonPreviewSize(String cameraId,
+            List<String> dualPhysicalCameraIds) throws Exception {
+
+        List<String> physicalCameraIds =
+                mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+        assertTrue("Logical camera must contain at least 2 physical camera ids",
+                physicalCameraIds.size() >= 2);
+
+        List<Size> previewSizes = getSupportedPreviewSizes(
+                cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+        HashMap<String, List<Size>> physicalPreviewSizesMap = new HashMap<String, List<Size>>();
+        HashMap<String, StreamConfigurationMap> physicalConfigs = new HashMap<>();
+        for (String physicalCameraId : physicalCameraIds) {
+            CameraCharacteristics properties =
+                    mCameraManager.getCameraCharacteristics(physicalCameraId);
+            assertNotNull("Can't get camera characteristics!", properties);
+            StreamConfigurationMap configMap =
+                properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+            physicalConfigs.put(physicalCameraId, configMap);
+            physicalPreviewSizesMap.put(physicalCameraId,
+                    getSupportedPreviewSizes(physicalCameraId, mCameraManager, PREVIEW_SIZE_BOUND));
+        }
+
+        // Find display size from window service.
+        Context context = getInstrumentation().getTargetContext();
+        WindowManager windowManager =
+                (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = windowManager.getDefaultDisplay();
+
+        int displayWidth = display.getWidth();
+        int displayHeight = display.getHeight();
+
+        if (displayHeight > displayWidth) {
+            displayHeight = displayWidth;
+            displayWidth = display.getHeight();
+        }
+
+        StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        for (Size previewSize : previewSizes) {
+            dualPhysicalCameraIds.clear();
+            // Skip preview sizes larger than screen size
+            if (previewSize.getWidth() > displayWidth ||
+                    previewSize.getHeight() > displayHeight) {
+                continue;
+            }
+
+            final long minFrameDuration = config.getOutputMinFrameDuration(
+                   ImageFormat.YUV_420_888, previewSize);
+
+            ArrayList<String> supportedPhysicalCameras = new ArrayList<String>();
+            for (String physicalCameraId : physicalCameraIds) {
+                List<Size> physicalPreviewSizes = physicalPreviewSizesMap.get(physicalCameraId);
+                if (physicalPreviewSizes.contains(previewSize)) {
+                   long minDurationPhysical =
+                           physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
+                           ImageFormat.YUV_420_888, previewSize);
+                   if (minDurationPhysical <= minFrameDuration) {
+                        dualPhysicalCameraIds.add(physicalCameraId);
+                        if (dualPhysicalCameraIds.size() == 2) {
+                            return previewSize;
+                        }
+                   }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Test physical camera YUV streaming within a particular logical camera.
+     *
+     * Use 2 YUV streams with PREVIEW or smaller size, which is guaranteed for LIMITED device level.
+     */
+    private void testBasicPhysicalStreamingForCamera(String logicalCameraId,
+            List<String> physicalCameraIds, Size previewSize) throws Exception {
+        List<OutputConfiguration> outputConfigs = new ArrayList<>();
+        List<ImageReader> imageReaders = new ArrayList<>();
+
+        // Add 1 logical YUV stream
+        ImageReader logicalTarget = CameraTestUtils.makeImageReader(previewSize,
+                ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                new ImageDropperListener(), mHandler);
+        imageReaders.add(logicalTarget);
+        outputConfigs.add(new OutputConfiguration(logicalTarget.getSurface()));
+
+        // Add physical YUV streams
+        List<ImageReader> physicalTargets = new ArrayList<>();
+        for (String physicalCameraId : physicalCameraIds) {
+            ImageReader physicalTarget = CameraTestUtils.makeImageReader(previewSize,
+                    ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                    new ImageDropperListener(), mHandler);
+            OutputConfiguration config = new OutputConfiguration(physicalTarget.getSurface());
+            config.setPhysicalCameraId(physicalCameraId);
+            outputConfigs.add(config);
+            physicalTargets.add(physicalTarget);
+        }
+
+        mSessionListener = new BlockingSessionCallback();
+        mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                mSessionListener, mHandler);
+
+        // Stream logical YUV stream and note down the FPS
+        CaptureRequest.Builder requestBuilder =
+                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        requestBuilder.addTarget(logicalTarget.getSurface());
+
+        SimpleCaptureCallback simpleResultListener =
+                new SimpleCaptureCallback();
+        StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        final long minFrameDuration = config.getOutputMinFrameDuration(
+                ImageFormat.YUV_420_888, previewSize);
+        if (minFrameDuration > 0) {
+            Range<Integer> targetRange = getSuitableFpsRangeForDuration(logicalCameraId,
+                    minFrameDuration);
+            requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+        }
+        mSession.setRepeatingRequest(requestBuilder.build(),
+                simpleResultListener, mHandler);
+
+        // Converge AE
+        waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+        if (mStaticInfo.isAeLockSupported()) {
+            // Lock AE if supported.
+            requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+            mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
+                    mHandler);
+            waitForResultValue(simpleResultListener, CaptureResult.CONTROL_AE_STATE,
+                    CaptureResult.CONTROL_AE_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT);
+        }
+
+        // Verify results
+        CaptureResultTest.validateCaptureResult(mCollector, simpleResultListener,
+                mStaticInfo, mAllStaticInfo, null/*requestedPhysicalIds*/,
+                requestBuilder, NUM_FRAMES_CHECKED);
+
+        // Collect timestamps for one logical stream only.
+        long prevTimestamp = -1;
+        long[] logicalTimestamps = new long[NUM_FRAMES_CHECKED];
+        for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
+            TotalCaptureResult totalCaptureResult =
+                    simpleResultListener.getTotalCaptureResult(
+                    CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
+            logicalTimestamps[i] = totalCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP);
+        }
+
+        double logicalAvgDurationMs = (logicalTimestamps[NUM_FRAMES_CHECKED-1] -
+                logicalTimestamps[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1));
+
+        // Start requesting on both logical and physical streams
+        SimpleCaptureCallback simpleResultListenerDual =
+                new SimpleCaptureCallback();
+        for (ImageReader physicalTarget : physicalTargets) {
+            requestBuilder.addTarget(physicalTarget.getSurface());
+        }
+        mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListenerDual,
+                mHandler);
+
+        // Verify results for physical streams request.
+        CaptureResultTest.validateCaptureResult(mCollector, simpleResultListenerDual,
+                mStaticInfo, mAllStaticInfo, physicalCameraIds, requestBuilder,
+                NUM_FRAMES_CHECKED);
+
+        // Acquire the timestamps of the physical camera.
+        long[] logicalTimestamps2 = new long[NUM_FRAMES_CHECKED];
+        long [][] physicalTimestamps = new long[physicalTargets.size()][];
+        for (int i = 0; i < physicalTargets.size(); i++) {
+            physicalTimestamps[i] = new long[NUM_FRAMES_CHECKED];
+        }
+        for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
+            TotalCaptureResult totalCaptureResultDual =
+                    simpleResultListenerDual.getTotalCaptureResult(
+                    CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
+            logicalTimestamps2[i] = totalCaptureResultDual.get(CaptureResult.SENSOR_TIMESTAMP);
+
+            int index = 0;
+            Map<String, CaptureResult> physicalResultsDual =
+                    totalCaptureResultDual.getPhysicalCameraResults();
+            for (String physicalId : physicalCameraIds) {
+                 physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
+                         CaptureResult.SENSOR_TIMESTAMP);
+                 index++;
+            }
+        }
+
+        // Check timestamp monolithity for individual camera and across cameras
+        for (int i = 0; i < NUM_FRAMES_CHECKED-1; i++) {
+            assertTrue("Logical camera timestamp must monolithically increase",
+                    logicalTimestamps2[i] < logicalTimestamps2[i+1]);
+        }
+        for (int i = 0; i < physicalCameraIds.size(); i++) {
+            for (int j = 0 ; j < NUM_FRAMES_CHECKED-1; j++) {
+                assertTrue("Physical camera timestamp must monolithically increase",
+                        physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
+                if (j > 0) {
+                    assertTrue("Physical camera's timestamp N must be greater than logical " +
+                            "camera's timestamp N-1",
+                            physicalTimestamps[i][j] > logicalTimestamps[j-1]);
+                }
+                assertTrue("Physical camera's timestamp N must be less than logical camera's " +
+                        "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
+            }
+        }
+        double logicalAvgDurationMs2 = (logicalTimestamps2[NUM_FRAMES_CHECKED-1] -
+                logicalTimestamps2[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1));
+
+        mCollector.expectLessOrEqual("The average frame duration increase of all physical "
+                + "streams is larger than threshold: "
+                + String.format("increase = %.2f, threshold = %.2f",
+                  (logicalAvgDurationMs2 - logicalAvgDurationMs)/logicalAvgDurationMs,
+                  FRAME_DURATION_THRESHOLD),
+                logicalAvgDurationMs*(1+FRAME_DURATION_THRESHOLD),
+                logicalAvgDurationMs2);
+
+        // Stop preview
+        if (mSession != null) {
+            mSession.close();
+        }
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 265729d..0db7384 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -21,13 +21,19 @@
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.cts.CameraTestUtils.ImageVerifierListener;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase;
 import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase.CameraPreviewListener;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.media.Image;
 import android.media.ImageReader;
 import android.os.SystemClock;
+import android.os.ConditionVariable;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -44,8 +50,11 @@
  */
 public class MultiViewTest extends Camera2MultiViewTestCase {
     private static final String TAG = "MultiViewTest";
-    private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 2000;
+    private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 5000; //ms
     private final static long PREVIEW_TIME_MS = 2000;
+    private final static int NUM_SURFACE_SWITCHES = 10;
+    private final static int IMG_READER_COUNT = 2;
+    private final static int YUV_IMG_READER_COUNT = 3;
 
     public void testTextureViewPreview() throws Exception {
         for (String cameraId : mCameraIds) {
@@ -235,6 +244,579 @@
     }
 
     /*
+     * Verify dynamic shared surface behavior.
+     */
+    public void testSharedSurfaceBasic() throws Exception {
+        for (String cameraId : mCameraIds) {
+            try {
+                openCamera(cameraId);
+                if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+                    continue;
+                }
+                if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+
+                testSharedSurfaceBasicByCamera(cameraId);
+            }
+            finally {
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    private void testSharedSurfaceBasicByCamera(String cameraId) throws Exception {
+        Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+        CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+        SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+        Surface[] surfaces = new Surface[2];
+        OutputConfiguration[] outputConfigs = new OutputConfiguration[2];
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+
+        // Create surface textures with the same size
+        for (int i = 0; i < 2; i++) {
+            previewListener[i] = new CameraPreviewListener();
+            mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+            previewTexture[i] = getAvailableSurfaceTexture(
+                    WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+            assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+            previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+            // Correct the preview display rotation.
+            updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+            surfaces[i] = new Surface(previewTexture[i]);
+            outputConfigs[i] = new OutputConfiguration(surfaces[i]);
+            outputConfigs[i].enableSurfaceSharing();
+            outputConfigurations.add(outputConfigs[i]);
+        }
+
+        startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+        boolean previewDone =
+                previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+        assertTrue("Unable to start preview", previewDone);
+
+        //try to dynamically add and remove any of the initial configured outputs
+        try {
+            outputConfigs[1].addSurface(surfaces[0]);
+            updateOutputConfiguration(cameraId, outputConfigs[1]);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+            outputConfigs[1].removeSurface(surfaces[0]);
+        }
+
+        try {
+            outputConfigs[1].removeSurface(surfaces[1]);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+        }
+
+        try {
+            outputConfigs[0].addSurface(surfaces[1]);
+            updateOutputConfiguration(cameraId, outputConfigs[0]);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+            outputConfigs[0].removeSurface(surfaces[1]);
+        }
+
+        try {
+            outputConfigs[0].removeSurface(surfaces[0]);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+        }
+
+        //Check that we are able to add a shared texture surface with different size
+        List<Size> orderedPreviewSizes = getOrderedPreviewSizes(cameraId);
+        Size textureSize = previewSize;
+        for (Size s : orderedPreviewSizes) {
+            if (!s.equals(previewSize)) {
+                textureSize = s;
+                break;
+            }
+        }
+        if (textureSize.equals(previewSize)) {
+            return;
+        }
+        SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
+        outputTexture.setDefaultBufferSize(textureSize.getWidth(), textureSize.getHeight());
+        Surface outputSurface = new Surface(outputTexture);
+        //Add a valid output surface and then verify that it cannot be added any more
+        outputConfigs[1].addSurface(outputSurface);
+        updateOutputConfiguration(cameraId, outputConfigs[1]);
+        try {
+            outputConfigs[1].addSurface(outputSurface);
+            fail("should get IllegalStateException due to duplicate output");
+        } catch (IllegalStateException e) {
+            // expected exception
+        }
+
+        outputConfigs[0].addSurface(outputSurface);
+        try {
+            updateOutputConfiguration(cameraId, outputConfigs[0]);
+            fail("should get IllegalArgumentException due to duplicate output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+            outputConfigs[0].removeSurface(outputSurface);
+        }
+
+        //Verify that the same surface cannot be removed twice
+        outputConfigs[1].removeSurface(outputSurface);
+        updateOutputConfiguration(cameraId, outputConfigs[1]);
+        try {
+            outputConfigs[0].removeSurface(outputSurface);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+        }
+        try {
+            outputConfigs[1].removeSurface(outputSurface);
+            fail("should get IllegalArgumentException due to invalid output");
+        } catch (IllegalArgumentException e) {
+            // expected exception
+        }
+
+        stopPreview(cameraId);
+    }
+
+    /*
+     * Verify dynamic shared surface behavior using multiple ImageReaders.
+     */
+    public void testSharedSurfaceImageReaderSwitch() throws Exception {
+        for (String cameraId : mCameraIds) {
+            try {
+                openCamera(cameraId);
+                if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+                    continue;
+                }
+                if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+
+                testSharedSurfaceImageReaderSwitch(cameraId, NUM_SURFACE_SWITCHES);
+            }
+            finally {
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    private void testSharedSurfaceImageReaderSwitch(String cameraId, int switchCount)
+            throws Exception {
+        SimpleImageListener imageListeners[] = new SimpleImageListener[IMG_READER_COUNT];
+        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+        ImageReader imageReaders[] = new ImageReader[IMG_READER_COUNT];
+        Surface readerSurfaces[] = new Surface[IMG_READER_COUNT];
+        Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+        CameraPreviewListener previewListener = new CameraPreviewListener();
+        mTextureView[0].setSurfaceTextureListener(previewListener);
+        SurfaceTexture previewTexture = getAvailableSurfaceTexture(WAIT_FOR_COMMAND_TO_COMPLETE,
+                mTextureView[0]);
+        assertNotNull("Unable to get preview surface texture", previewTexture);
+        previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+        updatePreviewDisplayRotation(previewSize, mTextureView[0]);
+        Surface previewSurface = new Surface(previewTexture);
+        OutputConfiguration outputConfig = new OutputConfiguration(previewSurface);
+        outputConfig.enableSurfaceSharing();
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+        outputConfigurations.add(outputConfig);
+
+        if (outputConfig.getMaxSharedSurfaceCount() < (IMG_READER_COUNT + 1)) {
+            return;
+        }
+
+        //Start regular preview streaming
+        startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+        boolean previewDone = previewListener.waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+        assertTrue("Unable to start preview", previewDone);
+
+        //Test shared image reader outputs
+        for (int i = 0; i < IMG_READER_COUNT; i++) {
+            imageListeners[i] = new SimpleImageListener();
+            imageReaders[i] = ImageReader.newInstance(previewSize.getWidth(),
+                    previewSize.getHeight(), ImageFormat.PRIVATE, 2);
+            imageReaders[i].setOnImageAvailableListener(imageListeners[i], mHandler);
+            readerSurfaces[i] = imageReaders[i].getSurface();
+        }
+
+        for (int j = 0; j < switchCount; j++) {
+            for (int i = 0; i < IMG_READER_COUNT; i++) {
+                outputConfig.addSurface(readerSurfaces[i]);
+                updateOutputConfiguration(cameraId, outputConfig);
+                CaptureRequest.Builder imageReaderRequestBuilder = getCaptureBuilder(cameraId,
+                        CameraDevice.TEMPLATE_PREVIEW);
+                imageReaderRequestBuilder.addTarget(readerSurfaces[i]);
+                capture(cameraId, imageReaderRequestBuilder.build(), resultListener);
+                imageListeners[i].waitForAnyImageAvailable(PREVIEW_TIME_MS);
+                Image img = imageReaders[i].acquireLatestImage();
+                assertNotNull("Invalid image acquired!", img);
+                img.close();
+                outputConfig.removeSurface(readerSurfaces[i]);
+                updateOutputConfiguration(cameraId, outputConfig);
+            }
+        }
+
+        for (int i = 0; i < IMG_READER_COUNT; i++) {
+            imageReaders[i].close();
+        }
+
+        stopPreview(cameraId);
+    }
+
+    /*
+     * Verify dynamic shared surface behavior using YUV ImageReaders.
+     */
+    public void testSharedSurfaceYUVImageReaderSwitch() throws Exception {
+        int YUVFormats[] = {ImageFormat.YUV_420_888, ImageFormat.YUV_422_888,
+            ImageFormat.YUV_444_888, ImageFormat.YUY2, ImageFormat.YV12,
+            ImageFormat.NV16, ImageFormat.NV21};
+        for (String cameraId : mCameraIds) {
+            try {
+                openCamera(cameraId);
+                if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+                    continue;
+                }
+                if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                Size frameSize = null;
+                int yuvFormat = -1;
+                for (int it : YUVFormats) {
+                    Size yuvSizes[] = getStaticInfo(cameraId).getAvailableSizesForFormatChecked(
+                            it, StaticMetadata.StreamDirection.Output);
+                    if (yuvSizes != null) {
+                        frameSize = yuvSizes[0];
+                        yuvFormat = it;
+                        break;
+                    }
+                }
+
+                if ((yuvFormat != -1) && (frameSize.getWidth() > 0) &&
+                        (frameSize.getHeight() > 0)) {
+                    testSharedSurfaceYUVImageReaderSwitch(cameraId, NUM_SURFACE_SWITCHES, yuvFormat,
+                            frameSize);
+                } else {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support YUV outputs, skipping");
+                }
+            }
+            finally {
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    private void testSharedSurfaceYUVImageReaderSwitch(String cameraId, int switchCount, int format,
+            Size frameSize) throws Exception {
+
+        assertTrue("YUV_IMG_READER_COUNT should be equal or greater than 2",
+                (YUV_IMG_READER_COUNT >= 2));
+
+        SimpleImageListener imageListeners[] = new SimpleImageListener[YUV_IMG_READER_COUNT];
+        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+        ImageReader imageReaders[] = new ImageReader[YUV_IMG_READER_COUNT];
+        Surface readerSurfaces[] = new Surface[YUV_IMG_READER_COUNT];
+
+        for (int i = 0; i < YUV_IMG_READER_COUNT; i++) {
+            imageListeners[i] = new SimpleImageListener();
+            imageReaders[i] = ImageReader.newInstance(frameSize.getWidth(), frameSize.getHeight(),
+                    format, 2);
+            imageReaders[i].setOnImageAvailableListener(imageListeners[i], mHandler);
+            readerSurfaces[i] = imageReaders[i].getSurface();
+        }
+
+        OutputConfiguration outputConfig = new OutputConfiguration(readerSurfaces[0]);
+        outputConfig.enableSurfaceSharing();
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+        outputConfigurations.add(outputConfig);
+        if (outputConfig.getMaxSharedSurfaceCount() < YUV_IMG_READER_COUNT) {
+            return;
+        }
+
+        createSessionWithConfigs(cameraId, outputConfigurations);
+
+        // Test YUV ImageReader surface sharing. The first ImageReader will
+        // always be part of the capture request, the rest will switch on each
+        // iteration.
+        for (int j = 0; j < switchCount; j++) {
+            for (int i = 1; i < YUV_IMG_READER_COUNT; i++) {
+                outputConfig.addSurface(readerSurfaces[i]);
+                updateOutputConfiguration(cameraId, outputConfig);
+                CaptureRequest.Builder imageReaderRequestBuilder = getCaptureBuilder(cameraId,
+                        CameraDevice.TEMPLATE_PREVIEW);
+                imageReaderRequestBuilder.addTarget(readerSurfaces[i]);
+                imageReaderRequestBuilder.addTarget(readerSurfaces[0]);
+                capture(cameraId, imageReaderRequestBuilder.build(), resultListener);
+                imageListeners[i].waitForAnyImageAvailable(PREVIEW_TIME_MS);
+                Image img = imageReaders[i].acquireLatestImage();
+                assertNotNull("Invalid image acquired!", img);
+                assertNotNull("Image planes are invalid!", img.getPlanes());
+                img.close();
+                imageListeners[0].waitForAnyImageAvailable(PREVIEW_TIME_MS);
+                img = imageReaders[0].acquireLatestImage();
+                assertNotNull("Invalid image acquired!", img);
+                img.close();
+                outputConfig.removeSurface(readerSurfaces[i]);
+                updateOutputConfiguration(cameraId, outputConfig);
+            }
+        }
+
+        for (int i = 0; i < YUV_IMG_READER_COUNT; i++) {
+            imageReaders[i].close();
+        }
+    }
+
+    /*
+     * Test the dynamic shared surface limit.
+     */
+    public void testSharedSurfaceLimit() throws Exception {
+        for (String cameraId : mCameraIds) {
+            try {
+                openCamera(cameraId);
+                if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+                    continue;
+                }
+                if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+
+                testSharedSurfaceLimitByCamera(cameraId,
+                        Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS);
+            }
+            finally {
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    private void testSharedSurfaceLimitByCamera(String cameraId, int surfaceLimit)
+            throws Exception {
+        Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+        CameraPreviewListener[] previewListener = new CameraPreviewListener[surfaceLimit];
+        SurfaceTexture[] previewTexture = new SurfaceTexture[surfaceLimit];
+        Surface[] surfaces = new Surface[surfaceLimit];
+        int sequenceId = -1;
+
+        // Create surface textures with the same size
+        for (int i = 0; i < surfaceLimit; i++) {
+            previewListener[i] = new CameraPreviewListener();
+            mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+            previewTexture[i] = getAvailableSurfaceTexture(
+                    WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+            assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+            previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+            // Correct the preview display rotation.
+            updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+            surfaces[i] = new Surface(previewTexture[i]);
+        }
+
+        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+        // Create shared outputs for the two surface textures
+        OutputConfiguration surfaceSharedOutput = new OutputConfiguration(surfaces[0]);
+        surfaceSharedOutput.enableSurfaceSharing();
+
+        if ((surfaceLimit <= 1) ||
+                (surfaceLimit < surfaceSharedOutput.getMaxSharedSurfaceCount())) {
+            return;
+        }
+
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+        outputConfigurations.add(surfaceSharedOutput);
+
+        startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+        boolean previewDone =
+                previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+        assertTrue("Unable to start preview", previewDone);
+        mTextureView[0].setSurfaceTextureListener(null);
+
+        SystemClock.sleep(PREVIEW_TIME_MS);
+
+        int i = 1;
+        for (; i < surfaceLimit; i++) {
+            //Add one more output surface while preview is streaming
+            if (i >= surfaceSharedOutput.getMaxSharedSurfaceCount()){
+                try {
+                    surfaceSharedOutput.addSurface(surfaces[i]);
+                    fail("should get IllegalArgumentException due to output surface limit");
+                } catch (IllegalArgumentException e) {
+                    //expected
+                    break;
+                }
+            } else {
+                surfaceSharedOutput.addSurface(surfaces[i]);
+                updateOutputConfiguration(cameraId, surfaceSharedOutput);
+                sequenceId = updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+
+                SystemClock.sleep(PREVIEW_TIME_MS);
+            }
+        }
+
+        for (; i > 0; i--) {
+            if (i >= surfaceSharedOutput.getMaxSharedSurfaceCount()) {
+                try {
+                    surfaceSharedOutput.removeSurface(surfaces[i]);
+                    fail("should get IllegalArgumentException due to output surface limit");
+                } catch (IllegalArgumentException e) {
+                    // expected exception
+                }
+            } else {
+                surfaceSharedOutput.removeSurface(surfaces[i]);
+
+            }
+        }
+        //Remove all previously added shared outputs in one call
+        updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+        long lastSequenceFrameNumber = resultListener.getCaptureSequenceLastFrameNumber(
+                sequenceId, PREVIEW_TIME_MS);
+        checkForLastFrameInSequence(lastSequenceFrameNumber, resultListener);
+        updateOutputConfiguration(cameraId, surfaceSharedOutput);
+        SystemClock.sleep(PREVIEW_TIME_MS);
+
+        stopPreview(cameraId);
+    }
+
+    /*
+     * Test dynamic shared surface switch behavior.
+     */
+    public void testSharedSurfaceSwitch() throws Exception {
+        for (String cameraId : mCameraIds) {
+            try {
+                openCamera(cameraId);
+                if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+                    continue;
+                }
+                if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + cameraId +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+
+                testSharedSurfaceSwitchByCamera(cameraId, NUM_SURFACE_SWITCHES);
+            }
+            finally {
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    private void testSharedSurfaceSwitchByCamera(String cameraId, int switchCount)
+            throws Exception {
+        Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+        CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+        SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+        Surface[] surfaces = new Surface[2];
+
+        // Create surface textures with the same size
+        for (int i = 0; i < 2; i++) {
+            previewListener[i] = new CameraPreviewListener();
+            mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+            previewTexture[i] = getAvailableSurfaceTexture(
+                    WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+            assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+            previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+            // Correct the preview display rotation.
+            updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+            surfaces[i] = new Surface(previewTexture[i]);
+        }
+
+        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+        // Create shared outputs for the two surface textures
+        OutputConfiguration surfaceSharedOutput = new OutputConfiguration(surfaces[0]);
+        surfaceSharedOutput.enableSurfaceSharing();
+
+        List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+        outputConfigurations.add(surfaceSharedOutput);
+
+        startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+        boolean previewDone =
+                previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+        assertTrue("Unable to start preview", previewDone);
+        mTextureView[0].setSurfaceTextureListener(null);
+
+        SystemClock.sleep(PREVIEW_TIME_MS);
+
+        for (int i = 0; i < switchCount; i++) {
+            //Add one more output surface while preview is streaming
+            surfaceSharedOutput.addSurface(surfaces[1]);
+            updateOutputConfiguration(cameraId, surfaceSharedOutput);
+            int sequenceId = updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+
+            SystemClock.sleep(PREVIEW_TIME_MS);
+
+            //Try to remove the shared surface while while we still have active requests that
+            //use it as output.
+            surfaceSharedOutput.removeSurface(surfaces[1]);
+            try {
+                updateOutputConfiguration(cameraId, surfaceSharedOutput);
+                fail("should get IllegalArgumentException due to pending requests");
+            } catch (IllegalArgumentException e) {
+                // expected exception
+            }
+
+            //Wait for all pending requests to arrive and remove the shared output during active
+            //streaming
+            updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+            long lastSequenceFrameNumber = resultListener.getCaptureSequenceLastFrameNumber(
+                    sequenceId, PREVIEW_TIME_MS);
+            checkForLastFrameInSequence(lastSequenceFrameNumber, resultListener);
+            updateOutputConfiguration(cameraId, surfaceSharedOutput);
+
+            SystemClock.sleep(PREVIEW_TIME_MS);
+        }
+
+        stopPreview(cameraId);
+    }
+
+    private void checkForLastFrameInSequence(long lastSequenceFrameNumber,
+            SimpleCaptureCallback listener) throws Exception {
+        // Find the last frame number received in results and failures.
+        long lastFrameNumber = -1;
+        while (listener.hasMoreResults()) {
+            TotalCaptureResult result = listener.getTotalCaptureResult(PREVIEW_TIME_MS);
+            if (lastFrameNumber < result.getFrameNumber()) {
+                lastFrameNumber = result.getFrameNumber();
+            }
+        }
+
+        while (listener.hasMoreFailures()) {
+            ArrayList<CaptureFailure> failures = listener.getCaptureFailures(
+                    /*maxNumFailures*/ 1);
+            for (CaptureFailure failure : failures) {
+                if (lastFrameNumber < failure.getFrameNumber()) {
+                    lastFrameNumber = failure.getFrameNumber();
+                }
+            }
+        }
+
+        // Verify the last frame number received from capture sequence completed matches the
+        // the last frame number of the results and failures.
+        assertEquals(String.format("Last frame number from onCaptureSequenceCompleted " +
+                "(%d) doesn't match the last frame number received from " +
+                "results/failures (%d)", lastSequenceFrameNumber, lastFrameNumber),
+                lastSequenceFrameNumber, lastFrameNumber);
+    }
+
+    /*
      * Verify behavior of sharing surfaces within one OutputConfiguration
      */
     public void testSharedSurfaces() throws Exception {
@@ -397,31 +979,6 @@
         } catch (IllegalArgumentException e) {
             // expected;
         }
-
-        // Verify that non implementation-defined formats sharing are not supported.
-        int[] unsupportedFormats =
-                {ImageFormat.YUV_420_888, ImageFormat.DEPTH16, ImageFormat.DEPTH_POINT_CLOUD,
-                 ImageFormat.JPEG, ImageFormat.RAW_SENSOR, ImageFormat.RAW_PRIVATE};
-        for (int format : unsupportedFormats) {
-            Size[] availableSizes = getStaticInfo(cameraId).getAvailableSizesForFormatChecked(
-                    format, StaticMetadata.StreamDirection.Output);
-            if (availableSizes.length == 0) {
-                continue;
-            }
-            Size size = availableSizes[0];
-
-            imageReader = makeImageReader(size, format, MAX_READER_IMAGES,
-                    new ImageDropperListener(), mHandler);
-            configuration = new OutputConfiguration(OutputConfiguration.SURFACE_GROUP_ID_NONE,
-                    imageReader.getSurface());
-            configuration.enableSurfaceSharing();
-
-            List<OutputConfiguration> outputConfigs = new ArrayList<>();
-            outputConfigs.add(configuration);
-
-            verifyCreateSessionWithConfigsFailure(cameraId, outputConfigs);
-
-        }
     }
 
     private void testSharedSurfacesCaptureSessionByCamera(String cameraId) throws Exception {
@@ -517,7 +1074,7 @@
 
         // Run preview with both surfaces, and verify at least one frame is received for each
         // surface.
-        updateOutputConfigs(cameraId, deferredConfigs, null);
+        finalizeOutputConfigs(cameraId, deferredConfigs, null);
         previewDone =
                 previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
         assertTrue("Unable to start preview 1", previewDone);
@@ -547,7 +1104,7 @@
         surfaceSharedOutput.addSurface(surfaces[1]);
         deferredConfigs.clear();
         deferredConfigs.add(surfaceSharedOutput);
-        updateOutputConfigs(cameraId, deferredConfigs, null);
+        finalizeOutputConfigs(cameraId, deferredConfigs, null);
         previewDone =
                 previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
         assertTrue("Unable to start preview 1", previewDone);
@@ -574,7 +1131,7 @@
         surfaceSharedOutput.addSurface(surfaces[1]);
         deferredConfigs.clear();
         deferredConfigs.add(surfaceSharedOutput);
-        updateOutputConfigs(cameraId, deferredConfigs, null);
+        finalizeOutputConfigs(cameraId, deferredConfigs, null);
         for (int i = 0; i < 2; i++) {
             previewDone =
                     previewListener[i].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
@@ -584,4 +1141,20 @@
         SystemClock.sleep(PREVIEW_TIME_MS);
         stopPreview(cameraId);
     }
+
+    private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
+        private final ConditionVariable imageAvailable = new ConditionVariable();
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            imageAvailable.open();
+        }
+
+        public void waitForAnyImageAvailable(long timeout) {
+            if (imageAvailable.block(timeout)) {
+                imageAvailable.close();
+            } else {
+                fail("wait for image available timed out after " + timeout + "ms");
+            }
+        }
+    }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
index e2a61a6..06158d8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2.cts;
 
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.util.Log;
 import android.util.Size;
@@ -59,8 +60,29 @@
                 testCameraDeviceSimplePreviewNative(mPreviewSurface));
     }
 
+    public void testCameraDevicePreviewWithSessionParameters() {
+        // Init preview surface to a guaranteed working size
+        updatePreviewSurface(new Size(640, 480));
+        assertTrue("testCameraDevicePreviewWithSessionParametersNative fail, see log for details",
+                testCameraDevicePreviewWithSessionParametersNative(mPreviewSurface));
+    }
+
+    public void testCameraDeviceSharedOutputUpdate() {
+        // Init preview surface to a guaranteed working size
+        Size previewSize = new Size(640, 480);
+        updatePreviewSurface(previewSize);
+        SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
+        outputTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+        Surface outputSurface = new Surface(outputTexture);
+        assertTrue("testCameraDeviceSharedWindowAddRemove fail, see log for details",
+                testCameraDeviceSharedOutputUpdate(mPreviewSurface, outputSurface));
+    }
+
     private static native boolean testCameraDeviceOpenAndCloseNative();
     private static native boolean testCameraDeviceCreateCaptureRequestNative();
     private static native boolean testCameraDeviceSessionOpenAndCloseNative(Surface preview);
     private static native boolean testCameraDeviceSimplePreviewNative(Surface preview);
+    private static native boolean testCameraDevicePreviewWithSessionParametersNative(
+            Surface preview);
+    private static native boolean testCameraDeviceSharedOutputUpdate(Surface src, Surface dst);
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 1e9696f..3a1eb33 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -65,6 +65,7 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
     private static final int RECORDING_DURATION_MS = 3000;
+    private static final int PREVIEW_DURATION_MS = 3000;
     private static final float DURATION_MARGIN = 0.2f;
     private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
     private static final float FRAMEDURATION_MARGIN = 0.2f;
@@ -295,7 +296,8 @@
     }
 
     public void testConstrainedHighSpeedRecording() throws Exception {
-        constrainedHighSpeedRecording();
+        constrainedHighSpeedRecording(/*enableSessionParams*/ false);
+        constrainedHighSpeedRecording(/*enableSessionParams*/ true);
     }
 
     /**
@@ -495,7 +497,8 @@
                     // Start recording
                     SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
                     startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate,
-                            fpsRange, resultListener, /*useHighSpeedSession*/false);
+                            fpsRange, resultListener, /*useHighSpeedSession*/false,
+                            /*enableHighSpeedParams*/ false);
 
                     // Record certain duration.
                     SystemClock.sleep(RECORDING_DURATION_MS);
@@ -517,7 +520,7 @@
         }
     }
 
-    private void constrainedHighSpeedRecording() throws Exception {
+    private void constrainedHighSpeedRecording(boolean enableSessionParams) throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing constrained high speed recording for camera " + id);
@@ -550,31 +553,47 @@
                             continue;
                         }
 
+                        SimpleCaptureCallback previewResultListener = new SimpleCaptureCallback();
+
+                        // prepare preview surface by using video size.
+                        updatePreviewSurfaceWithVideo(size, captureRate);
+
+                        startConstrainedPreview(fpsRange, previewResultListener);
+
                         mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
                                 "fps_" + id + "_" + size.toString() + ".mp4";
 
                         prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
 
-                        // prepare preview surface by using video size.
-                        updatePreviewSurfaceWithVideo(size, captureRate);
+                        SystemClock.sleep(PREVIEW_DURATION_MS);
 
-                        // Start recording
+                        stopCameraStreaming();
+
                         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+                        // Start recording
                         startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE,
                                 captureRate, fpsRange, resultListener,
-                                /*useHighSpeedSession*/true);
+                                /*useHighSpeedSession*/true,
+                                /*enableHighSpeedParams*/ enableSessionParams);
 
                         // Record certain duration.
                         SystemClock.sleep(RECORDING_DURATION_MS);
 
                         // Stop recording and preview
                         stopRecording(/*useMediaRecorder*/true);
+
+                        startConstrainedPreview(fpsRange, previewResultListener);
+
                         // Convert number of frames camera produced into the duration in unit of ms.
                         float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
                         float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
 
                         // Validation.
                         validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
+
+                        SystemClock.sleep(PREVIEW_DURATION_MS);
+
+                        stopCameraStreaming();
                     }
                 }
 
@@ -635,9 +654,30 @@
         return fixedRanges;
     }
 
+    private void startConstrainedPreview(Range<Integer> fpsRange,
+            CameraCaptureSession.CaptureCallback listener) throws Exception {
+        List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+        assertTrue("Preview surface should be valid", mPreviewSurface.isValid());
+        outputSurfaces.add(mPreviewSurface);
+        mSessionListener = new BlockingSessionCallback();
+        mSession = configureCameraSession(mCamera, outputSurfaces, /*isHighSpeed*/ true,
+                mSessionListener, mHandler);
+
+        List<CaptureRequest> slowMoRequests = null;
+        CaptureRequest.Builder requestBuilder =
+            mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+        requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+        requestBuilder.addTarget(mPreviewSurface);
+        slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
+            createHighSpeedRequestList(requestBuilder.build());
+
+        mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
+    }
+
     private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
             int captureRate, Range<Integer> fpsRange,
-            CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception {
+            CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession,
+            boolean enableHighSpeedParams) throws Exception {
         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
         assertTrue("Both preview and recording surfaces should be valid",
                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
@@ -648,8 +688,13 @@
             outputSurfaces.add(mReaderSurface);
         }
         mSessionListener = new BlockingSessionCallback();
-        mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
-                mSessionListener, mHandler);
+        if (useHighSpeedSession && enableHighSpeedParams) {
+            mSession = buildConstrainedCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
+                    mSessionListener, mHandler);
+        } else {
+            mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
+                    mSessionListener, mHandler);
+        }
 
         // Create slow motion request list
         List<CaptureRequest> slowMoRequests = null;
@@ -1052,6 +1097,14 @@
                 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
 
                 prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener);
+                Range<Integer> fpsRange = Range.create(profile.videoFrameRate,
+                        profile.videoFrameRate);
+                videoSnapshotRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+                        fpsRange);
+                if (mStaticInfo.isVideoStabilizationSupported()) {
+                    videoSnapshotRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                            CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+                }
                 CaptureRequest request = videoSnapshotRequestBuilder.build();
 
                 // Start recording
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index a900b84..4361a56 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -30,10 +30,12 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OisSample;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
@@ -48,7 +50,9 @@
 
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 
 import static junit.framework.Assert.assertTrue;
 import static org.mockito.Mockito.*;
@@ -146,6 +150,7 @@
 
                 assertTrue("Camera does not contain outputted image resolution " + actualSize,
                         testSizes.contains(actualSize));
+                imageReader.close();
             } finally {
                 closeDevice(id);
             }
@@ -304,6 +309,10 @@
                     for (int[] config : RAW_COMBINATIONS) {
                         testOutputCombination(id, config, maxSizes);
                     }
+                } else if (mStaticInfo.isLogicalMultiCamera()) {
+                    for (int[] config : RAW_COMBINATIONS) {
+                        testMultiCameraOutputCombination(id, config, maxSizes);
+                    }
                 }
 
                 if (mStaticInfo.isHardwareLevelAtLeast(
@@ -957,6 +966,122 @@
         }
     }
 
+    public void testAfSceneChange() throws Exception {
+        final int NUM_FRAMES_VERIFIED = 3;
+
+        for (String id : mCameraIds) {
+            Log.i(TAG, String.format("Testing Camera %s for AF scene change", id));
+
+            StaticMetadata staticInfo =
+                    new StaticMetadata(mCameraManager.getCameraCharacteristics(id));
+            if (!staticInfo.isAfSceneChangeSupported()) {
+                continue;
+            }
+
+            openDevice(id);
+
+            try {
+                SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1);
+                Surface previewSurface = new Surface(preview);
+
+                CaptureRequest.Builder previewRequest = preparePreviewTestSession(preview);
+                SimpleCaptureCallback previewListener = new CameraTestUtils.SimpleCaptureCallback();
+
+                int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+
+                // Test AF scene change in each AF mode.
+                for (int afMode : availableAfModes) {
+                    previewRequest.set(CaptureRequest.CONTROL_AF_MODE, afMode);
+
+                    int sequenceId = mCameraSession.setRepeatingRequest(previewRequest.build(),
+                            previewListener, mHandler);
+
+                    // Verify that AF scene change is NOT_DETECTED or DETECTED.
+                    for (int i = 0; i < NUM_FRAMES_VERIFIED; i++) {
+                        TotalCaptureResult result =
+                            previewListener.getTotalCaptureResult(CAPTURE_TIMEOUT);
+                        mCollector.expectKeyValueIsIn(result,
+                                CaptureResult.CONTROL_AF_SCENE_CHANGE,
+                                CaptureResult.CONTROL_AF_SCENE_CHANGE_DETECTED,
+                                CaptureResult.CONTROL_AF_SCENE_CHANGE_NOT_DETECTED);
+                    }
+
+                    mCameraSession.stopRepeating();
+                    previewListener.getCaptureSequenceLastFrameNumber(sequenceId, CAPTURE_TIMEOUT);
+                    previewListener.drain();
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    public void testOisDataMode() throws Exception {
+        final int NUM_FRAMES_VERIFIED = 3;
+
+        for (String id : mCameraIds) {
+            Log.i(TAG, String.format("Testing Camera %s for OIS mode", id));
+
+            StaticMetadata staticInfo =
+                    new StaticMetadata(mCameraManager.getCameraCharacteristics(id));
+            if (!staticInfo.isOisDataModeSupported()) {
+                continue;
+            }
+
+            openDevice(id);
+
+            try {
+                SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1);
+                Surface previewSurface = new Surface(preview);
+
+                CaptureRequest.Builder previewRequest = preparePreviewTestSession(preview);
+                SimpleCaptureCallback previewListener = new CameraTestUtils.SimpleCaptureCallback();
+
+                int[] availableOisDataModes = staticInfo.getCharacteristics().get(
+                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES);
+
+                // Test each OIS data mode
+                for (int oisMode : availableOisDataModes) {
+                    previewRequest.set(CaptureRequest.STATISTICS_OIS_DATA_MODE, oisMode);
+
+                    int sequenceId = mCameraSession.setRepeatingRequest(previewRequest.build(),
+                            previewListener, mHandler);
+
+                    // Check OIS data in each mode.
+                    for (int i = 0; i < NUM_FRAMES_VERIFIED; i++) {
+                        TotalCaptureResult result =
+                            previewListener.getTotalCaptureResult(CAPTURE_TIMEOUT);
+
+                        OisSample[] oisSamples = result.get(CaptureResult.STATISTICS_OIS_SAMPLES);
+
+                        if (oisMode == CameraCharacteristics.STATISTICS_OIS_DATA_MODE_OFF) {
+                            mCollector.expectKeyValueEquals(result,
+                                    CaptureResult.STATISTICS_OIS_DATA_MODE,
+                                    CaptureResult.STATISTICS_OIS_DATA_MODE_OFF);
+                            mCollector.expectTrue("OIS samples reported in OIS_DATA_MODE_OFF",
+                                    oisSamples == null || oisSamples.length == 0);
+
+                        } else if (oisMode == CameraCharacteristics.STATISTICS_OIS_DATA_MODE_ON) {
+                            mCollector.expectKeyValueEquals(result,
+                                    CaptureResult.STATISTICS_OIS_DATA_MODE,
+                                    CaptureResult.STATISTICS_OIS_DATA_MODE_ON);
+                            mCollector.expectTrue("OIS samples not reported in OIS_DATA_MODE_ON",
+                                    oisSamples != null && oisSamples.length != 0);
+                        } else {
+                            mCollector.addMessage(String.format("Invalid OIS mode: %d", oisMode));
+                        }
+                    }
+
+                    mCameraSession.stopRepeating();
+                    previewListener.getCaptureSequenceLastFrameNumber(sequenceId, CAPTURE_TIMEOUT);
+                    previewListener.drain();
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
     private CaptureRequest.Builder preparePreviewTestSession(SurfaceTexture preview)
             throws Exception {
         Surface previewSurface = new Surface(preview);
@@ -1171,7 +1296,11 @@
         static final int RECORD  = 1;
         static final int MAXIMUM = 2;
         static final int VGA = 3;
-        static final int RESOLUTION_COUNT = 4;
+        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;
 
         public MaxStreamSizes(StaticMetadata sm, String cameraId, Context context) {
             Size[] privSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
@@ -1203,6 +1332,71 @@
                 maxPrivSizes[VGA] = vgaSize;
                 maxYuvSizes[VGA] = vgaSize;
                 maxJpegSizes[VGA] = vgaSize;
+
+                // 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;
+                        }
+                    }
+                }
+                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;
+
             }
 
             StreamConfigurationMap configs = sm.getCharacteristics().get(
@@ -1287,6 +1481,12 @@
                 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;
@@ -1462,36 +1662,69 @@
     private void testOutputCombination(String cameraId, int[] config, MaxStreamSizes maxSizes)
             throws Exception {
 
-        Log.i(TAG, String.format("Testing Camera %s, config %s",
+        //TODO: Remove below test workaround once HAL is fixed.
+        if (mStaticInfo.isLogicalMultiCamera()) {
+            int yuvStreams = 0;
+            int rawStreams = 0;
+            for (int i = 0; i < config.length; i+= 2) {
+                int format = config[i];
+                if (format == JPEG) {
+                    return;
+                } else if (format == YUV || format == PRIV) {
+                    yuvStreams++;
+                } else if (format == RAW) {
+                    rawStreams++;
+                }
+            }
+            if (yuvStreams > 2) {
+                return;
+            }
+        }
+
+        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<Surface> outputSurfaces = new ArrayList<Surface>();
+        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, outputSurfaces, MIN_RESULT_COUNT);
+                rawTargets, outputConfigs, MIN_RESULT_COUNT, -1 /*overrideStreamIndex*/,
+                null /*overridePhysicalCameraIds*/, null /*overridePhysicalCameraSizes*/);
 
         boolean haveSession = false;
         try {
             CaptureRequest.Builder requestBuilder =
                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
 
-            for (Surface s : outputSurfaces) {
-                requestBuilder.addTarget(s);
+            for (OutputConfiguration c : outputConfigs) {
+                requestBuilder.addTarget(c.getSurface());
             }
 
             CameraCaptureSession.CaptureCallback mockCaptureCallback =
                     mock(CameraCaptureSession.CaptureCallback.class);
 
-            createSession(outputSurfaces);
+            createSessionByConfigs(outputConfigs);
             haveSession = true;
             CaptureRequest request = requestBuilder.build();
             mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
@@ -1538,55 +1771,211 @@
         }
     }
 
-    private void setupConfigurationTargets(int[] outputConfigs, MaxStreamSizes maxSizes,
+    private void testMultiCameraOutputCombination(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;
+        List<String> physicalCameraIds = mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+
+        for (int i = 0; i < config.length; i += 2) {
+            int format = config[i];
+            int sizeLimit = config[i+1];
+            if (format != YUV && format != RAW) {
+                continue;
+            }
+
+            // Find physical cameras with matching size.
+            Size targetSize = (format == YUV) ? maxSizes.maxYuvSizes[sizeLimit] :
+                    maxSizes.maxRawSize;
+            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 == RAW && 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> rawTargets = new ArrayList<ImageReader>();
+
+            setupConfigurationTargets(config, maxSizes, privTargets, jpegTargets, yuvTargets,
+                    rawTargets, outputConfigs, MIN_RESULT_COUNT, i, 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);
+
+                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 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,
+            int overrideStreamIndex, List<String> overridePhysicalCameraIds,
+            List<Size> overridePhysicalCameraSizes) {
 
         ImageDropperListener imageDropperListener = new ImageDropperListener();
 
-        for (int i = 0; i < outputConfigs.length; i += 2) {
-            int format = outputConfigs[i];
-            int sizeLimit = outputConfigs[i + 1];
+        for (int i = 0; i < configs.length; i += 2) {
+            int format = configs[i];
+            int sizeLimit = configs[i + 1];
+            Surface newSurface;
 
-            switch (format) {
-                case PRIV: {
-                    Size targetSize = maxSizes.maxPrivSizes[sizeLimit];
-                    SurfaceTexture target = new SurfaceTexture(/*random int*/1);
-                    target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
-                    outputSurfaces.add(new Surface(target));
-                    privTargets.add(target);
-                    break;
+            int numConfigs = 1;
+            if (overrideStreamIndex == i && overridePhysicalCameraIds != null &&
+                    overridePhysicalCameraIds.size() > 1) {
+                numConfigs = overridePhysicalCameraIds.size();
+            }
+            for (int j = 0; j < numConfigs; j++) {
+                switch (format) {
+                    case PRIV: {
+                        Size targetSize = (numConfigs == 1) ? maxSizes.maxPrivSizes[sizeLimit] :
+                                overridePhysicalCameraSizes.get(j);
+                        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 JPEG: {
+                        Size targetSize = (numConfigs == 1) ? maxSizes.maxJpegSizes[sizeLimit] :
+                                overridePhysicalCameraSizes.get(j);
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), JPEG, 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 YUV: {
+                        Size targetSize = (numConfigs == 1) ? maxSizes.maxYuvSizes[sizeLimit] :
+                                overridePhysicalCameraSizes.get(j);
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), YUV, 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 RAW: {
+                        Size targetSize = (numConfigs == 1) ? maxSizes.maxRawSize :
+                                overridePhysicalCameraSizes.get(j);
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), RAW, 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);
                 }
-                case JPEG: {
-                    Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), JPEG, numBuffers);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputSurfaces.add(target.getSurface());
-                    jpegTargets.add(target);
-                    break;
-                }
-                case YUV: {
-                    Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), YUV, numBuffers);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputSurfaces.add(target.getSurface());
-                    yuvTargets.add(target);
-                    break;
-                }
-                case RAW: {
-                    Size targetSize = maxSizes.maxRawSize;
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), RAW, numBuffers);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputSurfaces.add(target.getSurface());
-                    rawTargets.add(target);
-                    break;
-                }
-                default:
-                    fail("Unknown output format " + format);
             }
         }
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java b/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
index b89f9bb..98f1032 100644
--- a/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
+++ b/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
@@ -223,7 +223,7 @@
             if (other instanceof AllocationKey){
                 AllocationKey otherKey = (AllocationKey) other;
 
-                return otherKey.mType.equals(mType) && otherKey.mUsage == otherKey.mUsage;
+                return otherKey.mType.equals(mType) && otherKey.mUsage == mUsage;
             }
 
             return false;
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 0108ee6..777e0cd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.util.Size;
 import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -48,6 +49,7 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 public class Camera2AndroidTestCase extends AndroidTestCase {
@@ -66,6 +68,7 @@
     protected BlockingSessionCallback mCameraSessionListener;
     protected BlockingStateCallback mCameraListener;
     protected String[] mCameraIds;
+    protected HashMap<String, StaticMetadata> mAllStaticInfo;
     protected ImageReader mReader;
     protected Surface mReaderSurface;
     protected Handler mHandler;
@@ -109,6 +112,14 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateCallback();
         mCollector = new CameraErrorCollector();
+
+        mAllStaticInfo = new HashMap<String, StaticMetadata>();
+        for (String cameraId : mCameraIds) {
+            StaticMetadata staticMetadata = new StaticMetadata(
+                    mCameraManager.getCameraCharacteristics(cameraId),
+                    CheckLevel.ASSERT, /*collector*/null);
+            mAllStaticInfo.put(cameraId, staticMetadata);
+        }
     }
 
     @Override
@@ -217,6 +228,18 @@
     }
 
     /**
+     * Create a {@link #CameraCaptureSession} using the currently open camera with
+     * OutputConfigurations.
+     *
+     * @param outputSurfaces The set of output surfaces to configure for this session
+     */
+    protected void createSessionByConfigs(List<OutputConfiguration> outputConfigs) throws Exception {
+        mCameraSessionListener = new BlockingSessionCallback();
+        mCameraSession = CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputConfigs,
+                mCameraSessionListener, mHandler);
+    }
+
+    /**
      * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
      * given camera id. The default mCameraListener is used to wait for states.
      * <p>
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 a1d63d8..66324da 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -64,10 +64,10 @@
     private static final String TAG = "MultiViewTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-
     private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
 
-    protected TextureView[] mTextureView = new TextureView[2];
+    protected TextureView[] mTextureView =
+            new TextureView[Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS];
     protected String[] mCameraIds;
     protected Handler mHandler;
 
@@ -99,8 +99,9 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateCallback();
         Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
-        mTextureView[0] = activity.getTextureView(0);
-        mTextureView[1] = activity.getTextureView(1);
+        for (int i = 0; i < Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS; i++) {
+            mTextureView[i] = activity.getTextureView(i);
+        }
         assertNotNull("Unable to get texture view", mTextureView);
         mCameraIdMap = new HashMap<String, Integer>();
         int numCameras = mCameraIds.length;
@@ -246,13 +247,13 @@
         camera.startPreview(outputSurfaces, listener);
     }
 
-    protected void startPreviewWithConfigs(String cameraId,
+    protected int startPreviewWithConfigs(String cameraId,
             List<OutputConfiguration> outputConfigs,
             CaptureCallback listener)
             throws Exception {
         CameraHolder camera = getCameraHolder(cameraId);
         assertTrue("Camera " + cameraId + " is not openned", camera.isOpened());
-        camera.startPreviewWithConfigs(outputConfigs, listener);
+        return camera.startPreviewWithConfigs(outputConfigs, listener);
     }
 
     protected void stopPreview(String cameraId) throws Exception {
@@ -261,11 +262,39 @@
         camera.stopPreview();
     }
 
-    protected void updateOutputConfigs(String cameraId, List<OutputConfiguration> configs,
+    protected void finalizeOutputConfigs(String cameraId, List<OutputConfiguration> configs,
             CaptureCallback listener) throws Exception {
         CameraHolder camera = getCameraHolder(cameraId);
         assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
-        camera.updateOutputConfigs(configs, listener);
+        camera.finalizeOutputConfigs(configs, listener);
+    }
+
+    protected int updateRepeatingRequest(String cameraId, List<OutputConfiguration> configs,
+            CaptureCallback listener) throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+        return camera.updateRepeatingRequest(configs, listener);
+    }
+
+    protected void updateOutputConfiguration(String cameraId, OutputConfiguration config)
+            throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+        camera.updateOutputConfiguration(config);
+    }
+
+    protected void capture(String cameraId, CaptureRequest request, CaptureCallback listener)
+            throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+        camera.capture(request, listener);
+    }
+
+    protected CaptureRequest.Builder getCaptureBuilder(String cameraId, int templateId)
+            throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+        return camera.getCaptureBuilder(templateId);
     }
 
     protected StaticMetadata getStaticInfo(String cameraId) {
@@ -426,6 +455,9 @@
                 throws Exception {
             mSessionListener = new BlockingSessionCallback();
             mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+            if (outputSurfaces.isEmpty()) {
+                return;
+            }
 
             // TODO: vary the different settings like crop region to cover more cases.
             CaptureRequest.Builder captureBuilder =
@@ -457,7 +489,7 @@
                     state == BlockingSessionCallback.SESSION_CONFIGURE_FAILED);
         }
 
-        public void startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
+        public int startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
                 CaptureCallback listener)
                 throws Exception {
             createSessionWithConfigs(outputConfigs);
@@ -470,13 +502,11 @@
                     captureBuilder.addTarget(surface);
                 }
             }
-            mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+            return mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
         }
 
-        public void updateOutputConfigs(List<OutputConfiguration> configs,
+        public int updateRepeatingRequest(List<OutputConfiguration> configs,
                 CaptureCallback listener) throws Exception {
-            mSession.finalizeOutputConfigurations(configs);
-
             CaptureRequest.Builder captureBuilder =
                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
 
@@ -485,7 +515,26 @@
                     captureBuilder.addTarget(surface);
                 }
             }
-            mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+            return mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+        }
+
+        public void updateOutputConfiguration(OutputConfiguration config) throws Exception {
+            mSession.updateOutputConfiguration(config);
+        }
+
+        public void capture(CaptureRequest request, CaptureCallback listener)
+                throws Exception {
+            mSession.capture(request, listener, mHandler);
+        }
+
+        public CaptureRequest.Builder getCaptureBuilder(int templateId) throws Exception {
+            return mCamera.createCaptureRequest(templateId);
+        }
+
+        public void finalizeOutputConfigs(List<OutputConfiguration> configs,
+                CaptureCallback listener) throws Exception {
+            mSession.finalizeOutputConfigurations(configs);
+            updateRepeatingRequest(configs, listener);
         }
 
         public boolean isPreviewStarted() {
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index 3b697ce..b9655bc 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -101,7 +101,6 @@
     private static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
     private static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
 
-
     private PreviewCallback mPreviewCallback = new PreviewCallback();
     private TestShutterCallback mShutterCallback = new TestShutterCallback();
     private RawPictureCallback mRawPictureCallback = new RawPictureCallback();
@@ -462,6 +461,30 @@
     }
 
     @UiThreadTest
+    public void testStabilizationOneShotPreviewCallback() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testStabilizationOneShotPreviewCallbackByCamera(id);
+        }
+    }
+
+    private void testStabilizationOneShotPreviewCallbackByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        Parameters params = mCamera.getParameters();
+        if(!params.isVideoStabilizationSupported()) {
+            return;
+        }
+        //Check whether we can support preview callbacks along with stabilization
+        params.setVideoStabilization(true);
+        mCamera.setParameters(params);
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
+        checkPreviewCallback();
+        terminateMessageLooper();
+        assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
+    }
+
+    @UiThreadTest
     public void testSetOneShotPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3188,5 +3211,4 @@
             }
         }
     }
-
 }
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 834d37c..0f48364 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -227,18 +227,15 @@
         verify(spyStateCb, times(1)).onOpened(any(CameraDevice.class));
 
         // Verify that we can no longer open the camera, as it is held by a higher priority process
-        boolean openException = false;
         try {
             manager.openCamera(chosenCamera, spyStateCb, cameraHandler);
+            fail("Didn't receive exception when trying to open camera held by higher priority " +
+                    "process.");
         } catch(CameraAccessException e) {
             assertTrue("Received incorrect camera exception when opening camera: " + e,
                     e.getReason() == CameraAccessException.CAMERA_IN_USE);
-            openException = true;
         }
 
-        assertTrue("Didn't receive exception when trying to open camera held by higher priority " +
-                "process.", openException);
-
         // Verify that attempting to open the camera didn't cause anything weird to happen in the
         // other process.
         List<ErrorLoggingService.LogEvent> eventList2 = null;
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 aad5fd8..b592709 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -37,6 +37,7 @@
 import android.hardware.cts.helpers.CameraUtils;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.location.Location;
 import android.location.LocationManager;
@@ -780,6 +781,44 @@
     }
 
     /**
+     * Build a new constrained camera session with output surfaces, type and recording session
+     * parameters.
+     *
+     * @param camera The CameraDevice to be configured.
+     * @param outputSurfaces The surface list that used for camera output.
+     * @param listener The callback CameraDevice will notify when capture results are available.
+     */
+    public static CameraCaptureSession buildConstrainedCameraSession(CameraDevice camera,
+            List<Surface> outputSurfaces, boolean isHighSpeed,
+            CameraCaptureSession.StateCallback listener, Handler handler)
+            throws CameraAccessException {
+        BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
+
+        CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+        CaptureRequest recordSessionParams = builder.build();
+
+        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+        for (Surface surface : outputSurfaces) {
+            outConfigurations.add(new OutputConfiguration(surface));
+        }
+        SessionConfiguration sessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_HIGH_SPEED, outConfigurations, sessionListener,
+                handler);
+        sessionConfig.setSessionParameters(recordSessionParams);
+        camera.createCaptureSession(sessionConfig);
+
+        CameraCaptureSession session =
+                sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+        assertFalse("Camera session should not be a reprocessable session",
+                session.isReprocessable());
+        assertTrue("Capture session type must be High Speed",
+                CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
+                        session.getClass()));
+
+        return session;
+    }
+
+    /**
      * Configure a new camera session with output configurations.
      *
      * @param camera The CameraDevice to be configured.
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
index aa048cf..328d91f 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -841,6 +841,23 @@
         expectInRange(key.getName(), value, min, max);
     }
 
+  /**
+     * Check if the key is non-null, and the key value is in the expected range.
+     *
+     * @param request {@link CaptureRequest.Builder} to check.
+     * @param key The {@link CaptureRequest} key to be checked.
+     * @param min The min value of the range
+     * @param max The max value of the range
+     */
+    public <T extends Comparable<? super T>> void expectKeyValueInRange(
+            Builder request, CaptureRequest.Key<T> key, T min, T max) {
+        T value;
+        if ((value = expectKeyValueNotNull(request, key)) == null) {
+            return;
+        }
+        expectInRange(key.getName(), value, min, max);
+    }
+
     /**
      * Check if the key is non-null, and the key value is one of the expected values.
      *
@@ -877,6 +894,24 @@
     }
 
     /**
+     * Check if the key is non-null, and the key value is one of the expected values.
+     *
+     * @param result {@link CaptureResult} to get the key from.
+     * @param key The {@link CaptureResult} key to be checked.
+     * @param expected The expected values of the CaptureResult key.
+     */
+    public <T> void expectKeyValueIsIn(CaptureResult result,
+                                       CaptureResult.Key<T> key, T... expected) {
+        T value = expectKeyValueNotNull(result, key);
+        if (value == null) {
+            return;
+        }
+        String reason = "Key " + key.getName() + " value " + value
+                + " isn't one of the expected values " + Arrays.deepToString(expected);
+        expectContains(reason, expected, value);
+    }
+
+    /**
      * Check if the key is non-null, and the key value contains the expected element.
      *
      * @param characteristics {@link CameraCharacteristics} to check.
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 319ec91..24b948b 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
@@ -1546,6 +1546,36 @@
         return edgeModes;
     }
 
+      public int[] getAvailableShadingModesChecked() {
+        Key<int[]> key = CameraCharacteristics.SHADING_AVAILABLE_MODES;
+        int[] shadingModes = getValueFromKeyNonNull(key);
+
+        if (shadingModes == null) {
+            return new int[0];
+        }
+
+        List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(shadingModes));
+        // Full device should always include OFF and FAST
+        if (isHardwareLevelAtLeastFull()) {
+            checkTrueForKey(key, "Full device must contain OFF and FAST shading modes",
+                    modeList.contains(CameraMetadata.SHADING_MODE_OFF) &&
+                    modeList.contains(CameraMetadata.SHADING_MODE_FAST));
+        }
+
+        if (isHardwareLevelAtLeastLimited()) {
+            // FAST and HIGH_QUALITY mode must be both present or both not present
+            List<Integer> coupledModes = Arrays.asList(new Integer[] {
+                    CameraMetadata.SHADING_MODE_FAST,
+                    CameraMetadata.SHADING_MODE_HIGH_QUALITY
+            });
+            checkTrueForKey(
+                    key, " FAST and HIGH_QUALITY mode must both present or both not present",
+                    containsAllOrNone(modeList, coupledModes));
+        }
+
+        return shadingModes;
+    }
+
     public int[] getAvailableNoiseReductionModesChecked() {
         Key<int[]> key =
                 CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
@@ -1860,7 +1890,7 @@
 
         checkArrayValuesInRange(key, availableCaps,
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO);
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA);
         capList = Arrays.asList(CameraTestUtils.toObject(availableCaps));
         return capList;
     }
@@ -2182,6 +2212,18 @@
     }
 
     /**
+     * Check if this camera device is a logical multi-camera backed by multiple
+     * physical cameras.
+     *
+     * @return true if this is a logical multi-camera.
+     */
+    public boolean isLogicalMultiCamera() {
+        List<Integer> availableCapabilities = getAvailableCapabilitiesChecked();
+        return (availableCapabilities.contains(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA));
+    }
+
+    /**
      * Check if high speed video is supported (HIGH_SPEED_VIDEO scene mode is
      * supported, supported high speed fps ranges and sizes are valid).
      *
@@ -2258,6 +2300,33 @@
     }
 
     /**
+     * Check if AF scene change key is supported.
+     */
+    public boolean isAfSceneChangeSupported() {
+        return areKeysAvailable(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+    }
+
+    /**
+     * Check if OIS data mode is supported.
+     */
+    public boolean isOisDataModeSupported() {
+        int[] availableOisDataModes = mCharacteristics.get(
+                CameraCharacteristics.STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES);
+
+        if (availableOisDataModes == null) {
+            return false;
+        }
+
+        for (int mode : availableOisDataModes) {
+            if (mode == CameraMetadata.STATISTICS_OIS_DATA_MODE_ON) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Get the value in index for a fixed-size array from a given key.
      *
      * <p>If the camera device is incorrectly reporting values, log a warning and return
diff --git a/tests/core/Android.mk b/tests/core/Android.mk
index 7a2a708..e9c9793 100644
--- a/tests/core/Android.mk
+++ b/tests/core/Android.mk
@@ -14,6 +14,4 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-BUILD_CTSCORE_PACKAGE:=$(LOCAL_PATH)/ctscore.mk
-
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/core/ctscore.mk b/tests/core/ctscore.mk
deleted file mode 100644
index 10a91f1..0000000
--- a/tests/core/ctscore.mk
+++ /dev/null
@@ -1,35 +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.
-
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-# don't include these packages in any target
-LOCAL_MODULE_TAGS := optional
-# and when installed explicitly put them in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# Don't delete META-INF from the core-tests jar
-LOCAL_DONT_DELETE_JAR_META_INF := true
-
-# TODO: Clean up this mess. (b/26483949). libnativehelper_compat_libc++ pulls in its own
-# static copy of libc++ and the libc++ we're bundling here is the platform libc++. This is
-# bround to break but is being submitted as a workaround for failing CTS tests.
-LOCAL_JNI_SHARED_LIBRARIES := libjavacoretests libsqlite_jni libnativehelper_compat_libc++ libc++
-
-# Include both the 32 and 64 bit versions of libjavacoretests,
-# where applicable.
-LOCAL_MULTILIB := both
-
-include $(BUILD_PACKAGE)
diff --git a/tests/core/runner/Android.mk b/tests/core/runner/Android.mk
index 282de2b..e2d5047 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -14,22 +14,6 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-include $(CLEAR_VARS)
-
-# include this package in the tests target for continuous testing
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_PACKAGE_NAME := android.core.tests.runner
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := cts-test-runner
-
-include $(BUILD_CTSCORE_PACKAGE)
-
 #==========================================================
 # Build the core runner.
 #==========================================================
@@ -46,7 +30,8 @@
     vogarexpect \
     testng
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java b/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
deleted file mode 100644
index 108e8fd..0000000
--- a/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
+++ /dev/null
@@ -1,258 +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.
- */
-
-/**
- * This file is a copy of https://cs.corp.google.com/android/frameworks/testing/runner/src/main/java/android/support/test/internal/runner/TestLoader.java
- * The only changes that have been made starts with // Libcore-specific
- */
-
-package com.android.cts.core.internal.runner;
-
-import android.util.Log;
-
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
- */
-public final class TestLoader {
-
-    private static final String LOG_TAG = "TestLoader";
-    // Libcore-specific change: Fully qualified name of TestNG annotation class.
-    private static final String TESTNG_TEST = "org.testng.annotations.Test";
-
-    private Map<String, Class<?>> mLoadedClassesMap = new LinkedHashMap<String, Class<?>>();
-    private Map<String, Failure> mLoadFailuresMap = new LinkedHashMap<String, Failure>();
-
-    private ClassLoader mClassLoader;
-
-    /**
-     * Set the {@link ClassLoader} to be used to load test cases.
-     *
-     * @param loader {@link ClassLoader} to load test cases with.
-     */
-    public void setClassLoader(ClassLoader loader) {
-        mClassLoader = loader;
-    }
-
-    /**
-     * Loads the test class from a given class name if its not already loaded.
-     * <p/>
-     * Will store the result internally. Successfully loaded classes can be retrieved via
-     * {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
-     *
-     * @param className the class name to attempt to load
-     * @return the loaded class or null.
-     */
-    public Class<?> loadClass(String className) {
-        Class<?> loadedClass = doLoadClass(className);
-        if (loadedClass != null) {
-            mLoadedClassesMap.put(className, loadedClass);
-        }
-        return loadedClass;
-    }
-
-    protected ClassLoader getClassLoader() {
-        if (mClassLoader != null) {
-            return mClassLoader;
-        }
-
-        // TODO: InstrumentationTestRunner uses
-        // Class.forName(className, false, getTargetContext().getClassLoader());
-        // Evaluate if that is needed. Initial testing indicates
-        // getTargetContext().getClassLoader() == this.getClass().getClassLoader()
-        return this.getClass().getClassLoader();
-    }
-
-    private Class<?> doLoadClass(String className) {
-        if (mLoadFailuresMap.containsKey(className)) {
-            // Don't load classes that already failed to load
-            return null;
-        } else if (mLoadedClassesMap.containsKey(className)) {
-            // Class with the same name was already loaded, return it
-            return mLoadedClassesMap.get(className);
-        }
-
-        try {
-            ClassLoader myClassLoader = getClassLoader();
-            return Class.forName(className, false, myClassLoader);
-        } catch (ClassNotFoundException e) {
-            String errMsg = String.format("Could not find class: %s", className);
-            Log.e(LOG_TAG, errMsg);
-            Description description = Description.createSuiteDescription(className);
-            Failure failure = new Failure(description, e);
-            mLoadFailuresMap.put(className, failure);
-        }
-        return null;
-    }
-
-    /**
-     * Loads the test class from the given class name.
-     * <p/>
-     * Similar to {@link #loadClass(String)}, but will ignore classes that are
-     * not tests.
-     *
-     * @param className the class name to attempt to load
-     * @return the loaded class or null.
-     */
-    public Class<?> loadIfTest(String className) {
-        Class<?> loadedClass = doLoadClass(className);
-        if (loadedClass != null && isTestClass(loadedClass)) {
-            mLoadedClassesMap.put(className, loadedClass);
-            return loadedClass;
-        }
-        return null;
-    }
-
-    /**
-     * @return whether this {@link TestLoader} contains any loaded classes or load failures.
-     */
-    public boolean isEmpty() {
-        return mLoadedClassesMap.isEmpty() && mLoadFailuresMap.isEmpty();
-    }
-
-    /**
-     * Get the {@link Collection) of classes successfully loaded via
-     * {@link #loadIfTest(String)} calls.
-     */
-    public Collection<Class<?>> getLoadedClasses() {
-        return mLoadedClassesMap.values();
-    }
-
-    /**
-     * Get the {@link List) of {@link Failure} that occurred during
-     * {@link #loadIfTest(String)} calls.
-     */
-    public Collection<Failure> getLoadFailures() {
-        return mLoadFailuresMap.values();
-    }
-
-    /**
-     * Determines if given class is a valid test class.
-     *
-     * @param loadedClass
-     * @return <code>true</code> if loadedClass is a test
-     */
-    private boolean isTestClass(Class<?> loadedClass) {
-        try {
-            if (Modifier.isAbstract(loadedClass.getModifiers())) {
-                logDebug(String.format("Skipping abstract class %s: not a test",
-                        loadedClass.getName()));
-                return false;
-            }
-            // Libcore-specific change: Also consider TestNG annotated classes.
-            if (isTestNgTestClass(loadedClass)) {
-              return true;
-            }
-            // TODO: try to find upstream junit calls to replace these checks
-            if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
-                // ensure that if a TestCase, it has at least one test method otherwise
-                // TestSuite will throw error
-                if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
-                    return hasJUnit3TestMethod(loadedClass);
-                }
-                return true;
-            }
-            // TODO: look for a 'suite' method?
-            if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
-                return true;
-            }
-            for (Method testMethod : loadedClass.getMethods()) {
-                if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
-                    return true;
-                }
-            }
-            logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
-            return false;
-        } catch (Exception e) {
-            // Defensively catch exceptions - Will throw runtime exception if it cannot load methods.
-            // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
-            // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
-            // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
-            // generic catch all.
-            // For ICS+, Dalvik will throw a NoClassDefFoundException.
-            Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
-                    loadedClass.getName()));
-            return false;
-        } catch (Error e) {
-            // defensively catch Errors too
-            Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
-                    loadedClass.getName()));
-            return false;
-        }
-    }
-
-    private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
-        for (Method testMethod : loadedClass.getMethods()) {
-            if (isPublicTestMethod(testMethod)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // copied from junit.framework.TestSuite
-    private boolean isPublicTestMethod(Method m) {
-        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
-    }
-
-    // copied from junit.framework.TestSuite
-    private boolean isTestMethod(Method m) {
-        return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
-                && m.getReturnType().equals(Void.TYPE);
-    }
-
-    // Libcore-specific change: Add method for checking TestNG-annotated classes.
-    private static boolean isTestNgTestClass(Class<?> cls) {
-      // TestNG test is either marked @Test at the class
-      for (Annotation a : cls.getAnnotations()) {
-          if (a.annotationType().getName().equals(TESTNG_TEST)) {
-              return true;
-          }
-      }
-
-      // Or It's marked @Test at the method level
-      for (Method m : cls.getDeclaredMethods()) {
-        for (Annotation a : m.getAnnotations()) {
-          if (a.annotationType().getName().equals(TESTNG_TEST)) {
-              return true;
-          }
-        }
-      }
-
-      return false;
-    }
-
-
-    /**
-     * Utility method for logging debug messages. Only actually logs a message if LOG_TAG is marked
-     * as loggable to limit log spam during normal use.
-     */
-    private void logDebug(String msg) {
-        if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
-            Log.d(LOG_TAG, msg);
-        }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
deleted file mode 100644
index 4b18cdc..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner;
-
-import android.support.test.runner.AndroidJUnitRunner;
-
-/**
- * Constants used to communicate to and from {@link AndroidJUnitRunner}.
- */
-public interface AndroidJUnitRunnerConstants {
-
-    /**
-     * The name of the file containing the names of the tests to run.
-     *
-     * <p>This is an internal constant used within
-     * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
-     * and
-     * client side. The constant is used when there are too many test names to pass on the command
-     * line, in which case they are stored in a file that is pushed to the device and then the
-     * location of that file is passed in this argument. The {@code RunnerArgs} on the client will
-     * read the contents of that file in order to retrieve the list of names and then return that
-     * to
-     * its client without the client being aware of how that was done.
-     */
-    String ARGUMENT_TEST_FILE = "testFile";
-
-    /**
-     * The name of the file containing the names of the tests not to run.
-     *
-     * <p>This is an internal constant used within
-     * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
-     * and
-     * client side. The constant is used when there are too many test names to pass on the command
-     * line, in which case they are stored in a file that is pushed to the device and then the
-     * location of that file is passed in this argument. The {@code RunnerArgs} on the client will
-     * read the contents of that file in order to retrieve the list of names and then return that
-     * to
-     * its client without the client being aware of how that was done.
-     */
-    String ARGUMENT_NOT_TEST_FILE = "notTestFile";
-
-    /**
-     * A comma separated list of the names of test classes to run.
-     *
-     * <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
-     * available
-     * through the public API.
-     */
-    String ARGUMENT_TEST_CLASS = "class";
-
-    /**
-     * A comma separated list of the names of test classes not to run
-     */
-    String ARGUMENT_NOT_TEST_CLASS = "notClass";
-
-    /**
-     * A comma separated list of the names of test packages to run.
-     *
-     * <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
-     * available
-     * through the public API.
-     */
-    String ARGUMENT_TEST_PACKAGE = "package";
-
-    /**
-     * A comma separated list of the names of test packages not to run.
-     */
-    String ARGUMENT_NOT_TEST_PACKAGE = "notPackage";
-
-    /**
-     * Log the results as if the tests were executed but don't actually run the tests.
-     *
-     * <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
-     */
-    String ARGUMENT_LOG_ONLY = "log";
-
-    /**
-     * Wait for the debugger before starting.
-     *
-     * <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
-     * used
-     * within that class.
-     */
-    String ARGUMENT_DEBUG = "debug";
-
-    /**
-     * Only count the number of tests to run.
-     *
-     * <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
-     * used
-     * within that class.
-     */
-    String ARGUMENT_COUNT = "count";
-
-    /**
-     * The per test timeout value.
-     */
-    String ARGUMENT_TIMEOUT = "timeout_msec";
-
-    /**
-     * Token representing how long (in seconds) the current test took to execute.
-     *
-     * <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
-     */
-    String REPORT_KEY_RUNTIME = "runtime";
-
-    /**
-     * An identifier for tests run using this class.
-     */
-    String REPORT_VALUE_ID = "CoreTestRunner";
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
deleted file mode 100644
index 42b6684..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
+++ /dev/null
@@ -1,355 +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.core.runner;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Debug;
-import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.support.test.internal.util.AndroidRunnerParams;
-import android.util.Log;
-import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder;
-import com.google.common.base.Splitter;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.junit.runner.Computer;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.notification.RunListener;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.RunnerBuilder;
-
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_COUNT;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_CLASS;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_FILE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_PACKAGE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_PACKAGE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TIMEOUT;
-
-/**
- * A drop-in replacement for AndroidJUnitTestRunner, which understands the same arguments, and has
- * similar functionality, but can filter by expectations and allows a custom runner-builder to be
- * provided.
- */
-public class CoreTestRunner extends Instrumentation {
-
-    static final String TAG = "LibcoreTestRunner";
-
-    private static final java.lang.String ARGUMENT_ROOT_CLASSES = "core-root-classes";
-
-    private static final String ARGUMENT_CORE_LISTENER = "core-listener";
-
-    private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
-
-    /** The args for the runner. */
-    private Bundle args;
-
-    /** Only log the number and names of tests, and not run them. */
-    private boolean logOnly;
-
-    /** The amount of time in millis to wait for a single test to complete. */
-    private long testTimeout;
-
-    /**
-     * The list of tests to run.
-     */
-    private TestList testList;
-
-    /**
-     * The list of {@link RunListener} classes to create.
-     */
-    private List<Class<? extends RunListener>> listenerClasses;
-    private Filter expectationFilter;
-
-    @Override
-    public void onCreate(final Bundle args) {
-        super.onCreate(args);
-        this.args = args;
-
-        boolean debug = "true".equalsIgnoreCase(args.getString(ARGUMENT_DEBUG));
-        if (debug) {
-            Log.i(TAG, "Waiting for debugger to connect...");
-            Debug.waitForDebugger();
-            Log.i(TAG, "Debugger connected.");
-        }
-
-        // Log the message only after getting a value from the args so that the args are
-        // unparceled.
-        Log.d(TAG, "In OnCreate: " + args);
-
-        // Treat logOnly and count as the same. This is not quite true as count should only send
-        // the host the number of tests but logOnly should send the name and number. However,
-        // this is how this has always behaved and it does not appear to have caused any problems.
-        // Changing it seems unnecessary given that count is CTSv1 only and CTSv1 will be removed
-        // soon now that CTSv2 is ready.
-        boolean testCountOnly = args.getBoolean(ARGUMENT_COUNT);
-        this.logOnly = "true".equalsIgnoreCase(args.getString(ARGUMENT_LOG_ONLY)) || testCountOnly;
-        this.testTimeout = parseUnsignedLong(args.getString(ARGUMENT_TIMEOUT), ARGUMENT_TIMEOUT);
-
-        expectationFilter = new ExpectationBasedFilter(args);
-
-        // The test can be run specifying a list of tests to run, or as cts-tradefed does it,
-        // by passing a fileName with a test to run on each line.
-        Set<String> testNameSet = new HashSet<>();
-        String arg;
-        if ((arg = args.getString(ARGUMENT_TEST_FILE)) != null) {
-            // The tests are specified in a file.
-            try {
-                testNameSet.addAll(readTestsFromFile(arg));
-            } catch (IOException err) {
-                finish(Activity.RESULT_CANCELED, new Bundle());
-                return;
-            }
-        } else if ((arg = args.getString(ARGUMENT_TEST_CLASS)) != null) {
-            // The tests are specified in a String passed in the bundle.
-            String[] tests = arg.split(",");
-            testNameSet.addAll(Arrays.asList(tests));
-        }
-
-        // Tests may be excluded from the run by passing a list of tests not to run,
-        // or by passing a fileName with a test not to run on each line.
-        Set<String> notTestNameSet = new HashSet<>();
-        if ((arg = args.getString(ARGUMENT_NOT_TEST_FILE)) != null) {
-            // The tests are specified in a file.
-            try {
-                notTestNameSet.addAll(readTestsFromFile(arg));
-            } catch (IOException err) {
-                finish(Activity.RESULT_CANCELED, new Bundle());
-                return;
-            }
-        } else if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) {
-            // The classes are specified in a String passed in the bundle
-            String[] tests = arg.split(",");
-            notTestNameSet.addAll(Arrays.asList(tests));
-        }
-
-        Set<String> packageNameSet = new HashSet<>();
-        if ((arg = args.getString(ARGUMENT_TEST_PACKAGE)) != null) {
-            // The packages are specified in a String passed in the bundle
-            String[] packages = arg.split(",");
-            packageNameSet.addAll(Arrays.asList(packages));
-        }
-
-        Set<String> notPackageNameSet = new HashSet<>();
-        if ((arg = args.getString(ARGUMENT_NOT_TEST_PACKAGE)) != null) {
-            // The packages are specified in a String passed in the bundle
-            String[] packages = arg.split(",");
-            notPackageNameSet.addAll(Arrays.asList(packages));
-        }
-
-        List<String> roots = getRootClassNames(args);
-        if (roots == null) {
-            // Find all test classes
-            Collection<Class<?>> classes = TestClassFinder.getClasses(
-                Collections.singletonList(getContext().getPackageCodePath()),
-                getClass().getClassLoader());
-            testList = new TestList(classes);
-        } else {
-            testList = TestList.rootList(roots);
-        }
-
-        testList.addIncludeTestPackages(packageNameSet);
-        testList.addExcludeTestPackages(notPackageNameSet);
-        testList.addIncludeTests(testNameSet);
-        testList.addExcludeTests(notTestNameSet);
-
-        listenerClasses = new ArrayList<>();
-        String listenerArg = args.getString(ARGUMENT_CORE_LISTENER);
-        if (listenerArg != null) {
-            List<String> listenerClassNames = CLASS_LIST_SPLITTER.splitToList(listenerArg);
-            for (String listenerClassName : listenerClassNames) {
-                try {
-                    Class<? extends RunListener> listenerClass = Class.forName(listenerClassName)
-                            .asSubclass(RunListener.class);
-                    listenerClasses.add(listenerClass);
-                } catch (ClassNotFoundException e) {
-                    Log.e(TAG, "Could not load listener class: " + listenerClassName, e);
-                }
-            }
-        }
-
-        start();
-    }
-
-    private List<String> getRootClassNames(Bundle args) {
-        String rootClasses = args.getString(ARGUMENT_ROOT_CLASSES);
-        List<String> roots;
-        if (rootClasses == null) {
-            roots = null;
-        } else {
-            roots = CLASS_LIST_SPLITTER.splitToList(rootClasses);
-        }
-        return roots;
-    }
-
-    @Override
-    public void onStart() {
-        if (logOnly) {
-            Log.d(TAG, "Counting/logging tests only");
-        } else {
-            Log.d(TAG, "Running tests");
-        }
-
-        AndroidRunnerParams runnerParams = new AndroidRunnerParams(this, args,
-                false, testTimeout, false /*ignoreSuiteMethods*/);
-
-        Runner runner;
-        try {
-            RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams);
-            Class[] classes = testList.getClassesToRun();
-            for (Class cls : classes) {
-              Log.d(TAG, "Found class to run: " + cls.getName());
-            }
-            runner = new Computer().getSuite(runnerBuilder, classes);
-
-            if (runner instanceof Filterable) {
-                Log.d(TAG, "Applying filters");
-                Filterable filterable = (Filterable) runner;
-
-                // Filter out all the tests that are expected to fail.
-                try {
-                    filterable.filter(expectationFilter);
-                } catch (NoTestsRemainException e) {
-                    // Sometimes filtering will remove all tests but we do not care about that.
-                }
-                Log.d(TAG, "Applied filters");
-            }
-
-            // If the tests are only supposed to be logged and not actually run then replace the
-            // runner with a runner that will fire notifications for all the tests that would have
-            // been run. This is needed because CTSv2 does a log only run through a CTS module in
-            // order to generate a list of tests that will be run so that it can monitor them.
-            // Encapsulating that in a Runner implementation makes it easier to leverage the
-            // existing code for running tests.
-            if (logOnly) {
-                runner = new DescriptionHierarchyNotifier(runner.getDescription());
-            }
-
-        } catch (InitializationError e) {
-            throw new RuntimeException("Could not create a suite", e);
-        }
-
-        InstrumentationResultPrinter instrumentationResultPrinter =
-                new InstrumentationResultPrinter();
-        instrumentationResultPrinter.setInstrumentation(this);
-
-        JUnitCore core = new JUnitCore();
-        core.addListener(instrumentationResultPrinter);
-
-        // If not logging the list of tests then add any additional configured listeners. These
-        // must be added before firing any events.
-        if (!logOnly) {
-            // Add additional configured listeners.
-            for (Class<? extends RunListener> listenerClass : listenerClasses) {
-                try {
-                    RunListener runListener = listenerClass.newInstance();
-                    if (runListener instanceof InstrumentationRunListener) {
-                        ((InstrumentationRunListener) runListener).setInstrumentation(this);
-                    }
-                    core.addListener(runListener);
-                } catch (InstantiationException | IllegalAccessException e) {
-                    Log.e(TAG,
-                            "Could not create instance of listener: " + listenerClass, e);
-                }
-            }
-        }
-
-        Log.d(TAG, "Finished preparations, running/listing tests");
-
-        Bundle results = new Bundle();
-        Result junitResults = new Result();
-        try {
-            junitResults = core.run(Request.runner(runner));
-        } catch (RuntimeException e) {
-            final String msg = "Fatal exception when running tests";
-            Log.e(TAG, msg, e);
-            // report the exception to instrumentation out
-            results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
-                    msg + "\n" + Log.getStackTraceString(e));
-        } finally {
-            ByteArrayOutputStream summaryStream = new ByteArrayOutputStream();
-            // create the stream used to output summary data to the user
-            PrintStream summaryWriter = new PrintStream(summaryStream);
-            instrumentationResultPrinter.instrumentationRunFinished(summaryWriter,
-                    results, junitResults);
-            summaryWriter.close();
-            results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
-                    String.format("\n%s", summaryStream.toString()));
-        }
-
-
-        Log.d(TAG, "Finished");
-        finish(Activity.RESULT_OK, results);
-    }
-
-    /**
-     * Read tests from a specified file.
-     *
-     * @return class names of tests. If there was an error reading the file, null is returned.
-     */
-    private static List<String> readTestsFromFile(String fileName) throws IOException {
-        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
-            List<String> tests = new ArrayList<>();
-            String line;
-            while ((line = br.readLine()) != null) {
-                tests.add(line);
-            }
-            return tests;
-        } catch (IOException err) {
-            Log.e(TAG, "There was an error reading the test class list: " + err.getMessage());
-            throw err;
-        }
-    }
-
-    /**
-     * Parse long from given value - except either Long or String.
-     *
-     * @return the value, -1 if not found
-     * @throws NumberFormatException if value is negative or not a number
-     */
-    private static long parseUnsignedLong(Object value, String name) {
-        if (value != null) {
-            long longValue = Long.parseLong(value.toString());
-            if (longValue < 0) {
-                throw new NumberFormatException(name + " can not be negative");
-            }
-            return longValue;
-        }
-        return -1;
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java b/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java
deleted file mode 100644
index 5c94fd7..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner;
-
-import java.util.List;
-import org.junit.runner.Description;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.RunNotifier;
-
-/**
- * A {@link Runner} that does not actually run any tests but simply fires events for all leaf
- * {@link Description} instances in the supplied {@link Description} hierarchy.
- */
-class DescriptionHierarchyNotifier extends Runner {
-
-    private final Description description;
-
-    DescriptionHierarchyNotifier(Description description) {
-        this.description = description;
-    }
-
-    @Override
-    public Description getDescription() {
-        return description;
-    }
-
-    @Override
-    public void run(RunNotifier notifier) {
-        generateListOfTests(notifier, description);
-    }
-
-    /**
-     * Generates a list of tests to run by recursing over the {@link Description} hierarchy and
-     * firing events to simulate the tests being run successfully.
-     * @param runNotifier the notifier to which the events are sent.
-     * @param description the description to traverse.
-     */
-    private void generateListOfTests(RunNotifier runNotifier, Description description) {
-        List<Description> children = description.getChildren();
-        if (children.isEmpty()) {
-            runNotifier.fireTestStarted(description);
-            runNotifier.fireTestFinished(description);
-        } else {
-            for (Description child : children) {
-                generateListOfTests(runNotifier, child);
-            }
-        }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
index 90034ec..f409dbd 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
@@ -105,7 +105,7 @@
             if (expectationStore != null) {
                 Expectation expectation = expectationStore.get(testName);
                 if (expectation.getResult() != Result.SUCCESS) {
-                    Log.d(CoreTestRunner.TAG, "Excluding test " + testDescription
+                    Log.d(TAG, "Excluding test " + testDescription
                             + " as it matches expectation: " + expectation);
                     return false;
                 }
diff --git a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java b/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
deleted file mode 100644
index 6ad95f6..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner;
-
-import android.support.test.internal.runner.ClassPathScanner;
-import android.support.test.internal.runner.ClassPathScanner.ClassNameFilter;
-import android.util.Log;
-
-import com.android.cts.core.internal.runner.TestLoader;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Set;
-
-import dalvik.system.DexFile;
-
-/**
- * Find tests in the current APK.
- */
-public class TestClassFinder {
-
-    private static final String TAG = "TestClassFinder";
-    private static final boolean DEBUG = false;
-
-    // Excluded test packages
-    private static final String[] DEFAULT_EXCLUDED_PACKAGES = {
-            "junit",
-            "org.junit",
-            "org.hamcrest",
-            "org.mockito",// exclude Mockito for performance and to prevent JVM related errors
-            "android.support.test.internal.runner.junit3",// always skip AndroidTestSuite
-    };
-
-    static Collection<Class<?>> getClasses(List<String> apks, ClassLoader loader) {
-        if (DEBUG) {
-          Log.d(TAG, "getClasses: =======================================");
-
-          for (String apkPath : apks) {
-            Log.d(TAG, "getClasses: -------------------------------");
-            Log.d(TAG, "getClasses: APK " + apkPath);
-
-            DexFile dexFile = null;
-            try {
-                dexFile = new DexFile(apkPath);
-                Enumeration<String> apkClassNames = dexFile.entries();
-                while (apkClassNames.hasMoreElements()) {
-                    String apkClassName = apkClassNames.nextElement();
-                    Log.d(TAG, "getClasses: DexClass element " + apkClassName);
-                }
-            } catch (IOException e) {
-              throw new AssertionError(e);
-            } finally {
-                if (dexFile != null) {
-                  try {
-                    dexFile.close();
-                  } catch (IOException e) {
-                    throw new AssertionError(e);
-                  }
-                }
-            }
-            Log.d(TAG, "getClasses: -------------------------------");
-          }
-        }  // if DEBUG
-
-        List<Class<?>> classes = new ArrayList<>();
-        ClassPathScanner scanner = new ClassPathScanner(apks);
-
-        ClassPathScanner.ChainedClassNameFilter filter =
-                new ClassPathScanner.ChainedClassNameFilter();
-        // exclude inner classes
-        filter.add(new ClassPathScanner.ExternalClassNameFilter());
-
-        // exclude default classes
-        for (String defaultExcludedPackage : DEFAULT_EXCLUDED_PACKAGES) {
-            filter.add(new ExcludePackageNameFilter(defaultExcludedPackage));
-        }
-
-        // exclude any classes that aren't a "test class" (see #loadIfTest)
-        TestLoader testLoader = new TestLoader();
-        testLoader.setClassLoader(loader);
-
-        try {
-            Set<String> classNames = scanner.getClassPathEntries(filter);
-            for (String className : classNames) {
-                // Important: This further acts as an additional filter;
-                // classes that aren't a "test class" are never loaded.
-                Class<?> cls = testLoader.loadIfTest(className);
-                if (cls != null) {
-                    classes.add(cls);
-
-                    if (DEBUG) {
-                      Log.d(TAG, "getClasses: Loaded " + className);
-                    }
-                } else if (DEBUG) {
-                  Log.d(TAG, "getClasses: Failed to load class " + className);
-                }
-            }
-            return classes;
-        } catch (IOException e) {
-            Log.e(CoreTestRunner.TAG, "Failed to scan classes", e);
-        }
-
-
-        if (DEBUG) {
-            Log.d(TAG, "getClasses: =======================================");
-        }
-
-        return testLoader.getLoadedClasses();
-    }
-
-    /**
-     * A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
-     */
-    public static class ExcludePackageNameFilter implements ClassNameFilter {
-
-        private final String mPkgName;
-
-        ExcludePackageNameFilter(String pkgName) {
-            if (!pkgName.endsWith(".")) {
-                mPkgName = String.format("%s.", pkgName);
-            } else {
-                mPkgName = pkgName;
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean accept(String pathName) {
-            return !pathName.startsWith(mPkgName);
-        }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/TestList.java b/tests/core/runner/src/com/android/cts/core/runner/TestList.java
deleted file mode 100644
index 9d97501..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/TestList.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner;
-
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
-
-/**
- * A list of the tests to run.
- */
-class TestList {
-
-    /** The set of test pacakges to run */
-    private final Set<String> mIncludedPackages = new HashSet<>();
-
-    /** The set of test packages not to run */
-    private final Set<String> mExcludedPackages = new HashSet<>();
-
-    /** The set of tests (classes or methods) to run */
-    private final Set<String> mIncludedTests = new HashSet<>();
-
-    /** The set of tests (classes or methods) not to run */
-    private final Set<String> mExcludedTests = new HashSet<>();
-
-    /** The list of all test classes to run (without filtering applied)*/
-    private final Collection<Class<?>> classesToRun;
-
-    public static TestList rootList(List<String> rootList) {
-
-        // Run from the root test class.
-        Set<String> classNamesToRun = new LinkedHashSet<>(rootList);
-        Log.d(CoreTestRunner.TAG, "Running all tests rooted at " + classNamesToRun);
-
-        List<Class<?>> classesToRun1 = getClasses(classNamesToRun);
-
-        return new TestList(classesToRun1);
-    }
-
-    private static List<Class<?>> getClasses(Set<String> classNames) {
-        // Populate the list of classes to run.
-        List<Class<?>> classesToRun = new ArrayList<>();
-        for (String className : classNames) {
-            try {
-                classesToRun.add(Class.forName(className));
-            } catch (ClassNotFoundException e) {
-                throw new IllegalStateException("Could not load class '" + className, e);
-            }
-        }
-        return classesToRun;
-    }
-
-    /**
-     * @param classes The list of classes to run.
-     */
-    public TestList(Collection<Class<?>> classes) {
-        this.classesToRun = classes;
-    }
-
-    public void addIncludeTestPackages(Set<String> packageNameSet) {
-        mIncludedPackages.addAll(packageNameSet);
-    }
-
-    public void addExcludeTestPackages(Set<String> packageNameSet) {
-        mExcludedPackages.addAll(packageNameSet);
-    }
-
-    public void addIncludeTests(Set<String> testNameSet) {
-        mIncludedTests.addAll(testNameSet);
-    }
-
-    public void addExcludeTests(Set<String> testNameSet) {
-        mExcludedTests.addAll(testNameSet);
-    }
-
-    /**
-     * Return all the classes to run.
-     */
-    public Class[] getClassesToRun() {
-        return classesToRun.toArray(new Class[classesToRun.size()]);
-    }
-
-    /**
-     * Return true if the test with the specified name should be run, false otherwise.
-     */
-    public boolean shouldRunTest(String testName) {
-
-        int index = testName.indexOf('#');
-        String className;
-        if (index == -1) {
-            className = testName;
-        } else {
-            className = testName.substring(0, index);
-        }
-        try {
-            Class<?> testClass = Class.forName(className);
-            Package testPackage = testClass.getPackage();
-            String testPackageName = "";
-            if (testPackage != null) {
-                testPackageName = testPackage.getName();
-            }
-
-            boolean include =
-                    (mIncludedPackages.isEmpty() || mIncludedPackages.contains(testPackageName)) &&
-                    (mIncludedTests.isEmpty() || mIncludedTests.contains(className) ||
-                            mIncludedTests.contains(testName));
-
-            boolean exclude =
-                    mExcludedPackages.contains(testPackageName) ||
-                    mExcludedTests.contains(className) ||
-                    mExcludedTests.contains(testName);
-
-            return include && !exclude;
-        } catch (ClassNotFoundException e) {
-            Log.w("Could not load class '" + className, e);
-            return false;
-        }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
deleted file mode 100644
index 91df2b9..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner.support;
-
-import android.support.test.internal.runner.junit3.AndroidJUnit3Builder;
-import android.support.test.internal.runner.junit3.AndroidSuiteBuilder;
-import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
-import android.support.test.internal.runner.junit4.AndroidJUnit4Builder;
-import android.support.test.internal.util.AndroidRunnerParams;
-
-import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
-import org.junit.internal.builders.AnnotatedBuilder;
-import org.junit.internal.builders.IgnoredBuilder;
-import org.junit.internal.builders.JUnit3Builder;
-import org.junit.internal.builders.JUnit4Builder;
-import org.junit.runners.model.RunnerBuilder;
-
-/**
- * A {@link RunnerBuilder} that can handle all types of tests.
- */
-// A copy of package private class android.support.test.internal.runner.AndroidRunnerBuilder.
-// Copied here so that it can be extended.
-class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
-
-    private final AndroidJUnit3Builder mAndroidJUnit3Builder;
-    private final AndroidJUnit4Builder mAndroidJUnit4Builder;
-    private final AndroidSuiteBuilder mAndroidSuiteBuilder;
-    private final AndroidAnnotatedBuilder mAndroidAnnotatedBuilder;
-    // TODO: customize for Android ?
-    private final IgnoredBuilder mIgnoredBuilder;
-
-    /**
-     * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
-     */
-    // Added canUseSuiteMethod parameter.
-    AndroidRunnerBuilder(AndroidRunnerParams runnerParams, boolean canUseSuiteMethod) {
-        super(canUseSuiteMethod);
-        mAndroidJUnit3Builder = new AndroidJUnit3Builder(runnerParams);
-        mAndroidJUnit4Builder = new AndroidJUnit4Builder(runnerParams);
-        mAndroidSuiteBuilder = new AndroidSuiteBuilder(runnerParams);
-        mAndroidAnnotatedBuilder = new AndroidAnnotatedBuilder(this, runnerParams);
-        mIgnoredBuilder = new IgnoredBuilder();
-    }
-
-    @Override
-    protected JUnit4Builder junit4Builder() {
-        return mAndroidJUnit4Builder;
-    }
-
-    @Override
-    protected JUnit3Builder junit3Builder() {
-        return mAndroidJUnit3Builder;
-    }
-
-    @Override
-    protected AnnotatedBuilder annotatedBuilder() {
-        return mAndroidAnnotatedBuilder;
-    }
-
-    @Override
-    protected IgnoredBuilder ignoredBuilder() {
-        return mIgnoredBuilder;
-    }
-
-    @Override
-    protected RunnerBuilder suiteMethodBuilder() {
-        return mAndroidSuiteBuilder;
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
deleted file mode 100644
index a2f95e2..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import android.support.test.internal.util.AndroidRunnerParams;
-import android.util.Log;
-import org.junit.runners.model.RunnerBuilder;
-import org.junit.runner.Runner;
-
-/**
- * Extends {@link AndroidRunnerBuilder} in order to provide alternate {@link RunnerBuilder}
- * implementations.
- */
-public class ExtendedAndroidRunnerBuilder extends AndroidRunnerBuilder {
-
-    private static final boolean DEBUG = false;
-
-    private final TestNgRunnerBuilder mTestNgBuilder;
-
-    /**
-     * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
-     */
-    public ExtendedAndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
-        super(runnerParams, false /* CTSv1 filtered out Test suite() classes. */);
-        mTestNgBuilder = new TestNgRunnerBuilder();
-    }
-
-    @Override
-    public Runner runnerForClass(Class<?> testClass) throws Throwable {
-      if (DEBUG) {
-        Log.d("ExAndRunBuild", "runnerForClass: Searching runner for class " + testClass.getName());
-      }
-
-      // Give TestNG tests a chance to participate in the Runner search first.
-      // (Note that the TestNG runner handles log-only runs by itself)
-      Runner runner = mTestNgBuilder.runnerForClass(testClass);
-      if (runner == null) {
-        // Use the normal Runner search mechanism (for Junit tests).
-        runner = super.runnerForClass(testClass);
-      }
-
-      logFoundRunner(runner);
-      return runner;
-    }
-
-    private static void logFoundRunner(Runner runner) {
-      if (DEBUG) {
-        Log.d("ExAndRunBuild", "runnerForClass: Found runner of type " +
-            ((runner == null) ? "<null>" : runner.getClass().getName()));
-      }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java b/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
deleted file mode 100644
index 3ccec3c..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Contains all the changes needed to the {@code android.support.test.internal.runner} classes.
- *
- * <p>As its name suggests {@code android.support.test.internal.runner} are internal classes that
- * are not designed to be extended from outside those packages. This package encapsulates all the
- * workarounds needed to overcome that limitation, from duplicating classes to using reflection.
- * The intention is that these changes are temporary and they (or a better equivalent) will be
- * quickly integrated into the internal classes.
- */
-package com.android.cts.core.runner.support;
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
deleted file mode 100644
index 08c2a5e..0000000
--- a/tests/expectations/knownfailures.txt
+++ /dev/null
@@ -1,282 +0,0 @@
-[
-{
-  description: "Disable ListeningPortsTest",
-  names: [
-    "android.security.cts.ListeningPortsTest"
-  ],
-  bug: 31803630
-},
-{
-  description: "some AlarmClockTests are not robust across different device types",
-  names: [
-    "android.alarmclock.cts.DismissAlarmTest#testAll",
-    "android.alarmclock.cts.SetAlarmTest#testAll",
-    "android.alarmclock.cts.SnoozeAlarmTest#testAll"
-  ],
-  bug: 23776083
-},
-{
-  description: "the UsageStats is not yet stable enough",
-  names: [
-    "android.app.usage.cts.UsageStatsTest"
-  ],
-  bug: 17536113
-},
-{
-  description: "the ConnectivityConstraintTest are not yet stable",
-  names: [
-    "android.jobscheduler.cts.ConnectivityConstraintTest"
-  ],
-  bug: 18117279
-},
-{
-  description: "tests a fragile by nature as they rely on hardcoded behavior",
-  names: [
-    "android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText",
-    "android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverTextExtend"
-  ],
-  bug: 17595050
-},
-{
-  description: "test fails on some devices",
-  names: [
-    "android.dumpsys.cts.DumpsysHostTest#testBatterystatsOutput",
-    "android.dumpsys.cts.DumpsysHostTest#testGfxinfoFramestats"
-  ],
-  bug: 23776893
-},
-{
-  description: "the SSLCertificateSocketFactoryTest often fails because of lack of live internet or short timeout, it should be refactored to do a local server testing",
-  names: [
-    "android.net.cts.SSLCertificateSocketFactoryTest#testCreateSocket",
-    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_bind",
-    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_simple",
-    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_wrapping"
-  ],
-  bug: 18682315
-},
-{
-  description: "the test result are too much dependent on live-internet connection, which for some devices might not exist",
-  names: [
-    "android.net.wifi.cts.NsdManagerTest#testAndroidTestCaseSetupProperly"
-  ],
-  bug: 18680089
-},
-{
-  description: "AudioPolicyBinder tests are not yet robust enough",
-  names: [
-    "android.security.cts.AudioPolicyBinderTest"
-  ],
-  bug: 18461670
-},
-{
-  description: "test not robust",
-  names: [
-    "android.telecom.cts.ExtendedInCallServiceTest#testAddNewOutgoingCallAndThenDisconnect",
-    "android.telecom.cts.RemoteConferenceTest#testRemoteConferenceCallbacks_ConferenceableConnections"
-  ],
-  bug: 23604254
-},
-{
-  description: "tests too flaky",
-  names: [
-    "android.transition.cts.ChangeScrollTest#testChangeScroll"
-  ],
-  bug: 23779020
-},
-{
-  description: "Not all jdwp features are currently supported. These tests will fail",
-  names: [
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch001",
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch002",
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch003",
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch004",
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowLaunchDebugger001#testDebugger002",
-    "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowLaunchDebugger002#testDebugger",
-    "org.apache.harmony.jpda.tests.jdwp.Events.ClassUnloadTest#testClassUnloadEvent",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorContendedEnterTest#testMonitorContendedEnterForClassMatch",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorContendedEnteredTest#testMonitorContendedEnteredForClassMatch",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassExclude",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchExact",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchFirst",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchSecond",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassOnly",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassExclude",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchExact",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchFirst",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchSecond",
-    "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassOnly",
-    "org.apache.harmony.jpda.tests.jdwp.ReferenceType.ClassFileVersionTest#testClassFileVersion001",
-    "org.apache.harmony.jpda.tests.jdwp.ReferenceType.NestedTypesTest#testNestedTypes001",
-    "org.apache.harmony.jpda.tests.jdwp.ThreadReference.StopTest#testStop001",
-    "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.HoldEventsTest#testHoldEvents001",
-    "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.ReleaseEventsTest#testReleaseEvents001"
-  ],
-  bug: 16720689
-},
-{
-  description: "test can only run properly on a user build device when the bug is resolved",
-  names: [
-    "android.appwidget.cts.AppWidgetTest#testAppWidgetProviderCallbacks",
-    "android.appwidget.cts.AppWidgetTest#testBindAppWidget",
-    "android.appwidget.cts.AppWidgetTest#testCollectionWidgets",
-    "android.appwidget.cts.AppWidgetTest#testDeleteHost",
-    "android.appwidget.cts.AppWidgetTest#testDeleteHosts",
-    "android.appwidget.cts.AppWidgetTest#testGetAppWidgetIds",
-    "android.appwidget.cts.AppWidgetTest#testGetAppWidgetInfo",
-    "android.appwidget.cts.AppWidgetTest#testGetAppWidgetOptions",
-    "android.appwidget.cts.AppWidgetTest#testPartiallyUpdateAppWidgetViaWidgetId",
-    "android.appwidget.cts.AppWidgetTest#testPartiallyUpdateAppWidgetViaWidgetIds",
-    "android.appwidget.cts.AppWidgetTest#testTwoAppWidgetProviderCallbacks",
-    "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaComponentName",
-    "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaWidgetId",
-    "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaWidgetIds"
-  ],
-  bug: 17993121
-},
-{
-  description: "permissions for the API previously used in the test has changed, making it impossible to pass",
-  names: [
-    "android.openglperf.cts.GlAppSwitchTest#testGlActivitySwitchingFast",
-    "android.openglperf.cts.GlAppSwitchTest#testGlActivitySwitchingSlow"
-  ],
-  bug: 17394321
-},
-{
-  description: "unexpected failures",
-  names: [
-    "android.openglperf.cts.GlVboPerfTest#testVboWithVaryingIndexBufferNumbers"
-  ],
-  bug: 18091590
-},
-{
-  description: "Test is not yet properly implemented",
-  names: [
-    "android.voicesettings.cts.ZenModeTest#testAll"
-  ],
-  bug: 23238984
-},
-{
-  description: "This test failed on devices that use effect off loading. In addition it uses hidden apis",
-  names: [
-    "android.media.cts.AudioEffectTest#test1_1ConstructorFromUuid"
-  ],
-  bug: 17605875
-},
-{
-  description: "This test failed on hw decoder that doesn't output frame with the configured format.",
-  names: [
-    "android.media.cts.ImageReaderDecoderTest#testHwAVCDecode360pForFlexibleYuv"
-  ],
-  bug: 17144778
-},
-{
-  description: "android.keystore tests will replace these tests",
-  names: [
-    "com.android.org.conscrypt.MacTest#test_getInstance_OpenSSL_ENGINE",
-    "com.android.org.conscrypt.NativeCryptoTest#test_ENGINE_by_id_TestEngine",
-    "com.android.org.conscrypt.SignatureTest#test_getInstance_OpenSSL_ENGINE"
-  ],
-  bug: 18030049
-},
-{
-  description: "The new prepare performance test is not yet passing on all devices",
-  names: [
-    "android.hardware.camera2.cts.SurfaceViewPreviewTest#testPreparePerformance"
-  ],
-  bug: 17989532
-},
-{
-  description: "The timing measurements for preview callbacks are not reliable",
-  names: [
-    "android.hardware.cts.CameraTest#testPreviewFpsRange"
-  ],
-  bug: 23008511
-},
-{
-  description: "Light status bar CTS coming in late",
-  names: [
-    "android.systemui.cts.LightStatusBarTests#testLightStatusBarIcons"
-  ],
-  bug: 23427621
-},
-{
-  description: "tests are not yet ready",
-  names: [
-    "com.android.cts.app.os.OsHostTests#testNonExportedActivities"
-  ],
-  bug: 23779168
-},
-{
-  description: "ConnectivityConstraintTest job scheduler not working.",
-  names: [
-     "android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withWifi",
-     "android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintExecutes_withWifi",
-     "android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withMobile"
-  ],
-  bug: 21262226
-},
-{
-   description: "ConnectivityConstraintTest times out.",
-   names: [
-     "android.jobscheduler.cts.TimingConstraintsTest#testJobParameters_unexpiredDeadline"
-   ],
-   bug: 23144425
-},
-{
-   description: "Video encoding tests are timing out.",
-   names: [
-     "android.media.cts.VideoEncoderTest#testGoogH264FlexArbitraryW",
-     "android.media.cts.VideoEncoderTest#testGoogH264SurfArbitraryW"
-   ],
-   bug: 23827982
-},
-{
-  description: "protected broadcast not working",
-  names: [
-   "android.permission2.cts.ProtectedBroadcastsTest#testSendProtectedBroadcasts"
-  ],
-  bug: 23192492
-},
-{
-  description: "restricted network is not working",
-  names: [
-    "android.net.cts.ConnectivityManagerTest#testRestrictedNetworks"
-  ],
-  bug: 25651805
-},
-{
-  description: "unit testing for MediaPreparer lives within mediastress module",
-  names: [
-    "android.mediastress.cts.preconditions.MediaPreparerTest"
-  ],
-  bug: 25850508
-},
-{
-  description: "Tests for the signature tests should not be in CTS",
-  names: [
-    "android.signature.cts.tests"
-  ],
-  bug: 26150806
-},
-{
-  description: "android.security.cts is using a Non-NDK library, libmedia_jni.so",
-  names: [
-      "android.security.cts.MediaCryptoTest#testMediaCryptoClearKey",
-      "android.security.cts.MediaCryptoTest#testMediaCryptoWidevine"
-  ],
-  bug: 27218502
-},
-{
-  description: "Still investigating this, root cause unknown yet",
-  bug: 27578806,
-  names: ["com.android.cts.cpptools.RunAsHostTest#testRunAs"]
-},
-{
-  description: "Wired headset tests are no longer a requirement per CDD",
-  names: [
-    "android.telecom.cts.WiredHeadsetTest"
-  ],
-  bug: 26149528
-}
-]
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index 5f3923b..fed22df 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/fragment/Android.mk b/tests/fragment/Android.mk
index 1785097..54447f0 100644
--- a/tests/fragment/Android.mk
+++ b/tests/fragment/Android.mk
@@ -33,8 +33,8 @@
     mockito-target-minus-junit4 \
     android-common \
     compatibility-device-util \
-    ctstestrunner \
-    legacy-android-test
+    ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 #LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -44,4 +44,6 @@
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/fragment/AndroidManifest.xml b/tests/fragment/AndroidManifest.xml
index a9fbde2..e1261ef 100644
--- a/tests/fragment/AndroidManifest.xml
+++ b/tests/fragment/AndroidManifest.xml
@@ -29,7 +29,7 @@
         </activity>
         <activity android:name=".LoaderActivity"/>
         <activity android:name=".NewIntentActivity" android:launchMode="singleInstance" />
-        <activity android:name=".NonConfigOnStopActivity"/>
+        <activity android:name=".ConfigOnStopActivity"/>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/fragment/AndroidTest.xml b/tests/fragment/AndroidTest.xml
index 4e563d9..f352ef2 100644
--- a/tests/fragment/AndroidTest.xml
+++ b/tests/fragment/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for app.usage Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/fragment/sdk26/Android.mk b/tests/fragment/sdk26/Android.mk
new file mode 100644
index 0000000..70f7d8c
--- /dev/null
+++ b/tests/fragment/sdk26/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsFragmentTestCasesSdk26
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-test
+
+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_SDK_VERSION := 26
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/fragment/sdk26/AndroidManifest.xml b/tests/fragment/sdk26/AndroidManifest.xml
new file mode 100644
index 0000000..be344b3
--- /dev/null
+++ b/tests/fragment/sdk26/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.fragment.cts.sdk26">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".NonConfigOnStopActivity"/>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.fragment.cts.sdk26"
+                     android:label="CTS tests of android.app Fragments" />
+</manifest>
+
diff --git a/tests/fragment/sdk26/AndroidTest.xml b/tests/fragment/sdk26/AndroidTest.xml
new file mode 100644
index 0000000..84fab15
--- /dev/null
+++ b/tests/fragment/sdk26/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="Configuration for app.usage Tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsFragmentTestCasesSdk26.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.fragment.cts.sdk26" />
+        <option name="runtime-hint" value="13m" />
+    </test>
+</configuration>
diff --git a/tests/fragment/sdk26/src/android/fragment/cts/sdk26/FragmentManagerNonConfigTest.java b/tests/fragment/sdk26/src/android/fragment/cts/sdk26/FragmentManagerNonConfigTest.java
new file mode 100644
index 0000000..a7859b6
--- /dev/null
+++ b/tests/fragment/sdk26/src/android/fragment/cts/sdk26/FragmentManagerNonConfigTest.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 android.fragment.cts.sdk26;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Debug;
+import android.support.test.filters.MediumTest;
+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;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentManagerNonConfigTest {
+
+    @Rule
+    public ActivityTestRule<NonConfigOnStopActivity> mActivityRule =
+            new ActivityTestRule<>(NonConfigOnStopActivity.class);
+
+    /**
+     * When a fragment is added during onStop(), it shouldn't show up in non-config
+     * state when restored for apps targeting SDKs before P.
+     */
+    @Test
+    public void nonConfigStop() throws Throwable {
+        // Trigger activity relaunch.
+        final NonConfigOnStopActivity activity = mActivityRule.getActivity();
+        activity.sDestroyed = new CountDownLatch(1);
+        activity.sResumed = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> activity.recreate());
+
+        // Wait for activity to be relaunched.
+        assertTrue(activity.sDestroyed.await(1, TimeUnit.SECONDS));
+        assertTrue(activity.sResumed.await(1, TimeUnit.SECONDS));
+
+        // A fragment was added in onStop(), but we shouldn't see it here...
+        final NonConfigOnStopActivity recreatedActivity = NonConfigOnStopActivity.sActivity;
+        assertTrue(recreatedActivity.getFragmentManager().getFragments().isEmpty());
+    }
+}
\ No newline at end of file
diff --git a/tests/fragment/sdk26/src/android/fragment/cts/sdk26/NonConfigOnStopActivity.java b/tests/fragment/sdk26/src/android/fragment/cts/sdk26/NonConfigOnStopActivity.java
new file mode 100644
index 0000000..b3922ca
--- /dev/null
+++ b/tests/fragment/sdk26/src/android/fragment/cts/sdk26/NonConfigOnStopActivity.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 android.fragment.cts.sdk26;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+
+import java.util.concurrent.CountDownLatch;
+
+public class NonConfigOnStopActivity extends Activity {
+    static NonConfigOnStopActivity sActivity;
+    static CountDownLatch sDestroyed;
+    static CountDownLatch sResumed;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        getFragmentManager()
+                .beginTransaction()
+                .add(new RetainedFragment(), "1")
+                .commitNowAllowingStateLoss();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+
+    public static class RetainedFragment extends Fragment {
+        public RetainedFragment() {
+            setRetainInstance(true);
+        }
+    }
+}
diff --git a/tests/fragment/src/android/fragment/cts/ConfigOnStopActivity.java b/tests/fragment/src/android/fragment/cts/ConfigOnStopActivity.java
new file mode 100644
index 0000000..eeff459
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/ConfigOnStopActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.fragment.cts;
+
+import android.app.Fragment;
+
+public class ConfigOnStopActivity extends RecreatedActivity {
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        getFragmentManager()
+                .beginTransaction()
+                .add(new RetainedFragment(), "1")
+                .commitNowAllowingStateLoss();
+    }
+
+    public static class RetainedFragment extends Fragment {
+        public RetainedFragment() {
+            setRetainInstance(true);
+        }
+    }
+}
diff --git a/tests/fragment/src/android/fragment/cts/FragmentManagerConfigTest.java b/tests/fragment/src/android/fragment/cts/FragmentManagerConfigTest.java
new file mode 100644
index 0000000..519588c
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/FragmentManagerConfigTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.fragment.cts;
+
+import static org.junit.Assert.assertFalse;
+
+import android.support.test.filters.MediumTest;
+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;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentManagerConfigTest {
+
+    @Rule
+    public ActivityTestRule<ConfigOnStopActivity> mActivityRule =
+            new ActivityTestRule<>(ConfigOnStopActivity.class);
+
+    /**
+     * When a fragment is added during onStop(), it should show up in non-config
+     * state when restored for apps targeting SDKs P+.
+     */
+    @Test
+    public void configStop() throws Throwable {
+        ConfigOnStopActivity activity = FragmentTestUtil.recreateActivity(mActivityRule,
+                mActivityRule.getActivity());
+
+        // A fragment was added in onStop(), but we shouldn't see it here...
+        assertFalse(activity.getFragmentManager().getFragments().isEmpty());
+    }
+
+}
\ No newline at end of file
diff --git a/tests/fragment/src/android/fragment/cts/FragmentManagerNonConfigTest.java b/tests/fragment/src/android/fragment/cts/FragmentManagerNonConfigTest.java
deleted file mode 100644
index 1b28290..0000000
--- a/tests/fragment/src/android/fragment/cts/FragmentManagerNonConfigTest.java
+++ /dev/null
@@ -1,51 +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.fragment.cts;
-
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentManagerNonConfigTest {
-
-    @Rule
-    public ActivityTestRule<NonConfigOnStopActivity> mActivityRule =
-            new ActivityTestRule<>(NonConfigOnStopActivity.class);
-
-    /**
-     * When a fragment is added during onStop(), it shouldn't show up in non-config
-     * state when restored.
-     */
-    @Test
-    public void nonConfigStop() throws Throwable {
-        NonConfigOnStopActivity activity = FragmentTestUtil.recreateActivity(mActivityRule,
-                mActivityRule.getActivity());
-
-        // A fragment was added in onStop(), but we shouldn't see it here...
-        assertTrue(activity.getFragmentManager().getFragments().isEmpty());
-    }
-
-}
\ No newline at end of file
diff --git a/tests/fragment/src/android/fragment/cts/NonConfigOnStopActivity.java b/tests/fragment/src/android/fragment/cts/NonConfigOnStopActivity.java
deleted file mode 100644
index a93b616..0000000
--- a/tests/fragment/src/android/fragment/cts/NonConfigOnStopActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.fragment.cts;
-
-import android.app.Fragment;
-
-public class NonConfigOnStopActivity extends RecreatedActivity {
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        getFragmentManager()
-                .beginTransaction()
-                .add(new RetainedFragment(), "1")
-                .commitNowAllowingStateLoss();
-    }
-
-    public static class RetainedFragment extends Fragment {
-        public RetainedFragment() {
-            setRetainInstance(true);
-        }
-    }
-}
diff --git a/hostsidetests/services/Android.mk b/tests/framework/Android.mk
similarity index 100%
rename from hostsidetests/services/Android.mk
rename to tests/framework/Android.mk
diff --git a/hostsidetests/services/Android.mk b/tests/framework/base/Android.mk
similarity index 100%
copy from hostsidetests/services/Android.mk
copy to tests/framework/base/Android.mk
diff --git a/tests/framework/base/activitymanager/Android.mk b/tests/framework/base/activitymanager/Android.mk
new file mode 100644
index 0000000..797610a
--- /dev/null
+++ b/tests/framework/base/activitymanager/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 optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceTestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-named-files-under,Components.java, app) \
+    $(call all-named-files-under,Components.java, app27) \
+    $(call all-named-files-under,Components.java, appDebuggable) \
+    $(call all-named-files-under,Components.java, appDeprecatedSdk) \
+    $(call all-named-files-under,Components.java, appDisplaySize) \
+    $(call all-named-files-under,Components.java, appPrereleaseSdk) \
+    $(call all-named-files-under,Components.java, appSecondUid) \
+    $(call all-named-files-under,Components.java, appThirdUid) \
+    $(call all-named-files-under,Components.java, translucentapp) \
+    $(call all-named-files-under,Components.java, translucentappsdk26) \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    cts-amwm-util \
+    cts-display-service-app-util
+
+LOCAL_CTS_TEST_PACKAGE := android.server
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
new file mode 100644
index 0000000..1b1a666
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?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.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" />
+
+    <application android:label="CtsActivityManagerDeviceTestCases">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MaxAspectRatioActivity"
+            android:label="MaxAspectRatioActivity"
+            android:maxAspectRatio="1.0"
+            android:resizeableActivity="false" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MetaDataMaxAspectRatioActivity"
+            android:label="MetaDataMaxAspectRatioActivity"
+            android:resizeableActivity="false">
+            <meta-data
+                android:name="android.max_aspect"
+                android:value="1.0" />
+        </activity>
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MaxAspectRatioResizeableActivity"
+            android:label="MaxAspectRatioResizeableActivity"
+            android:maxAspectRatio="1.0"
+            android:resizeableActivity="true" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MaxAspectRatioUnsetActivity"
+            android:label="MaxAspectRatioUnsetActivity"
+            android:resizeableActivity="false" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$FirstActivity" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SecondActivity"/>
+
+        <activity
+                android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$LaunchForResultActivity"/>
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ResultActivity"/>
+
+        <activity android:name="android.server.am.StartActivityTests$TestActivity2" />
+
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests of ActivityManager"
+        android:targetPackage="android.server.cts.am" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/AndroidTest.xml b/tests/framework/base/activitymanager/AndroidTest.xml
new file mode 100644
index 0000000..a2c6fa4
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?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 ActivityManager 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="CtsActivityManagerDeviceTestCases.apk" />
+        <option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
+        <option name="test-file-name" value="CtsDeviceServicesTestApp27.apk" />
+        <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
+        <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
+        <option name="test-file-name" value="CtsDeviceDebuggableApp.apk" />
+        <option name="test-file-name" value="CtsDeviceDeprecatedSdkApp.apk" />
+        <option name="test-file-name" value="CtsDeviceDisplaySizeApp.apk" />
+        <option name="test-file-name" value="CtsDevicePrereleaseSdkApp.apk" />
+        <option name="test-file-name" value="CtsDisplayServiceApp.apk" />
+        <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
+        <option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- 120 seconds screen off timeout to prevent device sleep while long running test. -->
+        <option name="run-command" value="settings put system screen_off_timeout 120000" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.cts.am"/>
+        <option name="runtime-hint" value="1h"/>
+    </test>
+</configuration>
diff --git a/tests/framework/base/activitymanager/app/Android.mk b/tests/framework/base/activitymanager/app/Android.mk
new file mode 100644
index 0000000..7075030
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/Android.mk
@@ -0,0 +1,36 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    cts-am-app-base \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+                   ../../../../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceServicesTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/app/AndroidManifest.xml b/tests/framework/base/activitymanager/app/AndroidManifest.xml
new file mode 100755
index 0000000..589c683
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/AndroidManifest.xml
@@ -0,0 +1,411 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="android.server.am">
+
+    <!-- virtual display test permissions -->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+
+    <application>
+        <activity android:name=".TestActivity"
+                android:resizeableActivity="true"
+                android:supportsPictureInPicture="true"
+                android:exported="true"
+        />
+        <activity android:name=".TestActivityWithSameAffinity"
+                android:resizeableActivity="true"
+                android:supportsPictureInPicture="true"
+                android:exported="true"
+                android:taskAffinity="nobody.but.PipActivitySameAffinity"
+        />
+        <activity android:name=".TranslucentTestActivity"
+                android:resizeableActivity="true"
+                android:supportsPictureInPicture="true"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                android:theme="@style/Theme.Transparent" />
+        <activity android:name=".VrTestActivity"
+                android:resizeableActivity="true"
+                android:exported="true"
+        />
+        <activity android:name=".ResumeWhilePausingActivity"
+                android:allowEmbedded="true"
+                android:resumeWhilePausing="true"
+                android:taskAffinity=""
+                android:exported="true"
+        />
+        <activity android:name=".ResizeableActivity"
+                android:resizeableActivity="true"
+                android:allowEmbedded="true"
+                android:exported="true"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
+        />
+        <activity android:name=".NonResizeableActivity"
+                android:resizeableActivity="false"
+                android:exported="true"
+        />
+        <activity android:name=".DockedActivity"
+                android:resizeableActivity="true"
+                android:exported="true"
+                android:taskAffinity="nobody.but.DockedActivity"
+        />
+        <activity android:name=".TranslucentActivity"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar"
+            android:resizeableActivity="true"
+            android:taskAffinity="nobody.but.TranslucentActivity"
+            android:exported="true"
+        />
+        <activity android:name=".DialogWhenLargeActivity"
+                android:exported="true"
+                android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
+        />
+        <activity android:name=".NoRelaunchActivity"
+                android:resizeableActivity="true"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
+                android:exported="true"
+                android:taskAffinity="nobody.but.NoRelaunchActivity"
+        />
+        <activity android:name=".SlowCreateActivity"
+                android:resizeableActivity="true"
+                android:exported="true"
+        />
+        <activity android:name=".LaunchingActivity"
+                android:resizeableActivity="true"
+                android:exported="true"
+                android:taskAffinity="nobody.but.LaunchingActivity"
+        />
+        <activity android:name=".AltLaunchingActivity"
+                android:resizeableActivity="true"
+                android:exported="true"
+                android:taskAffinity="nobody.but.LaunchingActivity"
+        />
+        <activity android:name=".PipActivity"
+                android:resizeableActivity="false"
+                android:supportsPictureInPicture="true"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                android:exported="true"
+                android:taskAffinity="nobody.but.PipActivity"
+        />
+        <activity android:name=".PipActivity2"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+                  android:taskAffinity="nobody.but.PipActivity2"
+        />
+        <activity android:name=".PipOnStopActivity"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+                  android:taskAffinity="nobody.but.PipOnStopActivity"
+        />
+        <activity android:name=".PipActivityWithSameAffinity"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+                  android:taskAffinity="nobody.but.PipActivitySameAffinity"
+        />
+        <activity android:name=".AlwaysFocusablePipActivity"
+                  android:theme="@style/Theme.Transparent"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  androidprv:alwaysFocusable="true"
+                  android:exported="true"
+                  android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
+        />
+        <activity android:name=".LaunchIntoPinnedStackPipActivity"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  androidprv:alwaysFocusable="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+        />
+        <activity android:name=".LaunchPipOnPipActivity"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+        />
+        <activity android:name=".LaunchEnterPipActivity"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  androidprv:alwaysFocusable="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true"
+        />
+        <activity android:name=".FreeformActivity"
+                  android:resizeableActivity="true"
+                  android:taskAffinity="nobody.but.FreeformActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".TopLeftLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="240dp"
+                          android:defaultHeight="160dp"
+                          android:gravity="top|left"
+                          android:minWidth="100dp"
+                          android:minHeight="80dp"
+                  />
+        </activity>
+        <activity android:name=".TopRightLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="25%"
+                          android:defaultHeight="35%"
+                          android:gravity="top|right"
+                          android:minWidth="90dp"
+                          android:minHeight="80dp"
+                  />
+        </activity>
+        <activity android:name=".BottomLeftLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="25%"
+                          android:defaultHeight="35%"
+                          android:gravity="bottom|left"
+                          android:minWidth="90dp"
+                          android:minHeight="80dp"
+                  />
+        </activity>
+        <activity android:name=".BottomRightLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="240dp"
+                          android:defaultHeight="160dp"
+                          android:gravity="bottom|right"
+                          android:minWidth="100dp"
+                          android:minHeight="80dp"
+                  />
+        </activity>
+        <activity android:name=".TurnScreenOnActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".TurnScreenOnDismissKeyguardActivity"
+            android:exported="true"
+        />
+        <activity android:name=".SingleTaskActivity"
+            android:exported="true"
+            android:launchMode="singleTask"
+        />
+        <activity android:name=".SingleInstanceActivity"
+            android:exported="true"
+            android:launchMode="singleInstance"
+        />
+        <activity android:name=".TrampolineActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.NoDisplay"
+        />
+        <activity android:name=".BroadcastReceiverActivity"
+                  android:resizeableActivity="true"
+                  android:exported="true"
+        />
+        <activity-alias android:enabled="true"
+                android:exported="true"
+                android:name=".EntryPointAliasActivity"
+                android:targetActivity=".TrampolineActivity" >
+        </activity-alias>
+        <activity android:name=".BottomActivity"
+                  android:exported="true"
+                  android:theme="@style/NoPreview"
+        />
+        <activity android:name=".TopActivity"
+                  android:process=".top_process"
+                  android:exported="true"
+                  android:theme="@style/NoPreview"
+        />
+        <activity android:name=".TranslucentTopActivity"
+                  android:process=".top_process"
+                  android:exported="true"
+                  android:theme="@style/TranslucentTheme"
+        />
+        <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
+             animation background being tested is not used in translucent activities. -->
+        <activity android:name=".AnimationTestActivity"
+                  android:theme="@style/OpaqueTheme"
+                  android:exported="true"
+        />
+        <activity android:name=".VirtualDisplayActivity"
+                  android:resizeableActivity="true"
+                  android:exported="true"
+                  android:taskAffinity="nobody.but.VirtualDisplayActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+        />
+        <activity android:name=".ShowWhenLockedActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".ShowWhenLockedWithDialogActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".ShowWhenLockedDialogActivity"
+            android:exported="true"
+            android:theme="@android:style/Theme.Material.Dialog"
+        />
+        <activity android:name=".ShowWhenLockedTranslucentActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.Translucent"
+        />
+        <activity android:name=".DismissKeyguardActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".DismissKeyguardMethodActivity"
+            android:exported="true"
+        />
+        <activity android:name=".WallpaperActivity"
+            android:exported="true"
+            android:theme="@style/WallpaperTheme"
+        />
+        <activity android:name=".KeyguardLockActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".LogConfigurationActivity"
+            android:exported="true"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+        />
+        <activity android:name=".PortraitOrientationActivity"
+                  android:exported="true"
+                  android:screenOrientation="portrait"
+                  android:documentLaunchMode="always"
+        />
+        <activity android:name=".LandscapeOrientationActivity"
+                  android:exported="true"
+                  android:screenOrientation="landscape"
+                  android:documentLaunchMode="always"
+        />
+        <activity android:name=".MoveTaskToBackActivity"
+                  android:exported="true"
+                  android:launchMode="singleInstance"
+        />
+        <activity android:name=".NightModeActivity"
+                  android:exported="true"
+                  android:configChanges="uiMode"
+        />
+        <activity android:name=".FontScaleActivity"
+                  android:exported="true"
+        />
+        <activity android:name=".FontScaleNoRelaunchActivity"
+                  android:exported="true"
+                  android:configChanges="fontScale"
+        />
+        <receiver
+            android:name=".LaunchBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.server.am.LAUNCH_BROADCAST_ACTION"/>
+            </intent-filter>
+        </receiver>
+
+        <activity android:name=".AssistantActivity"
+            android:exported="true" />
+        <activity android:name=".TranslucentAssistantActivity"
+            android:exported="true"
+            android:theme="@style/Theme.Transparent" />
+        <activity android:name=".LaunchAssistantActivityFromSession"
+            android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
+            android:exported="true" />
+        <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
+            android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
+            android:exported="true" />
+
+        <service android:name=".AssistantVoiceInteractionService"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:exported="true">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_service" />
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService" />
+            </intent-filter>
+        </service>
+
+        <service android:name=".AssistantVoiceInteractionSessionService"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:exported="true" />
+
+        <activity android:name=".SplashscreenActivity"
+            android:taskAffinity="nobody.but.SplashscreenActivity"
+            android:theme="@style/SplashscreenTheme"
+            android:exported="true" />
+
+
+        <activity android:name=".SwipeRefreshActivity"
+                  android:exported="true" />
+
+        <activity android:name=".NoHistoryActivity"
+                  android:noHistory="true"
+                  android:exported="true" />
+
+        <activity android:name=".ShowWhenLockedAttrActivity"
+                  android:showWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
+                  android:showWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
+                  android:showWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".TurnScreenOnAttrActivity"
+                  android:turnScreenOn="true"
+                  android:exported="true" />
+
+        <activity android:name=".TurnScreenOnShowOnLockActivity"
+                  android:showWhenLocked="true"
+                  android:turnScreenOn="true"
+                  android:exported="true" />
+
+        <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".TurnScreenOnSingleTaskActivity"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"
+                  android:exported="true"
+                  android:launchMode="singleTask" />
+
+        <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
+                  android:turnScreenOn="true"
+                  android:exported="true"/>
+
+        <activity android:name=".TurnScreenOnWithRelayoutActivity"
+                  android:exported="true"/>
+
+        <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
+                 android:exported="true"
+                 android:enabled="true"
+                 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+           <intent-filter>
+               <action android:name="android.service.vr.VrListenerService" />
+           </intent-filter>
+        </service>
+    </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/anim/animation_with_background.xml b/tests/framework/base/activitymanager/app/res/anim/animation_with_background.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/anim/animation_with_background.xml
rename to tests/framework/base/activitymanager/app/res/anim/animation_with_background.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/assistant.xml b/tests/framework/base/activitymanager/app/res/layout/assistant.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/assistant.xml
rename to tests/framework/base/activitymanager/app/res/layout/assistant.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/floating.xml b/tests/framework/base/activitymanager/app/res/layout/floating.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/floating.xml
rename to tests/framework/base/activitymanager/app/res/layout/floating.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/font_scale.xml b/tests/framework/base/activitymanager/app/res/layout/font_scale.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/font_scale.xml
rename to tests/framework/base/activitymanager/app/res/layout/font_scale.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/main.xml b/tests/framework/base/activitymanager/app/res/layout/main.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/main.xml
rename to tests/framework/base/activitymanager/app/res/layout/main.xml
diff --git a/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml b/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml
new file mode 100644
index 0000000..3c249bf
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml
@@ -0,0 +1,21 @@
+<?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
+  -->
+
+<android.server.am.LifecycleLogView xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+</android.server.am.LifecycleLogView>
\ No newline at end of file
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml b/tests/framework/base/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
rename to tests/framework/base/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/task_overlay.xml b/tests/framework/base/activitymanager/app/res/layout/task_overlay.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/task_overlay.xml
rename to tests/framework/base/activitymanager/app/res/layout/task_overlay.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/translucent.xml b/tests/framework/base/activitymanager/app/res/layout/translucent.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/translucent.xml
rename to tests/framework/base/activitymanager/app/res/layout/translucent.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/virtual_display_layout.xml b/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/virtual_display_layout.xml
rename to tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/colors.xml b/tests/framework/base/activitymanager/app/res/values/colors.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/colors.xml
rename to tests/framework/base/activitymanager/app/res/values/colors.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/styles.xml b/tests/framework/base/activitymanager/app/res/values/styles.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/styles.xml
rename to tests/framework/base/activitymanager/app/res/values/styles.xml
diff --git a/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml b/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml
new file mode 100644
index 0000000..f586037
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.server.am.AssistantVoiceInteractionSessionService"
+    android:recognitionService="android.server.am.AssistantVoiceInteractionSessionService"
+    android:supportsAssist="true" />
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java
new file mode 100644
index 0000000..3d7aa8a
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * An additional launching activity used for alternating between two activities.
+ */
+public class AltLaunchingActivity extends LaunchingActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java
new file mode 100644
index 0000000..0e74977
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+
+public class AlwaysFocusablePipActivity extends Activity {
+
+    static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
+        final Intent intent = new Intent(caller, AlwaysFocusablePipActivity.class);
+
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK);
+        if (newTask) {
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        }
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchBounds(new Rect(0, 0, 500, 500));
+        options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+        caller.startActivity(intent, options.toBundle());
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java
new file mode 100644
index 0000000..987ac87
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+public class AnimationTestActivity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        overridePendingTransition(R.anim.animation_with_background, -1);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java
new file mode 100644
index 0000000..82b0218
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.Components.AssistantActivity.EXTRA_ASSISTANT_DISPLAY_ID;
+import static android.server.am.Components.AssistantActivity.EXTRA_ENTER_PIP;
+import static android.server.am.Components.AssistantActivity.EXTRA_FINISH_SELF;
+import static android.server.am.Components.AssistantActivity.EXTRA_LAUNCH_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class AssistantActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set the layout
+        setContentView(R.layout.assistant);
+
+        // Launch the new activity if requested
+        if (getIntent().hasExtra(EXTRA_LAUNCH_NEW_TASK)) {
+            final ComponentName launchActivity = ComponentName.unflattenFromString(
+                    getIntent().getStringExtra(EXTRA_LAUNCH_NEW_TASK));
+            final Intent launchIntent = new Intent();
+            launchIntent.setComponent(launchActivity)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            if (getIntent().hasExtra(EXTRA_ASSISTANT_DISPLAY_ID)) {
+                ActivityOptions displayOptions = ActivityOptions.makeBasic();
+                displayOptions.setLaunchDisplayId(Integer.parseInt(getIntent()
+                        .getStringExtra(EXTRA_ASSISTANT_DISPLAY_ID)));
+                startActivity(launchIntent, displayOptions.toBundle());
+            } else {
+                startActivity(launchIntent);
+            }
+        }
+
+        // Enter pip if requested
+        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+            try {
+                enterPictureInPictureMode();
+            } catch (IllegalStateException e) {
+                finish();
+                return;
+            }
+        }
+
+        // Finish this activity if requested
+        if (getIntent().hasExtra(EXTRA_FINISH_SELF)) {
+            finish();
+        }
+    }
+
+    /**
+     * Launches a new instance of the AssistantActivity directly into the assistant stack.
+     */
+    static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
+        final Intent intent = new Intent(caller, AssistantActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
+        caller.startActivity(intent, options.toBundle());
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java
new file mode 100644
index 0000000..b772ce2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionService;
+import android.util.Log;
+
+public class AssistantVoiceInteractionService extends VoiceInteractionService {
+
+    private static final String TAG = AssistantVoiceInteractionService.class.getSimpleName();
+
+    private boolean mReady;
+
+    @Override
+    public void onReady() {
+        super.onReady();
+        mReady = true;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (!isActiveService(this, new ComponentName(this, getClass()))) {
+            Log.wtf(TAG, "**** Not starting AssistantVoiceInteractionService because" +
+                    " it is not set as the current voice interaction service");
+            stopSelf();
+            return START_NOT_STICKY;
+        }
+        if (mReady) {
+            Bundle extras = intent.getExtras() != null ? intent.getExtras() : new Bundle();
+            showSession(extras, 0);
+        }
+        return START_NOT_STICKY;
+    }
+
+    /**
+     * Starts the assistant voice interaction service, which initiates a new session that starts
+     * the assistant activity.
+     */
+    public static void launchAssistantActivity(Context context, Bundle extras) {
+        Intent i = new Intent(context, AssistantVoiceInteractionService.class);
+        if (extras != null) {
+            i.putExtras(extras);
+        }
+        context.startService(i);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java
new file mode 100644
index 0000000..81ed202
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class AssistantVoiceInteractionSessionService extends VoiceInteractionSessionService {
+
+    @Override
+    public VoiceInteractionSession onNewSession(Bundle args) {
+        return new VoiceInteractionSession(this) {
+            @Override
+            public void onPrepareShow(Bundle args, int showFlags) {
+                setUiEnabled(false);
+            }
+
+            @Override
+            public void onShow(Bundle args, int showFlags) {
+                Intent i = new Intent(AssistantVoiceInteractionSessionService.this,
+                        AssistantActivity.class);
+                if (args != null) {
+                    i.putExtras(args);
+                }
+                startAssistantActivity(i);
+            }
+        };
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java
new file mode 100644
index 0000000..de56159
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+public class BottomActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = BottomActivity.class.getSimpleName();
+
+    private int mStopDelay;
+    private View mFloatingWindow;
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+        if (useWallpaper) {
+            setTheme(R.style.WallpaperTheme);
+        }
+        setContentView(R.layout.main);
+
+        // Delayed stop is for simulating a case where resume happens before
+        // activityStopped() is received by AM, and the transition starts without
+        // going through fully stopped state (see b/30255354).
+        // If enabled, we stall onStop() of BottomActivity, open TopActivity but make
+        // it finish before onStop() ends. This will cause BottomActivity to resume before
+        // it notifies AM of activityStopped(). We also add a second window of
+        // TYPE_BASE_APPLICATION, so that the transition animation could start earlier.
+        // Otherwise the main window has to relayout to visible first and the error won't occur.
+        // Note that if the test fails, we shouldn't try to change the app here to make
+        // it pass. The test app is artificially made to simulate an failure case, but
+        // it's not doing anything wrong.
+        mStopDelay = getIntent().getIntExtra("STOP_DELAY", 0);
+        if (mStopDelay > 0) {
+            LayoutInflater inflater = getLayoutInflater();
+            mFloatingWindow = inflater.inflate(R.layout.floating, null);
+
+            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+            params.setTitle("Floating");
+            getWindowManager().addView(mFloatingWindow, params);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        Log.d(TAG, "onResume() E");
+        super.onResume();
+
+        if (mStopDelay > 0) {
+            // Refresh floating window
+            Log.d(TAG, "Scheuling invalidate Floating Window in onResume()");
+            mFloatingWindow.invalidate();
+        }
+
+        Log.d(TAG, "onResume() X");
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (mStopDelay > 0) {
+            try {
+                Log.d(TAG, "Stalling onStop() by " + mStopDelay + " ms...");
+                Thread.sleep(mStopDelay);
+            } catch(InterruptedException e) {}
+
+            // Refresh floating window
+            Log.d(TAG, "Scheuling invalidate Floating Window in onStop()");
+            mFloatingWindow.invalidate();
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java
new file mode 100644
index 0000000..3c4fcb0
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class BottomLeftLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java
new file mode 100644
index 0000000..8d0e1e2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class BottomRightLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
new file mode 100644
index 0000000..282d750
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that registers broadcast receiver .
+ */
+public class BroadcastReceiverActivity extends Activity {
+
+    public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+    private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
+
+    private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
+
+        registerReceiver(mBroadcastReceiver, broadcastFilter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    public class TestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final Bundle extras = intent.getExtras();
+            Log.i(TAG, "onReceive: extras=" + extras);
+
+            if (extras == null) {
+                return;
+            }
+            if (extras.getBoolean("finish")) {
+                finish();
+            }
+            if (extras.getBoolean("moveToBack")) {
+                moveTaskToBack(true);
+            }
+            if (extras.containsKey("orientation")) {
+                setRequestedOrientation(extras.getInt("orientation"));
+            }
+            if (extras.getBoolean("dismissKeyguard")) {
+                getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
+            }
+            if (extras.getBoolean("dismissKeyguardMethod")) {
+                getSystemService(KeyguardManager.class).requestDismissKeyguard(
+                        BroadcastReceiverActivity.this, new KeyguardDismissLoggerCallback(context));
+            }
+
+            ActivityLauncher.launchActivityFromExtras(BroadcastReceiverActivity.this, extras);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/Components.java b/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
new file mode 100644
index 0000000..a9973ba
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
@@ -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.server.am;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+    public static final ComponentName ALT_LAUNCHING_ACTIVITY = component("AltLaunchingActivity");
+    public static final ComponentName ALWAYS_FOCUSABLE_PIP_ACTIVITY =
+            component("AlwaysFocusablePipActivity");
+    public static final ComponentName ANIMATION_TEST_ACTIVITY = component("AnimationTestActivity");
+    public static final ComponentName ASSISTANT_ACTIVITY = component("AssistantActivity");
+    public static final ComponentName BOTTOM_ACTIVITY = component("BottomActivity");
+    public static final ComponentName BOTTOM_LEFT_LAYOUT_ACTIVITY =
+            component("BottomLeftLayoutActivity");
+    public static final ComponentName BOTTOM_RIGHT_LAYOUT_ACTIVITY =
+            component("BottomRightLayoutActivity");
+    public static final ComponentName BROADCAST_RECEIVER_ACTIVITY =
+            component("BroadcastReceiverActivity");
+    public static final ComponentName DIALOG_WHEN_LARGE_ACTIVITY =
+            component("DialogWhenLargeActivity");
+    public static final ComponentName DISMISS_KEYGUARD_ACTIVITY =
+            component("DismissKeyguardActivity");
+    public static final ComponentName DISMISS_KEYGUARD_METHOD_ACTIVITY =
+            component("DismissKeyguardMethodActivity");
+    public static final ComponentName DOCKED_ACTIVITY = component("DockedActivity");
+    public static final ComponentName ENTRY_POINT_ALIAS_ACTIVITY =
+            component("EntryPointAliasActivity");
+    public static final ComponentName FONT_SCALE_ACTIVITY = component("FontScaleActivity");
+    public static final ComponentName FONT_SCALE_NO_RELAUNCH_ACTIVITY =
+            component("FontScaleNoRelaunchActivity");
+    public static final ComponentName FREEFORM_ACTIVITY = component("FreeformActivity");
+    public static final ComponentName KEYGUARD_LOCK_ACTIVITY = component("KeyguardLockActivity");
+    public static final ComponentName LANDSCAPE_ORIENTATION_ACTIVITY =
+            component("LandscapeOrientationActivity");
+    public static final ComponentName LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION =
+            component("LaunchAssistantActivityFromSession");
+    public static final ComponentName LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK  =
+            component("LaunchAssistantActivityIntoAssistantStack");
+    public static final ComponentName LAUNCH_ENTER_PIP_ACTIVITY =
+            component("LaunchEnterPipActivity");
+    public static final ComponentName LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
+            component("LaunchIntoPinnedStackPipActivity");
+    public static final ComponentName LAUNCH_PIP_ON_PIP_ACTIVITY =
+            component("LaunchPipOnPipActivity");
+    public static final ComponentName LAUNCHING_ACTIVITY = component("LaunchingActivity");
+    public static final ComponentName LOG_CONFIGURATION_ACTIVITY =
+            component("LogConfigurationActivity");
+    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_HISTORY_ACTIVITY = component("NoHistoryActivity");
+    public static final ComponentName NO_RELAUNCH_ACTIVITY = component("NoRelaunchActivity");
+    public static final ComponentName NON_RESIZEABLE_ACTIVITY = component("NonResizeableActivity");
+    public static final ComponentName PIP_ACTIVITY = component("PipActivity");
+    public static final ComponentName PIP_ACTIVITY2 = component("PipActivity2");
+    public static final ComponentName PIP_ACTIVITY_WITH_SAME_AFFINITY =
+            component("PipActivityWithSameAffinity");
+    public static final ComponentName PIP_ON_STOP_ACTIVITY = component("PipOnStopActivity");
+    public static final ComponentName PORTRAIT_ORIENTATION_ACTIVITY =
+            component("PortraitOrientationActivity");
+    public static final ComponentName RESIZEABLE_ACTIVITY = component("ResizeableActivity");
+    public static final ComponentName RESUME_WHILE_PAUSING_ACTIVITY =
+            component("ResumeWhilePausingActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY =
+            component("ShowWhenLockedActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_ATTR_ACTIVITY =
+            component("ShowWhenLockedAttrActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY =
+            component("ShowWhenLockedAttrRemoveAttrActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_DIALOG_ACTIVITY =
+            component("ShowWhenLockedDialogActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY =
+            component("ShowWhenLockedTranslucentActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY =
+            component("ShowWhenLockedWithDialogActivity");
+    public static final ComponentName SINGLE_INSTANCE_ACTIVITY =
+            component("SingleInstanceActivity");
+    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 TOP_ACTIVITY = component("TopActivity");
+    public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY =
+            component("TestActivityWithSameAffinity");
+    public static final ComponentName TOP_LEFT_LAYOUT_ACTIVITY = component("TopLeftLayoutActivity");
+    public static final ComponentName TOP_RIGHT_LAYOUT_ACTIVITY =
+            component("TopRightLayoutActivity");
+    public static final ComponentName TRANSLUCENT_ACTIVITY = component("TranslucentActivity");
+    public static final ComponentName TRANSLUCENT_ASSISTANT_ACTIVITY =
+            component("TranslucentAssistantActivity");
+    public static final ComponentName TRANSLUCENT_TOP_ACTIVITY =
+            component("TranslucentTopActivity");
+    public static final ComponentName TRANSLUCENT_TEST_ACTIVITY =
+            component("TranslucentTestActivity");
+    public static final ComponentName TURN_SCREEN_ON_ACTIVITY = component("TurnScreenOnActivity");
+    public static final ComponentName TURN_SCREEN_ON_ATTR_ACTIVITY =
+            component("TurnScreenOnAttrActivity");
+    public static final ComponentName TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY =
+            component("TurnScreenOnAttrDismissKeyguardActivity");
+    public static final ComponentName TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY =
+            component("TurnScreenOnAttrRemoveAttrActivity");
+    public static final ComponentName TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY =
+            component("TurnScreenOnDismissKeyguardActivity");
+    public static final ComponentName TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY =
+            component("TurnScreenOnShowOnLockActivity");
+    public static final ComponentName TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY =
+            component("TurnScreenOnSingleTaskActivity");
+    public static final ComponentName TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
+            component("TurnScreenOnWithRelayoutActivity");
+    public static final ComponentName VIRTUAL_DISPLAY_ACTIVITY =
+            component("VirtualDisplayActivity");
+    public static final ComponentName VR_TEST_ACTIVITY = component("VrTestActivity");
+    public static final ComponentName WALLPAPAER_ACTIVITY = component("WallpaperActivity");
+
+    public static final ComponentName ASSISTANT_VOICE_INTERACTION_SERVICE =
+            component("AssistantVoiceInteractionService");
+
+    public static final ComponentName LAUNCH_BROADCAST_RECEIVER =
+            component("LaunchBroadcastReceiver");
+    public static final String LAUNCH_BROADCAST_ACTION =
+            getPackageName() + ".LAUNCH_BROADCAST_ACTION";
+
+    /**
+     * Action and extra key constants for {@link #TEST_ACTIVITY}.
+     *
+     * TODO(b/73346885): These constants should be in {@link android.server.am.TestActivity} once
+     * the activity is moved to test APK.
+     */
+    public static class TestActivity {
+        // Finishes the activity
+        public static final String TEST_ACTIVITY_ACTION_FINISH_SELF =
+                TestActivity.class.getName() + ".finish_self";
+        // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+        public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+    }
+
+    /**
+     * Extra key constants for {@link #LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK} and
+     * {@link #LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION}.
+     *
+     * TODO(b/73346885): These constants should be in {@link android.server.am.AssistantActivity}
+     * once the activity is moved to test APK.
+     */
+    public static class AssistantActivity {
+        // Launches the given activity in onResume
+        public static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
+        // Finishes this activity in onResume, this happens after EXTRA_LAUNCH_NEW_TASK
+        public static final String EXTRA_FINISH_SELF = "finish_self";
+        // Attempts to enter picture-in-picture in onResume
+        public static final String EXTRA_ENTER_PIP = "enter_pip";
+        // Display on which Assistant runs
+        public static final String EXTRA_ASSISTANT_DISPLAY_ID = "assistant_display_id";
+    }
+
+    /**
+     * Extra key constants for {@link #LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK}.
+     *
+     * TODO(b/73346885): These constants should be in
+     * {@link android.server.am.LaunchAssistantActivityIntoAssistantStack} once the activity is
+     * moved to test APK.
+     */
+    public static class LaunchAssistantActivityIntoAssistantStack {
+        // Launches the translucent assist activity
+        public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
+    }
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+
+    private static String getPackageName() {
+        return getPackageName(Components.class);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java
new file mode 100644
index 0000000..0e73939
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * Activity with DialogWhenLarge Theme.
+ */
+public class DialogWhenLargeActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = DialogWhenLargeActivity.class.getSimpleName();
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java
new file mode 100644
index 0000000..30dc69c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class DismissKeyguardActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
new file mode 100644
index 0000000..e19c4a1
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.KeyguardManager;
+import android.os.Bundle;
+
+public class DismissKeyguardMethodActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+                new KeyguardDismissLoggerCallback(this));
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java
new file mode 100644
index 0000000..ce6dbb2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class DockedActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
new file mode 100644
index 0000000..d46ab38
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class FontScaleActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = FontScaleActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        dumpActivityDpi();
+        dumpFontSize();
+    }
+
+    // We're basically ensuring that no matter what happens to the resources underneath the
+    // Activity, any TypedArrays obtained from the pool have the correct DisplayMetrics.
+    protected void dumpFontSize() {
+        try (XmlResourceParser parser = getResources().getXml(R.layout.font_scale)) {
+            //noinspection StatementWithEmptyBody
+            while (parser.next() != XmlPullParser.START_TAG) { }
+
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            TypedArray ta = getTheme().obtainStyledAttributes(attrs,
+                    new int[] { android.R.attr.textSize }, 0, 0);
+            try {
+                final int fontPixelSize = ta.getDimensionPixelSize(0, -1);
+                if (fontPixelSize == -1) {
+                    throw new AssertionError("android:attr/textSize not found");
+                }
+
+                Log.i(getTag(), "fontPixelSize=" + fontPixelSize);
+            } finally {
+                ta.recycle();
+            }
+        } catch (XmlPullParserException | IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void dumpActivityDpi() {
+        final int fontActivityDpi = getResources().getDisplayMetrics().densityDpi;
+        Log.i(getTag(), "fontActivityDpi=" + fontActivityDpi);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java
new file mode 100644
index 0000000..c7744b0
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class FontScaleNoRelaunchActivity extends FontScaleActivity {
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpActivityDpi();
+        dumpFontSize();
+    }
+
+    @Override
+    protected String getTag() {
+        return FontScaleNoRelaunchActivity.class.getSimpleName();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java
new file mode 100644
index 0000000..0d60e66
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Rect;
+
+public class FreeformActivity extends Activity {
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        final Intent intent = new Intent(this, TestActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchBounds(new Rect(0, 0, 900, 900));
+        this.startActivity(intent, options.toBundle());
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
new file mode 100644
index 0000000..c7113e6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.KeyguardManager;
+import android.app.KeyguardManager.KeyguardDismissCallback;
+import android.content.Context;
+import android.util.Log;
+
+public class KeyguardDismissLoggerCallback extends KeyguardDismissCallback {
+
+    private final String TAG = "KeyguardDismissLoggerCallback";
+
+    private final Context mContext;
+
+    public KeyguardDismissLoggerCallback(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void onDismissError() {
+        Log.i(TAG, "onDismissError");
+    }
+
+    @Override
+    public void onDismissSucceeded() {
+        if (mContext.getSystemService(KeyguardManager.class).isDeviceLocked()) {
+            // Device is still locked? What a fail. Don't print "onDismissSucceded" such that the
+            // log fails.
+            Log.i(TAG, "dismiss succedded was called but device is still locked.");
+        } else {
+            Log.i(TAG, "onDismissSucceeded");
+        }
+    }
+
+    @Override
+    public void onDismissCancelled() {
+        Log.i(TAG, "onDismissCancelled");
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java
new file mode 100644
index 0000000..f69dc5d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.KeyguardManager;
+import android.os.Bundle;
+
+public class KeyguardLockActivity extends BroadcastReceiverActivity {
+
+    private KeyguardManager.KeyguardLock mKeyguardLock;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mKeyguardLock = getSystemService(KeyguardManager.class).newKeyguardLock("test");
+        mKeyguardLock.disableKeyguard();
+    }
+
+    @Override
+    protected void onDestroy() {
+        mKeyguardLock.reenableKeyguard();
+        super.onDestroy();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
new file mode 100644
index 0000000..2d7ca6f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class LandscapeOrientationActivity extends AbstractLifecycleLogActivity {
+    @Override
+    protected String getTag() {
+        return "LandscapeOrientationActivity";
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        final Configuration config = getResources().getConfiguration();
+        dumpDisplaySize(config);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java
new file mode 100644
index 0000000..c67e9dd
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class LaunchAssistantActivityFromSession extends Activity {
+    @Override
+    protected void onResume() {
+        super.onResume();
+        AssistantVoiceInteractionService.launchAssistantActivity(this, getIntent().getExtras());
+        finishAndRemoveTask();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java
new file mode 100644
index 0000000..19cc73b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.Components.LaunchAssistantActivityIntoAssistantStack
+        .EXTRA_IS_TRANSLUCENT;
+
+import android.app.Activity;
+import android.content.Intent;
+
+public class LaunchAssistantActivityIntoAssistantStack extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        final Intent intent = getIntent();
+        if (isTranslucent(intent)) {
+            TranslucentAssistantActivity.launchActivityIntoAssistantStack(this, intent.getExtras());
+        } else {
+            AssistantActivity.launchActivityIntoAssistantStack(this, getIntent().getExtras());
+        }
+        finishAndRemoveTask();
+    }
+
+    private static boolean isTranslucent(Intent intent) {
+        return intent.hasExtra(EXTRA_IS_TRANSLUCENT)
+                && Boolean.valueOf(intent.getStringExtra(EXTRA_IS_TRANSLUCENT));
+    }
+}
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
new file mode 100644
index 0000000..f3cb966
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/** Broadcast receiver that can launch activities. */
+public class LaunchBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        try {
+            ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException launching activity");
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java
new file mode 100644
index 0000000..db1119d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+
+public class LaunchEnterPipActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        PipActivity.launchEnterPipActivity(this);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java
new file mode 100644
index 0000000..bb97261
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+public class LaunchIntoPinnedStackPipActivity extends Activity {
+    @Override
+    protected void onResume() {
+        super.onResume();
+        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java
new file mode 100644
index 0000000..52263f2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+
+public class LaunchPipOnPipActivity extends Activity {
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this,
+            getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java b/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java
new file mode 100644
index 0000000..e9cc897
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class LifecycleLogView extends View {
+    private final String TAG = "LifecycleLogView";
+
+    public LifecycleLogView(Context context) {
+        super(context);
+    }
+
+    public LifecycleLogView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
+    {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        Log.i(TAG, "onConfigurationChanged");
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
new file mode 100644
index 0000000..5ed370e
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.util.Log;
+
+/**
+ * Activity that logs configuration changes.
+ */
+public class LogConfigurationActivity extends Activity {
+
+    private static final String TAG = "LogConfigurationActivity";
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        Log.i(TAG, "Configuration changed: " + newConfig.screenWidthDp + ","
+                + newConfig.screenHeightDp);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java
new file mode 100644
index 0000000..6c37115
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that finishes itself using "moveTaskToBack".
+ */
+public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
+
+    private String mFinishPoint;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        mFinishPoint = intent.getExtras().getString("finish_point");
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mFinishPoint.equals("on_pause")) {
+            moveTaskToBack(true /* nonRoot */);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (mFinishPoint.equals("on_stop")) {
+            moveTaskToBack(true /* nonRoot */);
+        }
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java
new file mode 100644
index 0000000..7c73900
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.Bundle;
+
+/** Activity that changes UI mode on creation and handles corresponding configuration change. */
+public class NightModeActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = NightModeActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        UiModeManager uiManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
+        // Switch the mode two times to make sure it is independent of the current setting.
+        uiManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
+        uiManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java
new file mode 100644
index 0000000..3080fac
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * An activity that has the noHistory flag set.
+ */
+public class NoHistoryActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = NoHistoryActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java
new file mode 100644
index 0000000..b173f8d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+public class NoRelaunchActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = NoRelaunchActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java
new file mode 100644
index 0000000..e3468e3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+public class NonResizeableActivity extends AbstractLifecycleLogActivity {
+
+     private static final String TAG = NonResizeableActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
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
new file mode 100644
index 0000000..f5397e6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PictureInPictureParams;
+import android.content.res.Configuration;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Rational;
+import android.view.WindowManager;
+
+public class PipActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = "PipActivity";
+
+    // Intent action that this activity dynamically registers to enter picture-in-picture
+    private static final String ACTION_ENTER_PIP = "android.server.am.PipActivity.enter_pip";
+    // Intent action that this activity dynamically registers to move itself to the back
+    private static final String ACTION_MOVE_TO_BACK = "android.server.am.PipActivity.move_to_back";
+    // Intent action that this activity dynamically registers to expand itself.
+    // If EXTRA_SET_ASPECT_RATIO_WITH_DELAY is set, it will also attempt to apply the aspect ratio
+    // after a short delay.
+    private static final String ACTION_EXPAND_PIP = "android.server.am.PipActivity.expand_pip";
+    // Intent action that this activity dynamically registers to set requested orientation.
+    // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+    private static final String ACTION_SET_REQUESTED_ORIENTATION =
+            "android.server.am.PipActivity.set_requested_orientation";
+    // Intent action that will finish this activity
+    private static final String ACTION_FINISH = "android.server.am.PipActivity.finish";
+
+    // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+    private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+    // Calls enterPictureInPicture() on creation
+    private static final String EXTRA_ENTER_PIP = "enter_pip";
+    // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
+    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
+            "enter_pip_aspect_ratio_numerator";
+    // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
+    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
+            "enter_pip_aspect_ratio_denominator";
+    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
+    private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
+    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
+    private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
+    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
+    // fixed delay
+    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
+            "set_aspect_ratio_with_delay_numerator";
+    // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
+    // fixed delay
+    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
+            "set_aspect_ratio_with_delay_denominator";
+    // Adds a click listener to finish this activity when it is clicked
+    private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+    // Calls requestAutoEnterPictureInPicture() with the value provided
+    private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+    // Starts the activity (component name) provided by the value at the end of onCreate
+    private static final String EXTRA_START_ACTIVITY = "start_activity";
+    // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
+    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+    // Calls enterPictureInPicture() again after onPictureInPictureModeChanged(false) is called
+    private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+    // Shows this activity over the keyguard
+    private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+    // Adds an assertion that we do not ever get onStop() before we enter picture in picture
+    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+    // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
+    // is processed)
+    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+    private boolean mEnteredPictureInPicture;
+
+    private Handler mHandler = new Handler();
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent != null) {
+                switch (intent.getAction()) {
+                    case ACTION_ENTER_PIP:
+                        enterPictureInPictureMode();
+                        break;
+                    case ACTION_MOVE_TO_BACK:
+                        moveTaskToBack(false /* nonRoot */);
+                        break;
+                    case ACTION_EXPAND_PIP:
+                        // Trigger the activity to expand
+                        Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
+                        startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                        startActivity(startIntent);
+
+                        if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
+                                && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
+                            // Ugly, but required to wait for the startActivity to actually start
+                            // the activity...
+                            mHandler.postDelayed(() -> {
+                                final PictureInPictureParams.Builder builder =
+                                        new PictureInPictureParams.Builder();
+                                builder.setAspectRatio(getAspectRatio(intent,
+                                        EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
+                                        EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
+                                setPictureInPictureParams(builder.build());
+                            }, 100);
+                        }
+                        break;
+                    case ACTION_SET_REQUESTED_ORIENTATION:
+                        setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
+                                EXTRA_FIXED_ORIENTATION)));
+                        break;
+                    case ACTION_FINISH:
+                        finish();
+                        break;
+                }
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set the fixed orientation if requested
+        if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
+            final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
+            setRequestedOrientation(ori);
+        }
+
+        // Set the window flag to show over the keyguard
+        if (getIntent().hasExtra(EXTRA_SHOW_OVER_KEYGUARD)) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        }
+
+        // Enter picture in picture with the given aspect ratio if provided
+        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+            if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
+                    && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
+                try {
+                    final PictureInPictureParams.Builder builder =
+                            new PictureInPictureParams.Builder();
+                    builder.setAspectRatio(getAspectRatio(getIntent(),
+                            EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
+                            EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
+                    enterPictureInPictureMode(builder.build());
+                } catch (Exception e) {
+                    // This call can fail intentionally if the aspect ratio is too extreme
+                }
+            } else {
+                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+            }
+        }
+
+        // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
+        // to be called before setting the aspect ratio
+        if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
+                && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
+            final PictureInPictureParams.Builder builder =
+                    new PictureInPictureParams.Builder();
+            builder.setAspectRatio(getAspectRatio(getIntent(),
+                    EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
+            try {
+                setPictureInPictureParams(builder.build());
+            } catch (Exception e) {
+                // This call can fail intentionally if the aspect ratio is too extreme
+            }
+        }
+
+        // Enable tap to finish if necessary
+        if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
+            setContentView(R.layout.tap_to_finish_pip_layout);
+            findViewById(R.id.content).setOnClickListener(v -> {
+                finish();
+            });
+        }
+
+        // Launch a new activity if requested
+        String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
+        if (launchActivityComponent != null) {
+            Intent launchIntent = new Intent();
+            launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
+            startActivity(launchIntent);
+        }
+
+        // Register the broadcast receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_ENTER_PIP);
+        filter.addAction(ACTION_MOVE_TO_BACK);
+        filter.addAction(ACTION_EXPAND_PIP);
+        filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
+        filter.addAction(ACTION_FINISH);
+        registerReceiver(mReceiver, filter);
+
+        // Dump applied display metrics.
+        Configuration config = getResources().getConfiguration();
+        dumpDisplaySize(config);
+        dumpConfiguration(config);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Finish self if requested
+        if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
+            finish();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        // Pause if requested
+        if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
+            SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
+        }
+
+        // Enter PIP on move to background
+        if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
+            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
+            Log.w(TAG, "Unexpected onStop() called before entering picture-in-picture");
+            finish();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+
+        // Fail early if the activity state does not match the dispatched state
+        if (isInPictureInPictureMode() != isInPictureInPictureMode) {
+            Log.w(TAG, "Received onPictureInPictureModeChanged mode=" + isInPictureInPictureMode
+                    + " activityState=" + isInPictureInPictureMode());
+            finish();
+        }
+
+        // Mark that we've entered picture-in-picture so that we can stop checking for
+        // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
+        if (isInPictureInPictureMode) {
+            mEnteredPictureInPicture = true;
+        }
+
+        if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
+            // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
+            // checking that the stacks ever changed). Therefor, we need to delay here slightly to
+            // allow the tests to verify that the stacks have changed before re-entering.
+            mHandler.postDelayed(() -> {
+                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+            }, 1000);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+        dumpConfiguration(newConfig);
+    }
+
+    /**
+     * Launches a new instance of the PipActivity directly into the pinned stack.
+     */
+    static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
+        final Intent intent = new Intent(caller, PipActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchBounds(bounds);
+        options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+        caller.startActivity(intent, options.toBundle());
+    }
+
+    /**
+     * Launches a new instance of the PipActivity in the same task that will automatically enter
+     * PiP.
+     */
+    static void launchEnterPipActivity(Activity caller) {
+        final Intent intent = new Intent(caller, PipActivity.class);
+        intent.putExtra(EXTRA_ENTER_PIP, "true");
+        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+        caller.startActivity(intent);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    /**
+     * @return a {@link Rational} aspect ratio from the given intent and extras.
+     */
+    private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
+        return new Rational(
+                Integer.valueOf(intent.getStringExtra(extraNum)),
+                Integer.valueOf(intent.getStringExtra(extraDenom)));
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java
new file mode 100644
index 0000000..c7c9858
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * A secondary activity that has the same behavior as {@link PipActivity}.
+ */
+public class PipActivity2 extends PipActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java
new file mode 100644
index 0000000..12ff39f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.PictureInPictureParams;
+
+/**
+ * An activity that can enter PiP with a fixed affinity to match
+ * {@link TestActivityWithSameAffinity}.
+ */
+public class PipActivityWithSameAffinity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java
new file mode 100644
index 0000000..264e14c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Intent;
+import android.os.Bundle;
+
+/**
+ * This activity will try and enter picture in picture when it is stopped.
+ */
+public class PipOnStopActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.tap_to_finish_pip_layout);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        startActivity(new Intent(this, PipActivity.class));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        try {
+            enterPictureInPictureMode();
+        } catch (RuntimeException e) {
+            // Known failure, we expect this call to throw an exception
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
new file mode 100644
index 0000000..a63e3b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class PortraitOrientationActivity extends AbstractLifecycleLogActivity {
+
+    @Override
+    protected String getTag() {
+        return "PortraitOrientationActivity";
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        final Configuration config = getResources().getConfiguration();
+        dumpDisplaySize(config);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
new file mode 100644
index 0000000..31aa9f2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+public class ResizeableActivity extends AbstractLifecycleLogActivity {
+    @Override
+    protected String getTag() {
+        return "ResizeableActivity";
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.resizeable_activity);
+        dumpDisplaySize(getResources().getConfiguration());
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java
new file mode 100644
index 0000000..62fbb8b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class ResumeWhilePausingActivity extends Activity {
+    // Empty
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java
new file mode 100644
index 0000000..1fc7b74
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedActivity extends BroadcastReceiverActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java
new file mode 100644
index 0000000..2814361
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class ShowWhenLockedAttrActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = ShowWhenLockedAttrActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java
new file mode 100644
index 0000000..414d021
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+public class ShowWhenLockedAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = ShowWhenLockedAttrRemoveAttrActivity.class.getSimpleName();
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        setShowWhenLocked(false);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java
new file mode 100644
index 0000000..4f9b214
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedAttrWithDialogActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        new AlertDialog.Builder(this)
+                .setTitle("Dialog")
+                .show();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java
new file mode 100644
index 0000000..56eb154
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedDialogActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java
new file mode 100644
index 0000000..6422104
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.view.WindowManager;
+
+public class ShowWhenLockedTranslucentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        setContentView(R.layout.translucent);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java
new file mode 100644
index 0000000..3951532
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedWithDialogActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        new AlertDialog.Builder(this)
+                .setTitle("Dialog")
+                .show();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java
new file mode 100644
index 0000000..2c8b40f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class SingleInstanceActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java
new file mode 100644
index 0000000..d95aa3f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class SingleTaskActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java
new file mode 100644
index 0000000..4b78fc5
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.SystemClock;
+
+import java.util.concurrent.TimeUnit;
+
+public class SlowCreateActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java
new file mode 100644
index 0000000..2d8fbae
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Activity that shows a custom splashscreen when being launched.
+ */
+public class SplashscreenActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Make sure splash screen is visible. The test won't take 5 seconds because the condition
+        // such that we can dump the state will trigger much earlier and then the test will just
+        // kill us.
+        SystemClock.sleep(5000);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java
new file mode 100644
index 0000000..8e2b781
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.server.am.SwipeRefreshLayout;
+
+
+/**
+ * An activity containing a SwipeRefreshLayout which prevents activity idle.
+ */
+public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = SwipeRefreshActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new SwipeRefreshLayout(this));
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java
new file mode 100644
index 0000000..2b3071c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * An extension of {@link android.support.v4.widget.SwipeRefreshLayout} that calls
+ * {@link #setRefreshing} during construction, preventing activity idle.
+ */
+public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {
+    public SwipeRefreshLayout(Context context) {
+        super(context);
+        initialize();
+    }
+
+    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize();
+    }
+
+    private void initialize() {
+        setRefreshing(true);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java
new file mode 100644
index 0000000..d89d786
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.PictureInPictureParams;
+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.os.Bundle;
+import android.util.Log;
+
+public class TestActivityWithSameAffinity extends TestActivity {
+
+    private static final String TAG = TestActivityWithSameAffinity.class.getSimpleName();
+
+    // Calls enterPictureInPicture() on creation
+    private static final String EXTRA_ENTER_PIP = "enter_pip";
+    // Starts the activity (component name) provided by the value at the end of onCreate
+    private static final String EXTRA_START_ACTIVITY = "start_activity";
+    // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
+    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Enter picture in picture if requested
+        if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        }
+
+        // Launch a new activity if requested
+        String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
+        if (launchActivityComponent != null) {
+            Intent launchIntent = new Intent();
+            launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
+            startActivity(launchIntent);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Finish self if requested
+        if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
+            finish();
+        }
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java
new file mode 100644
index 0000000..7d69ef9
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Handler;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TopActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = TopActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+        if (useWallpaper) {
+            setTheme(R.style.WallpaperTheme);
+        }
+
+        final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
+        if (finishDelay > 0) {
+            Handler handler = new Handler();
+            handler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    Log.d(TAG, "Calling finish()");
+                    finish();
+                }
+            }, finishDelay);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java
new file mode 100644
index 0000000..1302b22
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TopLeftLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java
new file mode 100644
index 0000000..bb13c01
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TopRightLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java
new file mode 100644
index 0000000..7391b04
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.content.Intent;
+
+public class TrampolineActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = TrampolineActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Add a delay here to expose more easily a failure case where the real target
+        // activity is visible before it's launched, because its task is being brought
+        // to foreground. We need to verify that 'am start' is unblocked correctly.
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {}
+        Intent intent = new Intent(this, SingleTaskActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
+
+        startActivity(intent);
+        finish();
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java
new file mode 100644
index 0000000..a83ceaf
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TranslucentActivity extends Activity {
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java
new file mode 100644
index 0000000..2ad5e17
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class TranslucentAssistantActivity extends AssistantActivity {
+
+    /**
+     * Launches a new instance of the TranslucentAssistantActivity directly into the assistant
+     * stack.
+     */
+    static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
+        final Intent intent = new Intent(caller, TranslucentAssistantActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
+        caller.startActivity(intent, options.toBundle());
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java
new file mode 100644
index 0000000..ba60f67
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+public class TranslucentTestActivity extends TestActivity {
+
+    private static final String TAG = TranslucentTestActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.task_overlay);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java
new file mode 100644
index 0000000..5d639db
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Handler;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TranslucentTopActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = TranslucentTopActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+        if (useWallpaper) {
+            setTheme(R.style.TranslucentWallpaperTheme);
+        }
+
+        final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
+        if (finishDelay > 0) {
+            Handler handler = new Handler();
+            handler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    Log.d(TAG, "Calling finish()");
+                    finish();
+                }
+            }, finishDelay);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java
new file mode 100644
index 0000000..4334503
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class TurnScreenOnActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java
new file mode 100644
index 0000000..f644eb2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnAttrActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnAttrActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
new file mode 100644
index 0000000..acc92b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.KeyguardManager;
+import android.os.Bundle;
+
+public class TurnScreenOnAttrDismissKeyguardActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnAttrDismissKeyguardActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        ((KeyguardManager) getSystemService(KEYGUARD_SERVICE))
+                .requestDismissKeyguard(this, new KeyguardDismissLoggerCallback(this));
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java
new file mode 100644
index 0000000..9ad7ddd
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnAttrRemoveAttrActivity.class.getSimpleName();
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        setTurnScreenOn(false);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
new file mode 100644
index 0000000..8f4b947
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.KeyguardManager;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class TurnScreenOnDismissKeyguardActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+                new KeyguardDismissLoggerCallback(this));
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java
new file mode 100644
index 0000000..6113a23
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnShowOnLockActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnShowOnLockActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java
new file mode 100644
index 0000000..2dddb9b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnSingleTaskActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnSingleTaskActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java
new file mode 100644
index 0000000..a1ebbab
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+import android.view.WindowManager;
+
+public class TurnScreenOnWithRelayoutActivity extends AbstractLifecycleLogActivity {
+    private static final String TAG = TurnScreenOnWithRelayoutActivity.class.getSimpleName();
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        // This is to force a relayout, specifically with insets changed. When the insets are
+        // changed, it will trigger a performShow that could turn the screen on.
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+    }
+}
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
new file mode 100644
index 0000000..9b8feeb
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+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;
+import static android.server.am.ActivityLauncher.KEY_TARGET_COMPONENT;
+import static android.server.am.ComponentNameUtils.getActivityName;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+
+/**
+ * Activity that is able to create and destroy a virtual display.
+ */
+public class VirtualDisplayActivity extends Activity implements SurfaceHolder.Callback {
+    private static final String TAG = "VirtualDisplayActivity";
+
+    private static final int DEFAULT_DENSITY_DPI = 160;
+    private static final String KEY_DENSITY_DPI = "density_dpi";
+    private static final String KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD
+            = "can_show_with_insecure_keyguard";
+    private static final String KEY_PUBLIC_DISPLAY = "public_display";
+    private static final String KEY_RESIZE_DISPLAY = "resize_display";
+    private static final String KEY_LAUNCH_TARGET_COMPONENT = "launch_target_component";
+    private static final String KEY_COUNT = "count";
+
+    private DisplayManager mDisplayManager;
+
+    // Container for details about a pending virtual display creation request.
+    private static class VirtualDisplayRequest {
+        public final SurfaceView surfaceView;
+        public final Bundle extras;
+
+        public VirtualDisplayRequest(SurfaceView surfaceView, Bundle extras) {
+            this.surfaceView = surfaceView;
+            this.extras = extras;
+        }
+    }
+
+    // Container to hold association between an active virtual display and surface view.
+    private static class VirtualDisplayEntry {
+        public final VirtualDisplay display;
+        public final SurfaceView surfaceView;
+        public final boolean resizeDisplay;
+        public final int density;
+
+        public VirtualDisplayEntry(VirtualDisplay display, SurfaceView surfaceView, int density,
+                boolean resizeDisplay) {
+            this.display = display;
+            this.surfaceView = surfaceView;
+            this.density = density;
+            this.resizeDisplay = resizeDisplay;
+        }
+    }
+
+    private HashMap<Surface, VirtualDisplayRequest> mPendingDisplayRequests;
+    private HashMap<Surface, VirtualDisplayEntry> mVirtualDisplays;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.virtual_display_layout);
+
+        mVirtualDisplays = new HashMap<>();
+        mPendingDisplayRequests = new HashMap<>();
+        mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        final Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return;
+        }
+
+        String command = extras.getString("command");
+        switch (command) {
+            case "create_display":
+                createVirtualDisplay(extras);
+                break;
+            case "destroy_display":
+                destroyVirtualDisplays();
+                break;
+            case "resize_display":
+                resizeDisplay();
+                break;
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        destroyVirtualDisplays();
+    }
+
+    private void createVirtualDisplay(Bundle extras) {
+        final int requestedCount = extras.getInt(KEY_COUNT, 1);
+        Log.d(TAG, "createVirtualDisplays. requested count:" + requestedCount);
+
+        for (int displayCount = 0; displayCount < requestedCount; ++displayCount) {
+            final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+            final SurfaceView surfaceView = new SurfaceView(this);
+            surfaceView.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+            surfaceView.getHolder().addCallback(this);
+            mPendingDisplayRequests.put(surfaceView.getHolder().getSurface(),
+                    new VirtualDisplayRequest(surfaceView, extras));
+            root.addView(surfaceView);
+        }
+    }
+
+    private void destroyVirtualDisplays() {
+        Log.d(TAG, "destroyVirtualDisplays");
+        final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+
+        for (VirtualDisplayEntry entry : mVirtualDisplays.values()) {
+            Log.d(TAG, "destroying:" + entry.display);
+            entry.display.release();
+            root.removeView(entry.surfaceView);
+        }
+
+        mPendingDisplayRequests.clear();
+        mVirtualDisplays.clear();
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder surfaceHolder) {
+        final VirtualDisplayRequest entry =
+                mPendingDisplayRequests.remove(surfaceHolder.getSurface());
+
+        if (entry == null) {
+            return;
+        }
+
+        final int densityDpi = entry.extras.getInt(KEY_DENSITY_DPI, DEFAULT_DENSITY_DPI);
+        final boolean resizeDisplay = entry.extras.getBoolean(KEY_RESIZE_DISPLAY);
+        final String launchComponentName = entry.extras.getString(KEY_LAUNCH_TARGET_COMPONENT);
+        final Surface surface = surfaceHolder.getSurface();
+
+        // Initially, the surface will not have a set width or height so rely on the parent.
+        // This should be accurate with match parent on both params.
+        final int width = surfaceHolder.getSurfaceFrame().width();
+        final int height = surfaceHolder.getSurfaceFrame().height();
+
+        int flags = 0;
+
+        final boolean canShowWithInsecureKeyguard
+                = entry.extras.getBoolean(KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD);
+        if (canShowWithInsecureKeyguard) {
+            flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+        }
+
+        final boolean publicDisplay = entry.extras.getBoolean(KEY_PUBLIC_DISPLAY);
+        if (publicDisplay) {
+            flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+        }
+
+        Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: "
+                + densityDpi + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
+                + ", publicDisplay=" + publicDisplay);
+        try {
+            VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
+                    "HostedVirtualDisplay" + mVirtualDisplays.size(), width,
+                    height, densityDpi, surface, flags);
+            mVirtualDisplays.put(surface,
+                    new VirtualDisplayEntry(virtualDisplay, entry.surfaceView, densityDpi,
+                            resizeDisplay));
+            if (launchComponentName != null) {
+                final ComponentName targetActivity =
+                        ComponentName.unflattenFromString(launchComponentName);
+                final int displayId = virtualDisplay.getDisplay().getDisplayId();
+                Log.d(TAG, "Launch activity after display created: activityName="
+                        + getActivityName(targetActivity) + ", displayId=" + displayId);
+                launchActivity(targetActivity, displayId);
+            }
+        } catch (IllegalArgumentException e) {
+            final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+            // This is expected when trying to create show-when-locked public display.
+            root.removeView(entry.surfaceView);
+        }
+
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
+        final VirtualDisplayEntry entry = mVirtualDisplays.get(surfaceHolder.getSurface());
+
+        if (entry != null && entry.resizeDisplay) {
+            entry.display.resize(width, height, entry.density);
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    }
+
+    /** Resize virtual display to half of the surface frame size. */
+    private void resizeDisplay() {
+        final VirtualDisplayEntry vd = (VirtualDisplayEntry) mVirtualDisplays.values().toArray()[0];
+        final SurfaceHolder surfaceHolder = vd.surfaceView.getHolder();
+        vd.display.resize(surfaceHolder.getSurfaceFrame().width() / 2,
+                surfaceHolder.getSurfaceFrame().height() / 2, vd.density);
+    }
+
+    private void launchActivity(ComponentName activityName, int displayId) {
+        final Bundle extras = new Bundle();
+        extras.putBoolean(KEY_LAUNCH_ACTIVITY, true);
+        extras.putString(KEY_TARGET_COMPONENT, getActivityName(activityName));
+        extras.putInt(KEY_DISPLAY_ID, displayId);
+        ActivityLauncher.launchActivityFromExtras(this, extras);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java
new file mode 100644
index 0000000..e163aa8
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.vr.MockVrListenerService;
+
+/**
+ * Activity that is able to create and destroy a virtual display.
+ */
+public class VrTestActivity extends Activity {
+    private static final String TAG = "VrTestActivity";
+    private static final boolean DEBUG = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (DEBUG) Log.i(TAG, "onCreate called.");
+        super.onCreate(savedInstanceState);
+        try {
+            setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Could not set VR mode: " + e);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        if (DEBUG) Log.i(TAG, "onResume called.");
+        super.onResume();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (DEBUG) Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
+        super.onWindowFocusChanged(hasFocus);
+    }
+
+    @Override
+    protected void onPause() {
+        if (DEBUG) Log.i(TAG, "onPause called.");
+        super.onPause();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java
new file mode 100644
index 0000000..2bbe1b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class WallpaperActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app27/Android.mk b/tests/framework/base/activitymanager/app27/Android.mk
new file mode 100644
index 0000000..e644c62
--- /dev/null
+++ b/tests/framework/base/activitymanager/app27/Android.mk
@@ -0,0 +1,35 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    cts-am-app-base \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 27
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceServicesTestApp27
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/app27/AndroidManifest.xml b/tests/framework/base/activitymanager/app27/AndroidManifest.xml
new file mode 100755
index 0000000..3ed378e
--- /dev/null
+++ b/tests/framework/base/activitymanager/app27/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.server.am.app27">
+
+    <application android:label="App27">
+        <activity android:name="android.server.am.LaunchingActivity"
+                  android:resizeableActivity="true"
+                  android:supportsPictureInPicture="true"
+                  android:exported="true"
+        />
+    </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
new file mode 100644
index 0000000..ec50cd6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app27/src/android/server/am/app27/Components.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.server.am.app27;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SDK_27_LAUNCHING_ACTIVITY =
+            component(Components.class, "android.server.am.LaunchingActivity");
+}
diff --git a/tests/framework/base/activitymanager/appDebuggable/Android.mk b/tests/framework/base/activitymanager/appDebuggable/Android.mk
new file mode 100644
index 0000000..3d8bb1a
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceDebuggableApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml b/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml
new file mode 100644
index 0000000..e8a2452
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.server.am.debuggable">
+
+    <!--
+     * Security policy requires that only debuggable processes can be profiled
+     * which is tested by ActivityManagerAmProfileTests.
+     -->
+    <application android:debuggable="true">
+        <activity android:name=".DebuggableAppActivity"
+                  android:resizeableActivity="true"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/Components.java b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/Components.java
new file mode 100644
index 0000000..2d32de6
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/Components.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.server.am.debuggable;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName DEBUGGABLE_APP_ACTIVITY =
+            component(Components.class, "DebuggableAppActivity");
+}
diff --git a/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java
new file mode 100644
index 0000000..33034b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.debuggable;
+
+import android.app.Activity;
+
+public class DebuggableAppActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/appDeprecatedSdk/Android.mk b/tests/framework/base/activitymanager/appDeprecatedSdk/Android.mk
new file mode 100644
index 0000000..2b556f6
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDeprecatedSdk/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 16
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceDeprecatedSdkApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appDeprecatedSdk/AndroidManifest.xml b/tests/framework/base/activitymanager/appDeprecatedSdk/AndroidManifest.xml
new file mode 100644
index 0000000..19847b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDeprecatedSdk/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.
+-->
+
+<!-- Target sdk version set < 17. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.am.deprecatedsdk">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application>
+        <activity android:name=".MainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/Components.java b/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/Components.java
new file mode 100644
index 0000000..180738d
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/Components.java
@@ -0,0 +1,9 @@
+package android.server.am.deprecatedsdk;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName MAIN_ACTIVITY = component(Components.class, "MainActivity");
+}
diff --git a/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/MainActivity.java b/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/MainActivity.java
new file mode 100644
index 0000000..57b4e44
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDeprecatedSdk/src/android/server/am/deprecatedsdk/MainActivity.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.server.am.deprecatedsdk;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+
+}
diff --git a/tests/framework/base/activitymanager/appDisplaySize/Android.mk b/tests/framework/base/activitymanager/appDisplaySize/Android.mk
new file mode 100644
index 0000000..5d44137
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceDisplaySizeApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml b/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml
new file mode 100644
index 0000000..a3bef31
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.am.displaysize">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <!-- Set a ridiculously high value for required smallest width. Hopefully
+         we never have to run CTS on Times Square display. -->
+    <supports-screens android:requiresSmallestWidthDp="100000" />
+
+    <application>
+        <activity android:name=".SmallestWidthActivity"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/Components.java b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/Components.java
new file mode 100644
index 0000000..22633ee
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/Components.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.server.am.displaysize;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SMALLEST_WIDTH_ACTIVITY =
+            component(Components.class, "SmallestWidthActivity");
+
+    /**
+     * Extra key to launch another activity. The extra value is activity's component name.
+     */
+    public static class SmallestWidthActivity {
+        public static final String EXTRA_LAUNCH_ANOTHER_ACTIVITY = "launch_another_activity";
+    }
+}
diff --git a/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java
new file mode 100644
index 0000000..7c30b85
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.displaysize;
+
+import static android.server.am.displaysize.Components.SmallestWidthActivity
+        .EXTRA_LAUNCH_ANOTHER_ACTIVITY;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class SmallestWidthActivity extends Activity {
+
+    @Override
+    protected void onNewIntent(final Intent intent) {
+        super.onNewIntent(intent);
+
+        if (intent.hasExtra(EXTRA_LAUNCH_ANOTHER_ACTIVITY)) {
+            startActivity(new Intent().setComponent(ComponentName.unflattenFromString(
+                    intent.getStringExtra(EXTRA_LAUNCH_ANOTHER_ACTIVITY))));
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk b/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk
new file mode 100644
index 0000000..b1f9094
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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_USE_AAPT2 := true
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SDK_VERSION := core_current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_APK_LIBRARIES := fake-framework
+LOCAL_RES_LIBRARIES := fake-framework
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDevicePrereleaseSdkApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml b/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml
new file mode 100644
index 0000000..40f75a1
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.server.am.prerelease">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application>
+        <activity android:name=".MainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/Android.mk b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/Android.mk
new file mode 100644
index 0000000..264cd41
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/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)
+
+# A fake framework that mimics an older, pre-release SDK for the purposes of
+# testing what happens when an app linked against a pre-release SDK is installed
+# on release device.
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_PACKAGE_NAME := fake-framework
+LOCAL_SDK_VERSION := core_current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := optional
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_UNINSTALLABLE_MODULE := true
+include $(BUILD_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/AndroidManifest.xml b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/AndroidManifest.xml
new file mode 100644
index 0000000..2d7ec3c
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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"
+    android:versionCode="25"
+    android:versionName="O" />
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/res/values/values.xml b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/res/values/values.xml
new file mode 100644
index 0000000..04cace4
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/res/values/values.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <public type="attr" name="theme" id="0x01010000" />
+    <attr name="theme" format="reference" />
+
+    <public type="attr" name="name" id="0x01010003" />
+    <attr name="name" format="string" />
+
+    <public type="attr" name="exported" id="0x01010010" />
+    <attr name="exported" format="boolean" />
+
+    <public type="attr" name="minSdkVersion" id="0x0101020c" />
+    <attr name="minSdkVersion" format="integer|string" />
+
+    <public type="attr" name="versionCode" id="0x0101021b" />
+    <attr name="versionCode" format="integer" />
+
+    <public type="attr" name="versionName" id="0x0101021c" />
+    <attr name="versionName" format="string" />
+
+    <public type="attr" name="targetSdkVersion" id="0x01010270" />
+    <attr name="targetSdkVersion" format="integer|string" />
+
+    <public type="attr" name="compileSdkVersion" id="0x01010572" />
+    <attr name="compileSdkVersion" format="integer" />
+
+    <public type="attr" name="compileSdkVersionCodename" id="0x01010573" />
+    <attr name="compileSdkVersionCodename" format="string" />
+</resources>
+
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/app/Activity.java b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/app/Activity.java
new file mode 100644
index 0000000..960392b
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/app/Activity.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;
+
+public class Activity {
+}
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/content/ComponentName.java b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/content/ComponentName.java
new file mode 100644
index 0000000..32657fd
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/fake-framework/src/android/content/ComponentName.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 android.content;
+
+public class ComponentName {
+    public ComponentName(String pkg, String cls) {}
+}
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/Components.java b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/Components.java
new file mode 100644
index 0000000..5e24ff4
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/Components.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am.prerelease;
+
+import android.content.ComponentName;
+
+// Because we are using fake-framework, we can't extend ComponentsBase here.
+public class Components {
+
+    private static final String PACKAGE_NAME = Components.class.getPackage().getName();
+
+    public static final ComponentName MAIN_ACTIVITY = new ComponentName(
+            PACKAGE_NAME, PACKAGE_NAME + ".MainActivity");
+}
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java
new file mode 100644
index 0000000..8684faf
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am.prerelease;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/Android.mk b/tests/framework/base/activitymanager/appSecondUid/Android.mk
new file mode 100644
index 0000000..2176440
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceServicesTestSecondApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
new file mode 100644
index 0000000..4d4e98d
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/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="android.server.am.second">
+
+    <application>
+        <activity
+            android:name=".SecondActivity"
+            android:resizeableActivity="true"
+            android:allowEmbedded="true"
+            android:exported="true" />
+        <activity
+            android:name=".SecondActivityNoEmbedding"
+            android:resizeableActivity="true"
+            android:allowEmbedded="false"
+            android:exported="true" />
+        <receiver
+            android:name=".LaunchBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.server.am.second.LAUNCH_BROADCAST_ACTION"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java
new file mode 100644
index 0000000..264b83c
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.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.second;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SECOND_ACTIVITY = component("SecondActivity");
+    public static final ComponentName SECOND_NO_EMBEDDING_ACTIVITY =
+            component("SecondActivityNoEmbedding");
+
+    public static final ComponentName SECOND_LAUNCH_BROADCAST_RECEIVER =
+            component("LaunchBroadcastReceiver");
+    /** See AndroidManifest.xml. */
+    public static final String SECOND_LAUNCH_BROADCAST_ACTION =
+            getPackageName() + ".LAUNCH_BROADCAST_ACTION";
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+
+    private static String getPackageName() {
+        return getPackageName(Components.class);
+    }
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
new file mode 100644
index 0000000..1b975ac
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.server.am.ActivityLauncher;
+import android.util.Log;
+
+/** Broadcast receiver that can launch activities. */
+public class LaunchBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        try {
+            ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException launching activity");
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java
new file mode 100644
index 0000000..9607ee2
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.app.Activity;
+
+public class SecondActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java
new file mode 100644
index 0000000..43a967b
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.app.Activity;
+
+public class SecondActivityNoEmbedding extends Activity {
+}
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/appThirdUid/Android.mk b/tests/framework/base/activitymanager/appThirdUid/Android.mk
new file mode 100644
index 0000000..128c8d7
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceServicesTestThirdApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml b/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml
new file mode 100644
index 0000000..ddd89b0
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.server.am.third">
+
+    <application>
+        <activity android:name=".ThirdActivity"
+                  android:resizeableActivity="true"
+                  android:allowEmbedded="true"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/Components.java b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/Components.java
new file mode 100644
index 0000000..a8d2fbf
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/Components.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.server.am.third;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName THIRD_ACTIVITY = component(Components.class, "ThirdActivity");
+}
diff --git a/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java
new file mode 100644
index 0000000..4327d5f
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.third;
+
+import android.app.Activity;
+
+public class ThirdActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app_base/Android.mk b/tests/framework/base/activitymanager/app_base/Android.mk
new file mode 100644
index 0000000..384d715
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    cts-amwm-util \
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-named-files-under,Components.java, ../app) \
+    $(call all-named-files-under,Components.java, ../app27) \
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := cts-am-app-base
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
new file mode 100644
index 0000000..28e6576
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.res.Configuration;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+public abstract class AbstractLifecycleLogActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(getTag(), "onCreate");
+    }
+
+    @Override
+    protected void onStart() {
+        super.onResume();
+        Log.i(getTag(), "onStart");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.i(getTag(), "onResume");
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        Log.i(getTag(), "onConfigurationChanged");
+    }
+
+    @Override
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+        super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+        Log.i(getTag(), "onMultiWindowModeChanged");
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+            Configuration newConfig) {
+        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+        Log.i(getTag(), "onPictureInPictureModeChanged");
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Log.i(getTag(), "onPause");
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Log.i(getTag(), "onStop");
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Log.i(getTag(), "onDestroy");
+    }
+
+    @Override
+    protected void onUserLeaveHint() {
+        super.onUserLeaveHint();
+        Log.i(getTag(), "onUserLeaveHint");
+    }
+
+    protected abstract String getTag();
+
+    protected void dumpConfiguration(Configuration config) {
+        Log.i(getTag(), "Configuration: " + config);
+    }
+
+    protected void dumpDisplaySize(Configuration config) {
+        // Dump the display size as seen by this Activity.
+        final WindowManager wm = getSystemService(WindowManager.class);
+        final Display display = wm.getDefaultDisplay();
+        final Point point = new Point();
+        display.getSize(point);
+        final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
+        final String line = "config"
+                + " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp)
+                + " displaySize=" + buildCoordString(point.x, point.y)
+                + " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels)
+                + " smallestScreenWidth=" + config.smallestScreenWidthDp
+                + " densityDpi=" + config.densityDpi
+                + " orientation=" + config.orientation;
+
+        Log.i(getTag(), line);
+    }
+
+    protected static String buildCoordString(int x, int y) {
+        return "(" + x + "," + y + ")";
+    }
+}
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
new file mode 100644
index 0000000..1eadf61
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.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 android.server.am;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.content.Intent;
+
+/**
+ * Activity that launches another activities when new intent is received.
+ */
+public class LaunchingActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = getIntent();
+        if (savedInstanceState == null && 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/app_base/src/android/server/am/TestActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
new file mode 100644
index 0000000..76e6e1a
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.am.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+public class TestActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = TestActivity.class.getSimpleName();
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent != null && TEST_ACTIVITY_ACTION_FINISH_SELF.equals(intent.getAction())) {
+                finish();
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Set the fixed orientation if requested
+        if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
+            final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
+            setRequestedOrientation(ori);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        registerReceiver(mReceiver, new IntentFilter(TEST_ACTIVITY_ACTION_FINISH_SELF));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        final Configuration config = getResources().getConfiguration();
+        dumpDisplaySize(config);
+        dumpConfiguration(config);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+        dumpConfiguration(newConfig);
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/component/ComponentsBase.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/component/ComponentsBase.java
new file mode 100644
index 0000000..f6c3471
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/component/ComponentsBase.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.server.am.component;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.ComponentName;
+
+/**
+ * Base class for Components constants holding class.
+ *
+ * Every testing APK should have Components class in Java package that equals to package of APK.
+ * Those Components class should extends {@link ComponentsBase}.
+ */
+public class ComponentsBase {
+
+    /**
+     * Build {@link ComponentName} constant which belongs to {@code componentsClass}'s package.
+     * @param componentsClass Components class object which has the same package of APK.
+     * @param className simple class name (has no '.') or fully qualified class name.
+     * @return {@link ComponentName} object.
+     */
+    protected static ComponentName component(
+            Class<? extends ComponentsBase> componentsClass, String className) {
+        assertFalse("className should not start with '.'", className.startsWith("."));
+        final String packageName = getPackageName(componentsClass);
+        final boolean isSimpleClassName = className.indexOf('.') < 0;
+        final String fullClassName = isSimpleClassName ? packageName + "." + className : className;
+        return new ComponentName(packageName, fullClassName);
+    }
+
+    /**
+     * Get package name of {@code componentsClass}.
+     * @param componentsClass Components class object which has the same package of APK.
+     * @return package name of APK.
+     */
+    protected static String getPackageName(Class<? extends ComponentsBase> componentsClass) {
+        assertEquals("Components", componentsClass.getSimpleName());
+        return componentsClass.getPackage().getName();
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/displayserviceapp/Android.mk
rename to tests/framework/base/activitymanager/displayserviceapp/Android.mk
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/displayserviceapp/app/Android.mk
rename to tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml b/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
new file mode 100644
index 0000000..4051804
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="android.server.am.displayservice">
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <application android:label="CtsDisplayService">
+        <service android:name=".VirtualDisplayService"
+                 android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java b/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
new file mode 100644
index 0000000..bf6b87e
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.displayservice;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Surface;
+
+public class VirtualDisplayService extends Service {
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
+    private static final String TAG = "VirtualDisplayService";
+
+    private static final int FOREGROUND_ID = 1;
+
+    private static final int DENSITY = 160;
+    private static final int HEIGHT = 480;
+    private static final int WIDTH = 800;
+
+    private ImageReader mReader;
+    private VirtualDisplay mVirtualDisplay;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(new NotificationChannel(
+            NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+            NotificationManager.IMPORTANCE_DEFAULT));
+        Notification notif = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(android.R.drawable.ic_dialog_alert)
+                .build();
+        startForeground(FOREGROUND_ID, notif);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        String command = intent.getStringExtra("command");
+        Log.d(TAG, "Got command: " + command);
+
+        if ("create".equals(command)) {
+            createVirtualDisplay(intent);
+        } if ("off".equals(command)) {
+            mVirtualDisplay.setSurface(null);
+        } else if ("on".equals(command)) {
+            mVirtualDisplay.setSurface(mReader.getSurface());
+        } else if ("destroy".equals(command)) {
+            destroyVirtualDisplay();
+            stopSelf();
+        }
+
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private void createVirtualDisplay(Intent intent) {
+        mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
+
+        final DisplayManager displayManager = getSystemService(DisplayManager.class);
+        final String name = "CtsVirtualDisplay";
+
+        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+        if (intent.getBooleanExtra("show_content_when_locked", false /* defaultValue */)) {
+            flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+        }
+        mVirtualDisplay = displayManager.createVirtualDisplay(
+                name, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
+    }
+
+    private void destroyVirtualDisplay() {
+        mVirtualDisplay.release();
+        mReader.close();
+    }
+}
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
new file mode 100644
index 0000000..613888a
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
@@ -0,0 +1,33 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    android-support-test
+
+LOCAL_MODULE := cts-display-service-app-util
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java b/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
new file mode 100644
index 0000000..f541770
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.server.am.displayservice;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DisplayHelper {
+    private static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
+    private static final String VIRTUAL_DISPLAY_SERVICE =
+            "android.server.am.displayservice/.VirtualDisplayService";
+    private static final Pattern mDisplayDevicePattern = Pattern.compile(
+            ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
+
+    private boolean mCreated;
+
+    public DisplayHelper() {
+    }
+
+    public void createAndWaitForDisplay(boolean external, boolean requestShowWhenLocked)
+            {
+        StringBuilder command =
+                new StringBuilder("am startfgservice -n " + VIRTUAL_DISPLAY_SERVICE);
+        command.append(" --es command create");
+        if (external) {
+            command.append(" --ez external_display true");
+        }
+        if (requestShowWhenLocked) {
+            command.append(" --ez show_content_when_locked true");
+        }
+        executeShellCommand(command.toString());
+
+        waitForDisplayState(false /* default */, true /* exists */, true /* on */);
+        mCreated = true;
+    }
+
+    public void turnDisplayOff() {
+        executeShellCommand(
+                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command off");
+        waitForDisplayState(false /* default */, true /* exists */, false /* on */);
+    }
+
+    public void turnDisplayOn() {
+        executeShellCommand(
+                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command on");
+        waitForDisplayState(false /* default */, true /* exists */, true /* on */);
+    }
+
+    public void releaseDisplay() {
+        if (mCreated) {
+            executeShellCommand(
+                    "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command destroy");
+            waitForDisplayState(false /* default */, false /* exists */, true /* on */);
+        }
+        mCreated = false;
+    }
+
+    public static void waitForDefaultDisplayState(boolean wantOn) {
+        waitForDisplayState(true /* default */, true /* exists */, wantOn);
+    }
+
+    public static boolean getDefaultDisplayState() {
+        return getDisplayState(true);
+    }
+
+    private static void waitForDisplayState(boolean defaultDisplay, boolean wantExists, boolean wantOn) {
+        int tries = 0;
+        boolean done = false;
+        do {
+            if (tries > 0) {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    // Oh well
+                }
+            }
+
+            Boolean state = getDisplayState(defaultDisplay);
+            done = (!wantExists && state == null)
+                    || (wantExists && state != null && state == wantOn);
+
+            tries++;
+        } while (tries < 10 && !done);
+
+        assertTrue(done);
+    }
+
+    private static Boolean getDisplayState(boolean defaultDisplay) {
+        String dump = executeShellCommand("dumpsys display");
+
+        boolean displayExists = false;
+        boolean displayOn = false;
+        for (String line : dump.split("\\n")) {
+            Matcher matcher = mDisplayDevicePattern.matcher(line);
+            if (matcher.matches()) {
+                if ((defaultDisplay && line.contains("FLAG_DEFAULT_DISPLAY"))
+                        || (!defaultDisplay && VIRTUAL_DISPLAY_NAME.equals(matcher.group(1)))) {
+                    return "ON".equals(matcher.group(2));
+                }
+            }
+        }
+        return null;
+    }
+
+    private static String executeShellCommand(String command) {
+        try {
+            return SystemUtil
+                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+        } catch (IOException e) {
+            //bubble it up
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
new file mode 100644
index 0000000..21d6c69
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.am.ComponentNameUtils.getLogTag;
+import static android.server.am.Components.LOG_CONFIGURATION_ACTIVITY;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logAlways;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityAndWindowManagerOverrideConfigTests
+ */
+public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
+
+    private static class ConfigurationChangeObserver {
+        private static final Pattern CONFIGURATION_CHANGED_PATTERN =
+            Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
+
+        private ConfigurationChangeObserver() {
+        }
+
+        private boolean findConfigurationChange(
+                ComponentName activityName, LogSeparator logSeparator) {
+            for (int retry = 1; retry <= 5; retry++) {
+                final String[] lines =
+                        getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
+                log("Looking at logcat");
+                for (int i = lines.length - 1; i >= 0; i--) {
+                    final String line = lines[i].trim();
+                    log(line);
+                    Matcher matcher = CONFIGURATION_CHANGED_PATTERN.matcher(line);
+                    if (matcher.matches()) {
+                        return true;
+                    }
+                }
+                logAlways("***Waiting configuration change of " + getLogTag(activityName)
+                        + " retry=" + retry);
+                SystemClock.sleep(500);
+            }
+            return false;
+        }
+    }
+
+    @Test
+    public void testReceiveOverrideConfigFromRelayout() throws Exception {
+        assumeTrue("Device doesn't support freeform. Skipping test.", supportsFreeform());
+
+        launchActivity(LOG_CONFIGURATION_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+            LogSeparator logSeparator = clearLogcat();
+            resizeActivityTask(LOG_CONFIGURATION_ACTIVITY, 0, 0, 100, 100);
+            ConfigurationChangeObserver c = new ConfigurationChangeObserver();
+            final boolean reportedSizeAfterResize = c.findConfigurationChange(
+                    LOG_CONFIGURATION_ACTIVITY, logSeparator);
+            assertTrue("Expected to observe configuration change when resizing",
+                    reportedSizeAfterResize);
+
+            logSeparator = clearLogcat();
+            rotationSession.set(ROTATION_180);
+            final boolean reportedSizeAfterRotation = c.findConfigurationChange(
+                    LOG_CONFIGURATION_ACTIVITY, logSeparator);
+            assertFalse("Not expected to observe configuration change after flip rotation",
+                    reportedSizeAfterRotation);
+        }
+    }
+}
+
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
new file mode 100644
index 0000000..7b76704
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.ActivityManager.StackId.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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.server.am.ActivityManagerState.STATE_PAUSED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+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.DOCKED_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
+import static android.server.am.Components.MOVE_TASK_TO_BACK_ACTIVITY;
+import static android.server.am.Components.NO_HISTORY_ACTIVITY;
+import static android.server.am.Components.SWIPE_REFRESH_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TRANSLUCENT_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_ATTR_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY;
+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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerActivityVisibilityTests
+ */
+public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
+
+    @Rule
+    public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
+
+    @Presubmit
+    @Test
+    public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        executeShellCommand(getAmStartCmdOverHome(LAUNCH_PIP_ON_PIP_ACTIVITY));
+        mAmWmState.waitForValidState(LAUNCH_PIP_ON_PIP_ACTIVITY);
+        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
+        // translucent activity.
+        final int stackId = mAmWmState.getAmState().getStackIdByActivity(
+                LAUNCH_PIP_ON_PIP_ACTIVITY);
+
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+        mAmWmState.computeState(LAUNCH_PIP_ON_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
+        mAmWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
+    }
+
+    /**
+     * Asserts that the home activity is visible when a translucent activity is launched in the
+     * fullscreen stack over the home activity.
+     */
+    @Test
+    public void testTranslucentActivityOnTopOfHome() throws Exception {
+        assumeTrue(hasHomeScreen());
+
+        launchHomeActivity();
+        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+
+        mAmWmState.computeState(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        mAmWmState.assertFrontStack("Fullscreen stack must be the front stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    /**
+     * Assert that the home activity is visible if a task that was launched from home is pinned
+     * and also assert the next task in the fullscreen stack isn't visible.
+     */
+    @Presubmit
+    @Test
+    public void testHomeVisibleOnActivityTaskPinned() throws Exception {
+        assumeTrue(supportsPip());
+
+        launchHomeActivity();
+        launchActivity(TEST_ACTIVITY);
+        launchHomeActivity();
+        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        final int stackId = mAmWmState.getAmState().getStackIdByActivity(
+                ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+        mAmWmState.computeState(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+
+        mAmWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    @Presubmit
+    @Test
+    public void testTranslucentActivityOverDockedStack() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        launchActivity(TRANSLUCENT_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(TEST_ACTIVITY),
+                new WaitForValidActivityState(DOCKED_ACTIVITY),
+                new WaitForValidActivityState(TRANSLUCENT_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
+    }
+
+    @Presubmit
+    @FlakyTest(bugId = 72526786)
+    @Test
+    public void testTurnScreenOnActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+            launchActivity(TURN_SCREEN_ON_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+            assertTrue(isDisplayOn());
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testFinishActivityInNonFocusedStack() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Launch two activities in docked stack.
+        launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+        getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).execute();
+        mAmWmState.computeState(BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+        // Launch something to fullscreen stack to make it focused.
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+        // Finish activity in non-focused (docked) stack.
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_PAUSED);
+        mAmWmState.waitForAllExitingWindows();
+
+        mAmWmState.computeState(LAUNCHING_ACTIVITY);
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
+    }
+
+    @Test
+    public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+        performFinishActivityWithMoveTaskToBack("on_pause");
+    }
+
+    @Test
+    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+        performFinishActivityWithMoveTaskToBack("on_stop");
+    }
+
+    private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+        // Make sure home activity is visible.
+        launchHomeActivity();
+        if (hasHomeScreen()) {
+            mAmWmState.assertHomeActivityVisible(true /* visible */);
+        }
+
+        // Launch an activity that calls "moveTaskToBack" to finish itself.
+        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY, "finish_point", finishPoint);
+        mAmWmState.waitForValidState(MOVE_TASK_TO_BACK_ACTIVITY);
+        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY, true);
+
+        // Launch a different activity on top.
+        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.waitForValidState(BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
+        mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY, false);
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+
+        // Finish the top-most activity.
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        //TODO: BUG: MoveTaskToBackActivity returns to the top of the stack when
+        // BroadcastActivity finishes, so homeActivity is not visible afterwards
+
+        // Home must be visible.
+        if (hasHomeScreen()) {
+            mAmWmState.waitForHomeActivityVisible();
+            mAmWmState.assertHomeActivityVisible(true /* visible */);
+        }
+    }
+
+    /**
+     * Asserts that launching between reorder to front activities exhibits the correct backstack
+     * behavior.
+     */
+    @Test
+    public void testReorderToFrontBackstack() throws Exception {
+        // Start with home on top
+        launchHomeActivity();
+        if (hasHomeScreen()) {
+            mAmWmState.assertHomeActivityVisible(true /* visible */);
+        }
+
+        // Launch the launching activity to the foreground
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        // Launch the alternate launching activity from launching activity with reorder to front.
+        getLaunchActivityBuilder().setTargetActivity(ALT_LAUNCHING_ACTIVITY)
+                .setReorderToFront(true).execute();
+
+        // Launch the launching activity from the alternate launching activity with reorder to
+        // front.
+        getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)
+                .setLaunchingActivity(ALT_LAUNCHING_ACTIVITY)
+                .setReorderToFront(true)
+                .execute();
+
+        // Press back
+        pressBackButton();
+
+        mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
+
+        // Ensure the alternate launching activity is in focus
+        mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
+                ALT_LAUNCHING_ACTIVITY);
+    }
+
+    /**
+     * Asserts that the activity focus and history is preserved moving between the activity and
+     * home stack.
+     */
+    @Test
+    public void testReorderToFrontChangingStack() throws Exception {
+        // Start with home on top
+        launchHomeActivity();
+        if (hasHomeScreen()) {
+            mAmWmState.assertHomeActivityVisible(true /* visible */);
+        }
+
+        // Launch the launching activity to the foreground
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        // Launch the alternate launching activity from launching activity with reorder to front.
+        getLaunchActivityBuilder().setTargetActivity(ALT_LAUNCHING_ACTIVITY)
+                .setReorderToFront(true)
+                .execute();
+
+        // Return home
+        launchHomeActivity();
+        if (hasHomeScreen()) {
+            mAmWmState.assertHomeActivityVisible(true /* visible */);
+        }
+        // Launch the launching activity from the alternate launching activity with reorder to
+        // front.
+
+        // Bring launching activity back to the foreground
+        launchActivity(LAUNCHING_ACTIVITY);
+        mAmWmState.waitForValidState(LAUNCHING_ACTIVITY);
+
+        // Ensure the alternate launching activity is still in focus.
+        mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
+                ALT_LAUNCHING_ACTIVITY);
+
+        pressBackButton();
+
+        mAmWmState.waitForValidState(LAUNCHING_ACTIVITY);
+
+        // Ensure launching activity was brought forward.
+        mAmWmState.assertFocusedActivity("Launching Activity must be focused",
+                LAUNCHING_ACTIVITY);
+    }
+
+    /**
+     * Asserts that a nohistory activity is stopped and removed immediately after a resumed activity
+     * above becomes visible and does not idle.
+     */
+    @Test
+    public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
+        assumeTrue(hasHomeScreen());
+
+        // Start with home on top
+        launchHomeActivity();
+
+        // Launch no history activity
+        launchActivity(NO_HISTORY_ACTIVITY);
+
+        // Launch an activity with a swipe refresh layout configured to prevent idle.
+        launchActivity(SWIPE_REFRESH_ACTIVITY);
+
+        pressBackButton();
+        mAmWmState.waitForHomeActivityVisible();
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    @Test
+    public void testTurnScreenOnAttrNoLockScreen() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.disableLockScreen()
+                    .sleepDevice();
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
+            assertTrue(isDisplayOn());
+            assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testTurnScreenOnAttrWithLockScreen() throws Exception {
+        assumeTrue(isHandheld());
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .sleepDevice();
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_ATTR_ACTIVITY);
+            assertFalse(isDisplayOn());
+            assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testTurnScreenOnShowOnLockAttr() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+            mAmWmState.waitForAllStoppedActivities();
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
+            assertTrue(isDisplayOn());
+            assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testTurnScreenOnAttrRemove() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+            mAmWmState.waitForAllStoppedActivities();
+            LogSeparator logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+            assertTrue(isDisplayOn());
+            assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+
+            lockScreenSession.sleepDevice();
+            mAmWmState.waitForAllStoppedActivities();
+            logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
+            assertFalse(isDisplayOn());
+            assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    @FlakyTest(bugId = 74034092)
+    @Presubmit
+    public void testTurnScreenOnSingleTask() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+            LogSeparator logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
+            assertTrue(isDisplayOn());
+            assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, logSeparator);
+
+            lockScreenSession.sleepDevice();
+            logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
+            assertTrue(isDisplayOn());
+            assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testTurnScreenOnActivity_withRelayout() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+            launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
+            mAmWmState.computeState(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
+
+            LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.sleepDevice();
+            mAmWmState.waitFor("Waiting for stopped state", () ->
+                    lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
+
+            // Ensure there was an actual stop if the waitFor timed out.
+            assertTrue(lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
+            assertFalse(isDisplayOn());
+        }
+    }
+
+    private boolean lifecycleStopOccurred(ComponentName activityName, LogSeparator logSeparator) {
+        ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+                logSeparator);
+        return lifecycleCounts.mStopCount > 0;
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java
new file mode 100644
index 0000000..3adec21
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.debuggable.Components.DEBUGGABLE_APP_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerAmProfileTests
+ *
+ * Please talk to Android Studio team first if you want to modify or delete these tests.
+ */
+public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
+
+    private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
+    private static final String FIRST_WORD_NO_STREAMING = "*version\n";
+    private static final String FIRST_WORD_STREAMING = "SLOW";  // Magic word set by runtime.
+
+    private String mReadableFilePath = null;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mReadableFilePath = InstrumentationRegistry.getContext()
+            .getExternalFilesDir(null)
+            .getPath() + "/profile.trace";
+    }
+
+    /**
+     * Test am profile functionality with the following 3 configurable options:
+     *    starting the activity before start profiling? yes;
+     *    sampling-based profiling? no;
+     *    using streaming output mode? no.
+     */
+    @Test
+    public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
+        // am profile start ... , and the same to the following 3 test methods.
+        testProfile(true, false, false);
+    }
+
+    /**
+     * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
+     * only different in the three configuration options.
+     */
+    @Test
+    public void testAmProfileStartNoSamplingStreaming() throws Exception {
+        testProfile(true, false, true);
+    }
+
+    @Test
+    public void testAmProfileStartSamplingNoStreaming() throws Exception {
+        testProfile(true, true, false);
+    }
+
+    @Test
+    public void testAmProfileStartSamplingStreaming() throws Exception {
+        testProfile(true, true, true);
+    }
+
+    @Test
+    public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
+        // am start --start-profiler ..., and the same to the following 3 test methods.
+        testProfile(false, false, false);
+    }
+
+    @Test
+    public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
+        testProfile(false, false, true);
+    }
+
+    @Test
+    public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
+        testProfile(false, true, false);
+    }
+
+    @Test
+    public void testAmStartStartProfilerSamplingStreaming() throws Exception {
+        testProfile(false, true, true);
+    }
+
+    private void testProfile(final boolean startActivityFirst, final boolean sampling,
+            final boolean streaming) throws Exception {
+        if (startActivityFirst) {
+            launchActivity(DEBUGGABLE_APP_ACTIVITY);
+        }
+
+        executeShellCommand(
+                getStartCmd(DEBUGGABLE_APP_ACTIVITY, startActivityFirst, sampling, streaming));
+        // Go to home screen and then warm start the activity to generate some interesting trace.
+        pressHomeButton();
+        launchActivity(DEBUGGABLE_APP_ACTIVITY);
+
+        executeShellCommand(getStopProfileCmd(DEBUGGABLE_APP_ACTIVITY));
+        // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
+        // file is complete.
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            //ignored
+        }
+        verifyOutputFileFormat(streaming);
+    }
+
+    private static String getStartCmd(final ComponentName activityName,
+            final boolean activityAlreadyStarted, final boolean sampling, final boolean streaming) {
+        final StringBuilder builder = new StringBuilder("am");
+        if (activityAlreadyStarted) {
+            builder.append(" profile start");
+        } else {
+            builder.append(String.format(" start -n %s -W -S --start-profiler %s",
+                    getActivityName(activityName), OUTPUT_FILE_PATH));
+        }
+        if (sampling) {
+            builder.append(" --sampling 1000");
+        }
+        if (streaming) {
+            builder.append(" --streaming");
+        }
+        if (activityAlreadyStarted) {
+            builder.append(String.format(
+                    " %s %s", activityName.getPackageName(), OUTPUT_FILE_PATH));
+        }
+        return builder.toString();
+    }
+
+    private static String getStopProfileCmd(final ComponentName activityName) {
+        return "am profile stop " + activityName.getPackageName();
+    }
+
+    private void verifyOutputFileFormat(final boolean streaming) throws Exception {
+        // This is a hack. The am service has to write to /data/local/tmp because it doesn't have
+        // access to the sdcard but the test app can't read there
+        executeShellCommand("mv " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
+
+        final String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
+        final byte[] data = readFile(mReadableFilePath);
+        assertTrue("data size=" + data.length, data.length >= expectedFirstWord.length());
+        final String actualFirstWord = new String(data, 0, expectedFirstWord.length());
+        assertEquals("Unexpected first word", expectedFirstWord, actualFirstWord);
+
+        // Clean up.
+        executeShellCommand("rm -f " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
+    }
+
+    private static byte[] readFile(String clientPath) throws Exception {
+        final File file = new File(clientPath);
+        assertTrue("File not found on client: " + clientPath, file.isFile());
+        final int size = (int) file.length();
+        final byte[] bytes = new byte[size];
+        try (final FileInputStream fis = new FileInputStream(file)) {
+            final int readSize = fis.read(bytes, 0, bytes.length);
+            assertEquals("Read all data", bytes.length, readSize);
+            return bytes;
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java
new file mode 100644
index 0000000..ab73fcd
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.ENTRY_POINT_ALIAS_ACTIVITY;
+import static android.server.am.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerAmStartOptionsTests
+ */
+public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
+
+    @Test
+    public void testDashD() throws Exception {
+        // Run at least 2 rounds to verify that -D works with an existing process.
+        // -D could fail in this case if the force stop of process is broken.
+        int prevProcId = -1;
+        for (int i = 0; i < 2; i++) {
+            executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY) + " -D");
+
+            mAmWmState.waitForDebuggerWindowVisible(TEST_ACTIVITY);
+            int procId = mAmWmState.getAmState().getActivityProcId(TEST_ACTIVITY);
+
+            assertTrue("Invalid ProcId.", procId >= 0);
+            if (i > 0) {
+                assertTrue("Run " + i + " didn't start new proc.", prevProcId != procId);
+            }
+            prevProcId = procId;
+        }
+    }
+
+    @Test
+    public void testDashW_Direct() throws Exception {
+        testDashW(SINGLE_TASK_ACTIVITY, SINGLE_TASK_ACTIVITY);
+    }
+
+    @Test
+    public void testDashW_Indirect() throws Exception {
+        testDashW(ENTRY_POINT_ALIAS_ACTIVITY, SINGLE_TASK_ACTIVITY);
+    }
+
+    private void testDashW(final ComponentName entryActivity, final ComponentName actualActivity)
+            throws Exception {
+        // Test cold start
+        startActivityAndVerifyResult(entryActivity, actualActivity, true);
+
+        // Test warm start
+        pressHomeButton();
+        startActivityAndVerifyResult(entryActivity, actualActivity, false);
+
+        // Test "hot" start (app already in front)
+        startActivityAndVerifyResult(entryActivity, actualActivity, false);
+    }
+
+    private void startActivityAndVerifyResult(final ComponentName entryActivity,
+            final ComponentName actualActivity, boolean shouldStart) throws Exception {
+        // See TODO below
+        // final LogSeparator logSeparator = clearLogcat();
+
+        // Pass in different data only when cold starting. This is to make the intent
+        // different in subsequent warm/hot launches, so that the entrypoint alias
+        // activity is always started, but the actual activity is not started again
+        // because of the NEW_TASK and singleTask flags.
+        final String result = executeShellCommand(
+                "am start -n " + getActivityName(entryActivity) + " -W"
+                + (shouldStart ? " -d about:blank" : ""));
+
+        // Verify shell command return value
+        verifyShellOutput(result, actualActivity, shouldStart);
+
+        // TODO: Disable logcat check for now.
+        // Logcat of WM or AM tag could be lost (eg. chatty if earlier events generated
+        // too many lines), and make the test look flaky. We need to either use event
+        // log or swith to other mechanisms. Only verify shell output for now, it should
+        // still catch most failures.
+
+        // Verify adb logcat log
+        //verifyLogcat(actualActivity, shouldStart, logSeparator);
+    }
+
+    private static final Pattern sNotStartedWarningPattern = Pattern.compile(
+            "Warning: Activity not started(.*)");
+    private static final Pattern sStatusPattern = Pattern.compile(
+            "Status: (.*)");
+    private static final Pattern sActivityPattern = Pattern.compile(
+            "Activity: (.*)");
+    private static final String sStatusOk = "ok";
+
+    private void verifyShellOutput(
+            final String result, final ComponentName activity, boolean shouldStart) {
+        boolean warningFound = false;
+        String status = null;
+        String reportedActivity = null;
+
+        final String[] lines = result.split("\\n");
+        // Going from the end of logs to beginning in case if some other activity is started first.
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            Matcher matcher = sNotStartedWarningPattern.matcher(line);
+            if (matcher.matches()) {
+                warningFound = true;
+                continue;
+            }
+            matcher = sStatusPattern.matcher(line);
+            if (matcher.matches()) {
+                status = matcher.group(1);
+                continue;
+            }
+            matcher = sActivityPattern.matcher(line);
+            if (matcher.matches()) {
+                reportedActivity = matcher.group(1);
+                continue;
+            }
+        }
+
+        assertEquals("Status is ok", sStatusOk, status);
+        assertEquals("Reported activity is " +  getActivityName(activity),
+                getActivityName(activity), reportedActivity);
+
+        if (shouldStart && warningFound) {
+            fail("Should start new activity but brought something to front.");
+        } else if (!shouldStart && !warningFound){
+            fail("Should bring existing activity to front but started new activity.");
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
new file mode 100644
index 0000000..834c7a6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityAndWindowManagersState.dpToPx;
+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.DIALOG_WHEN_LARGE_ACTIVITY;
+import static android.server.am.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.NIGHT_MODE_ACTIVITY;
+import static android.server.am.Components.PORTRAIT_ORIENTATION_ACTIVITY;
+import static android.server.am.Components.RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.server.am.translucentapp.Components.TRANSLUCENT_LANDSCAPE_ACTIVITY;
+import static android.server.am.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerAppConfigurationTests
+ */
+public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
+
+    private static final int SMALL_WIDTH_DP = 426;
+    private static final int SMALL_HEIGHT_DP = 320;
+
+    /**
+     * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
+     * has an updated size when the Activity is resized from fullscreen to docked state.
+     *
+     * The Activity handles configuration changes, so it will not be restarted between resizes.
+     * On Configuration changes, the Activity logs the Display size and Configuration width
+     * and heights. The values reported in fullscreen should be larger than those reported in
+     * docked state.
+     */
+    @Test
+    public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                logSeparator);
+
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                logSeparator);
+
+        assertSizesAreSane(fullscreenSizes, dockedSizes);
+    }
+
+    /**
+     * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
+     * from docked state to fullscreen (reverse).
+     */
+    @Presubmit
+    @Test
+    @FlakyTest(bugId = 71792393)
+    public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                logSeparator);
+
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                logSeparator);
+
+        assertSizesAreSane(fullscreenSizes, dockedSizes);
+    }
+
+    /**
+     * Tests whether the Display sizes change when rotating the device.
+     */
+    @Test
+    public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(RESIZEABLE_ACTIVITY,
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                    logSeparator);
+
+            rotateAndCheckSizes(rotationSession, initialSizes);
+        }
+    }
+
+    /**
+     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
+     * is in the docked stack.
+     */
+    @Presubmit
+    @Test
+    public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            final LogSeparator logSeparator = clearLogcat();
+            // Launch our own activity to side in case Recents (or other activity to side) doesn't
+            // support rotation.
+            launchActivitiesInSplitScreen(
+                    getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                    getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+            // Launch target activity in docked stack.
+            getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                    logSeparator);
+
+            rotateAndCheckSizes(rotationSession, initialSizes);
+        }
+    }
+
+    /**
+     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
+     * is launched to side from docked stack.
+     */
+    @Presubmit
+    @Test
+    public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivitiesInSplitScreen(
+                    getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                    getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                    logSeparator);
+
+            rotateAndCheckSizes(rotationSession, initialSizes);
+        }
+    }
+
+    private void rotateAndCheckSizes(RotationSession rotationSession, ReportedSizes prevSizes)
+            throws Exception {
+        final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
+        for (final int rotation : rotations) {
+            final LogSeparator logSeparator = clearLogcat();
+            final ActivityManagerState.ActivityTask task =
+                    mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY);
+            final int displayId = mAmWmState.getAmState().getStackById(task.mStackId).mDisplayId;
+            rotationSession.set(rotation);
+            final int newDeviceRotation = getDeviceRotation(displayId);
+            if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
+                logE("Got an invalid device rotation value. "
+                        + "Continuing the test despite of that, but it is likely to fail.");
+            } else if (rotation != newDeviceRotation) {
+                log("This device doesn't support locked user "
+                        + "rotation mode. Not continuing the rotation checks.");
+                return;
+            }
+
+            final ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
+                    logSeparator);
+            assertSizesRotate(prevSizes, rotatedSizes,
+                    // Skip orientation checks if we are not in fullscreen mode.
+                    task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN);
+            prevSizes = rotatedSizes;
+        }
+    }
+
+    /**
+     * Tests when activity moved from fullscreen stack to docked and back. Activity will be
+     * relaunched twice and it should have same config as initial one.
+     */
+    @Test
+    public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
+        moveActivityFullSplitFull(TEST_ACTIVITY);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
+     */
+    @Presubmit
+    @Test
+    public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
+        moveActivityFullSplitFull(RESIZEABLE_ACTIVITY);
+    }
+
+    /**
+     * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
+     * Last operation is done in a way which simulates split-screen divider movement maximizing
+     * docked stack size and then moving task to fullscreen stack - the same way it is done when
+     * user long-presses overview/recents button to exit split-screen.
+     * Asserts that initial and final reported sizes in fullscreen stack are the same.
+     */
+    private void moveActivityFullSplitFull(ComponentName activityName) throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Launch to fullscreen stack and record size.
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
+                logSeparator);
+        final Rect displayRect = getDisplayRect(activityName);
+
+        // Move to docked stack.
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
+        assertSizesAreSane(initialFullscreenSizes, dockedSizes);
+        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(activityName).build());
+        final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
+                .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Resize docked stack to fullscreen size. This will trigger activity relaunch with
+        // non-empty override configuration corresponding to fullscreen size.
+        logSeparator = clearLogcat();
+        mAm.resizeStack(stack.mStackId, displayRect);
+
+        // Move activity back to fullscreen stack.
+        setActivityTaskWindowingMode(activityName,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
+                logSeparator);
+
+        // After activity configuration was changed twice it must report same size as original one.
+        assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
+    }
+
+    /**
+     * Tests when activity moved from docked stack to fullscreen and back. Activity will be
+     * relaunched twice and it should have same config as initial one.
+     */
+    @Test
+    public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
+        moveActivitySplitFullSplit(TEST_ACTIVITY);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
+     */
+    @Test
+    public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
+        moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY);
+    }
+
+    /**
+     * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
+     * screen.
+     */
+    @Presubmit
+    @Test
+    public void testDialogWhenLargeSplitSmall() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        launchActivity(DIALOG_WHEN_LARGE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
+                .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final WindowManagerState.Display display =
+                mAmWmState.getWmState().getDisplay(stack.mDisplayId);
+        final int density = display.getDpi();
+        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));
+        mAmWmState.waitForValidState(
+                new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
+                        .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                        .setActivityType(ACTIVITY_TYPE_STANDARD)
+                        .build());
+    }
+
+    /**
+     * Test that device handles consequent requested orientations and displays the activities.
+     */
+    @Presubmit
+    @Test
+    @FlakyTest(bugId = 71875755)
+    public void testFullscreenAppOrientationRequests() throws Exception {
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
+        mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
+        ReportedSizes reportedSizes =
+                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        assertEquals("portrait activity should be in portrait",
+                1 /* portrait */, reportedSizes.orientation);
+        logSeparator = clearLogcat();
+
+        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+        mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
+        reportedSizes =
+                getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator);
+        assertEquals("landscape activity should be in landscape",
+                2 /* landscape */, reportedSizes.orientation);
+        logSeparator = clearLogcat();
+
+        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
+        mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
+        reportedSizes =
+                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        assertEquals("portrait activity should be in portrait",
+                1 /* portrait */, reportedSizes.orientation);
+        logSeparator = clearLogcat();
+    }
+
+    @Test
+    public void testNonfullscreenAppOrientationRequests() throws Exception {
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
+        final ReportedSizes initialReportedSizes =
+                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        assertEquals("portrait activity should be in portrait",
+                1 /* portrait */, initialReportedSizes.orientation);
+        logSeparator = clearLogcat();
+
+        launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+        assertEquals("Legacy non-fullscreen activity requested landscape orientation",
+                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+        // TODO(b/36897968): uncomment once we can suppress unsupported configurations
+        // final ReportedSizes updatedReportedSizes =
+        //      getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
+        // assertEquals("portrait activity should not have moved from portrait",
+        //         1 /* portrait */, updatedReportedSizes.orientation);
+    }
+
+    // TODO(b/70870253): This test seems malfunction.
+    @Ignore("b/70870253")
+    @Test
+    public void testNonFullscreenActivityProhibited() throws Exception {
+        // We do not wait for the activity as it should not launch based on the restrictions around
+        // specifying orientation. We instead start an activity known to launch immediately after
+        // so that we can ensure processing the first activity occurred.
+        launchActivityNoWait(TRANSLUCENT_LANDSCAPE_ACTIVITY);
+        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
+
+        assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
+                mAmWmState.getAmState().containsActivity(TRANSLUCENT_LANDSCAPE_ACTIVITY));
+    }
+
+    @Test
+    public void testNonFullscreenActivityPermitted() throws Exception {
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+            mAmWmState.assertResumedActivity(
+                    "target SDK <= 26 non-fullscreen activity should be allowed to launch",
+                    SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+            assertEquals("non-fullscreen activity requested landscape orientation",
+                    0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+        }
+    }
+
+    /**
+     * Test that device handles moving between two tasks with different orientations.
+     */
+    @Test
+    public void testTaskCloseRestoreOrientation() throws Exception {
+        // Start landscape activity.
+        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+        mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
+        assertEquals("Fullscreen app requested landscape orientation",
+                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+        // Start another activity in a different task.
+        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+        // Request portrait
+        executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+        mAmWmState.waitForRotation(1);
+
+        // Finish activity
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        // Verify that activity brought to front is in originally requested orientation.
+        mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
+        assertEquals("Should return to app in landscape orientation",
+                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+    }
+
+    /**
+     * Test that device handles moving between two tasks with different orientations.
+     */
+    @Presubmit
+    @Test
+    @FlakyTest(bugId = 71792393)
+    public void testTaskMoveToBackOrientation() throws Exception {
+        // Start landscape activity.
+        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+        mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
+        assertEquals("Fullscreen app requested landscape orientation",
+                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+        // Start another activity in a different task.
+        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+        // Request portrait
+        executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+        mAmWmState.waitForRotation(1);
+
+        // Finish activity
+        executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
+
+        // Verify that activity brought to front is in originally requested orientation.
+        mAmWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
+        assertEquals("Should return to app in landscape orientation",
+                0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+    }
+
+    /**
+     * Test that device doesn't change device orientation by app request while in multi-window.
+     */
+    @Presubmit
+    @FlakyTest(bugId = 71918731)
+    @Test
+    public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            requestOrientationInSplitScreen(rotationSession,
+                    ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
+        }
+    }
+
+    /**
+     * Test that device doesn't change device orientation by app request while in multi-window.
+     */
+    @Presubmit
+    @Test
+    public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            requestOrientationInSplitScreen(rotationSession,
+                    ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY);
+        }
+    }
+
+    /**
+     * Rotate the device and launch specified activity in split-screen, checking if orientation
+     * didn't change.
+     */
+    private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation,
+            ComponentName activity) throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Set initial orientation.
+        rotationSession.set(orientation);
+
+        // Launch activities that request orientations and check that device doesn't rotate.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true));
+
+        mAmWmState.assertVisibility(activity, true /* visible */);
+        assertEquals("Split-screen apps shouldn't influence device orientation",
+                orientation, mAmWmState.getWmState().getRotation());
+
+        getLaunchActivityBuilder().setMultipleTask(true).setTargetActivity(activity).execute();
+        mAmWmState.computeState(activity);
+        mAmWmState.assertVisibility(activity, true /* visible */);
+        assertEquals("Split-screen apps shouldn't influence device orientation",
+                orientation, mAmWmState.getWmState().getRotation());
+    }
+
+    /**
+     * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
+     * Asserts that initial and final reported sizes in docked stack are the same.
+     */
+    private void moveActivitySplitFullSplit(ComponentName activityName) throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Launch to docked stack and record size.
+        LogSeparator logSeparator = clearLogcat();
+        launchActivityInSplitScreenWithRecents(activityName);
+        final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(activityName).build());
+
+        // Move to fullscreen stack.
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(
+                activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
+        assertSizesAreSane(fullscreenSizes, initialDockedSizes);
+
+        // Move activity back to docked stack.
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+
+        // After activity configuration was changed twice it must report same size as original one.
+        assertSizesAreSame(initialDockedSizes, finalDockedSizes);
+    }
+
+    /**
+     * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
+     * have flipped.
+     */
+    private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes rotationB,
+            boolean skipOrientationCheck) throws Exception {
+        assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
+        assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
+        assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
+        assertEquals(rotationB.displayHeight, rotationB.metricsHeight);
+
+        if (skipOrientationCheck) {
+            // All done if we are not doing orientation check.
+            return;
+        }
+        final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
+        final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
+        assertFalse(beforePortrait == afterPortrait);
+
+        final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
+        final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
+        assertEquals(beforePortrait, beforeConfigPortrait);
+        assertEquals(afterPortrait, afterConfigPortrait);
+
+        assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
+    }
+
+    /**
+     * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
+     * that are smaller than the dockedSizes.
+     */
+    private static void assertSizesAreSane(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
+            throws Exception {
+        final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
+        if (portrait) {
+            assertTrue(dockedSizes.displayHeight < fullscreenSizes.displayHeight);
+            assertTrue(dockedSizes.heightDp < fullscreenSizes.heightDp);
+            assertTrue(dockedSizes.metricsHeight < fullscreenSizes.metricsHeight);
+        } else {
+            assertTrue(dockedSizes.displayWidth < fullscreenSizes.displayWidth);
+            assertTrue(dockedSizes.widthDp < fullscreenSizes.widthDp);
+            assertTrue(dockedSizes.metricsWidth < fullscreenSizes.metricsWidth);
+        }
+    }
+
+    /**
+     * Throws an AssertionError if sizes are different.
+     */
+    private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
+            throws Exception {
+        assertEquals(firstSize.widthDp, secondSize.widthDp);
+        assertEquals(firstSize.heightDp, secondSize.heightDp);
+        assertEquals(firstSize.displayWidth, secondSize.displayWidth);
+        assertEquals(firstSize.displayHeight, secondSize.displayHeight);
+        assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
+        assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
+        assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
+    }
+
+    private ReportedSizes getActivityDisplaySize(ComponentName activityName,
+            LogSeparator logSeparator) throws Exception {
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(activityName));
+        final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
+        assertNotNull(details);
+        return details;
+    }
+
+    private Rect getDisplayRect(ComponentName activityName)
+            throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(activityName);
+        mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+
+        final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
+
+        assertEquals("Should have exactly one window state for the activity.", 1,
+                tempWindowList.size());
+
+        WindowManagerState.WindowState windowState = tempWindowList.get(0);
+        assertNotNull("Should have a valid window", windowState);
+
+        WindowManagerState.Display display = mAmWmState.getWmState()
+                .getDisplay(windowState.getDisplayId());
+        assertNotNull("Should be on a display", display);
+
+        return display.getDisplayRect();
+    }
+
+    /**
+     * Test launching an activity which requests specific UI mode during creation.
+     */
+    @Test
+    public void testLaunchWithUiModeChange() throws Exception {
+        // Launch activity that changes UI mode and handles this configuration change.
+        launchActivity(NIGHT_MODE_ACTIVITY);
+        mAmWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED);
+
+        // Check if activity is launched successfully.
+        mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
+        mAmWmState.assertFocusedActivity("Launched activity should be focused",
+                NIGHT_MODE_ACTIVITY);
+        mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_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
new file mode 100644
index 0000000..84c22d2
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.ANIMATION_TEST_ACTIVITY;
+import static android.server.am.Components.ASSISTANT_ACTIVITY;
+import static android.server.am.Components.ASSISTANT_VOICE_INTERACTION_SERVICE;
+import static android.server.am.Components.AssistantActivity.EXTRA_ASSISTANT_DISPLAY_ID;
+import static android.server.am.Components.AssistantActivity.EXTRA_ENTER_PIP;
+import static android.server.am.Components.AssistantActivity.EXTRA_FINISH_SELF;
+import static android.server.am.Components.AssistantActivity.EXTRA_LAUNCH_NEW_TASK;
+import static android.server.am.Components.DOCKED_ACTIVITY;
+import static android.server.am.Components.LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION;
+import static android.server.am.Components.LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK;
+import static android.server.am.Components.LaunchAssistantActivityIntoAssistantStack
+        .EXTRA_IS_TRANSLUCENT;
+import static android.server.am.Components.PIP_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TRANSLUCENT_ASSISTANT_ACTIVITY;
+import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerAssistantStackTests
+ */
+//@Presubmit b/67706642
+@FlakyTest(bugId = 71875631)
+public class ActivityManagerAssistantStackTests extends ActivityManagerTestBase {
+
+    private int mAssistantDisplayId = DEFAULT_DISPLAY_ID;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK);
+            waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            ActivityManagerState.ActivityStack assistantStack =
+                    mAmWmState.getAmState().getStackByActivityType(ACTIVITY_TYPE_ASSISTANT);
+            mAssistantDisplayId = assistantStack.mDisplayId;
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchingAssistantActivityIntoAssistantStack() throws Exception {
+        // Enable the assistant and launch an assistant activity
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+            waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+
+            // Ensure that the activity launched in the fullscreen assistant stack
+            assertAssistantStackExists();
+            assertTrue("Expected assistant stack to be fullscreen",
+                    mAmWmState.getAmState().getStackByActivityType(
+                            ACTIVITY_TYPE_ASSISTANT).isFullscreen());
+        }
+    }
+
+    @FlakyTest(bugId = 69573940)
+    @Presubmit
+    @Test
+    public void testAssistantStackZOrder() throws Exception {
+        assumeTrue(assistantRunsOnPrimaryDisplay());
+        assumeTrue(supportsPip());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Launch a pinned stack task
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        waitForValidStateWithActivityTypeAndWindowingMode(
+                PIP_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_PINNED);
+        mAmWmState.assertContainsStack("Must contain pinned stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+        // Dock a task
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        // Enable the assistant and launch an assistant activity, ensure it is on top
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+            waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+
+            mAmWmState.assertFrontStack("Pinned stack should be on top.",
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+            mAmWmState.assertFocusedStack("Assistant stack should be focused.",
+                    WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testAssistantStackLaunchNewTask() throws Exception {
+        assertAssistantStackCanLaunchAndReturnFromNewTask();
+    }
+
+    @Test
+    @Presubmit
+    public void testAssistantStackLaunchNewTaskWithDockedStack() throws Exception {
+        assumeTrue(assistantRunsOnPrimaryDisplay());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Dock a task
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        assertAssistantStackCanLaunchAndReturnFromNewTask();
+    }
+
+    private void assertAssistantStackCanLaunchAndReturnFromNewTask() throws Exception {
+        final boolean inSplitScreenMode = mAmWmState.getAmState().containsStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        // Enable the assistant and launch an assistant activity which will launch a new task
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            launchActivityOnDisplay(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK, mAssistantDisplayId,
+                    EXTRA_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY),
+                    EXTRA_ASSISTANT_DISPLAY_ID, Integer.toString(mAssistantDisplayId));
+        }
+
+        final int expectedWindowingMode = inSplitScreenMode
+                ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                : WINDOWING_MODE_FULLSCREEN;
+        // Ensure that the fullscreen stack is on top and the test activity is now visible
+        waitForValidStateWithActivityTypeAndWindowingMode(
+                TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, expectedWindowingMode);
+        mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+        mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
+                expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
+                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);
+        mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+        mAmWmState.assertFrontStackActivityType(
+                "Assistant stack should be on top.", ACTIVITY_TYPE_ASSISTANT);
+        mAmWmState.assertFocusedStack("Assistant stack should be focused.",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71875631)
+    public void testAssistantStackFinishToPreviousApp() throws Exception {
+        // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
+        // the fullscreen activity is still visible and on top after the assistant activity finishes
+        launchActivityOnDisplay(TEST_ACTIVITY, mAssistantDisplayId);
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_FINISH_SELF, "true");
+        }
+        waitForValidStateWithActivityTypeAndWindowingMode(
+                TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
+        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+        mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+        mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71875631)
+    public void testDisallowEnterPiPFromAssistantStack() throws Exception {
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_ENTER_PIP, "true");
+        }
+        waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+        mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @FlakyTest(bugId = 69573940)
+    @Presubmit
+    @Test
+    public void testTranslucentAssistantActivityStackVisibility() throws Exception {
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            // Go home, launch the assistant and check to see that home is visible
+            removeStacksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
+                    WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+            launchHomeActivity();
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_IS_TRANSLUCENT, "true");
+            waitForValidStateWithActivityType(
+                    TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+            mAmWmState.waitForHomeActivityVisible();
+            if (hasHomeScreen()) {
+                mAmWmState.assertHomeActivityVisible(true);
+            }
+
+            // Launch a fullscreen app and then launch the assistant and check to see that it is
+            // also visible
+            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            launchActivityOnDisplay(TEST_ACTIVITY, mAssistantDisplayId);
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_IS_TRANSLUCENT, "true");
+            waitForValidStateWithActivityType(
+                    TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+            // Go home, launch assistant, launch app into fullscreen with activity present, and go
+            // back.Ensure home is visible.
+            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            launchHomeActivity();
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_IS_TRANSLUCENT, "true",
+                    EXTRA_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY));
+            waitForValidStateWithActivityTypeAndWindowingMode(
+                    TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
+            mAmWmState.assertHomeActivityVisible(false);
+            pressBackButton();
+            mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+            mAmWmState.waitForHomeActivityVisible();
+            if (hasHomeScreen()) {
+                mAmWmState.assertHomeActivityVisible(true);
+            }
+
+            // Launch a fullscreen and docked app and then launch the assistant and check to see
+            // that it
+            // is also visible
+            if (supportsSplitScreenMultiWindow() &&  assistantRunsOnPrimaryDisplay()) {
+                removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+                launchActivitiesInSplitScreen(
+                        getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+                mAmWmState.assertContainsStack("Must contain docked stack.",
+                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+                launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                        EXTRA_IS_TRANSLUCENT, "true");
+                waitForValidStateWithActivityType(
+                        TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+                assertAssistantStackExists();
+                mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
+                mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+            }
+        }
+    }
+
+    @FlakyTest(bugId = 69229402)
+    @Test
+    @Presubmit
+    public void testLaunchIntoSameTask() throws Exception {
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            // Launch the assistant
+            launchActivityOnDisplay(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION, mAssistantDisplayId);
+            assertAssistantStackExists();
+            mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
+            mAmWmState.assertFocusedStack("Expected assistant stack focused",
+                    WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+            final ActivityManagerState amState = mAmWmState.getAmState();
+            assertThat(amState.getStackByActivityType(ACTIVITY_TYPE_ASSISTANT).getTasks(),
+                    hasSize(1));
+            final int taskId = mAmWmState.getAmState().getTaskByActivity(ASSISTANT_ACTIVITY)
+                    .mTaskId;
+
+            // Launch a new fullscreen activity
+            // Using Animation Test Activity because it is opaque on all devices.
+            launchActivityOnDisplay(ANIMATION_TEST_ACTIVITY, mAssistantDisplayId);
+            mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, false);
+
+            // Launch the assistant again and ensure that it goes into the same task
+            launchActivityOnDisplay(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION, mAssistantDisplayId);
+            assertAssistantStackExists();
+            mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
+            mAmWmState.assertFocusedStack("Expected assistant stack focused",
+                    WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+            assertThat(amState.getStackByActivityType(ACTIVITY_TYPE_ASSISTANT).getTasks(),
+                    hasSize(1));
+            assertEquals(taskId,
+                    mAmWmState.getAmState().getTaskByActivity(ASSISTANT_ACTIVITY).mTaskId);
+
+        }
+    }
+
+    @Test
+    public void testPinnedStackWithAssistant() throws Exception {
+        assumeTrue(supportsPip());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
+
+            // Launch a fullscreen activity and a PIP activity, then launch the assistant, and
+            // ensure that the test activity is still visible
+            launchActivity(TEST_ACTIVITY);
+            launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_IS_TRANSLUCENT, String.valueOf(true));
+            waitForValidStateWithActivityType(
+                    TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+            mAmWmState.assertVisibility(TRANSLUCENT_ASSISTANT_ACTIVITY, true);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+        }
+    }
+
+    private void waitForValidStateWithActivityType(ComponentName activityName, int activityType)
+            throws Exception {
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setActivityType(activityType)
+                .build());
+    }
+
+    private void waitForValidStateWithActivityTypeAndWindowingMode(ComponentName activityName,
+            int activityType, int windowingMode) throws Exception {
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setActivityType(activityType)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
+    /**
+     * Asserts that the assistant stack exists.
+     */
+    private void assertAssistantStackExists() throws Exception {
+        mAmWmState.assertContainsStack("Must contain assistant stack.",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+    }
+
+    // Any 2D Activity in VR mode is run on a special VR virtual display, so check if the Assistant
+    // is going to run on the same display as other tasks.
+    protected boolean assistantRunsOnPrimaryDisplay() {
+        return mAssistantDisplayId == DEFAULT_DISPLAY_ID;
+    }
+
+    /** Helper class to save, set, and restore
+     * {@link Settings.Secure#VOICE_INTERACTION_SERVICE} system preference.
+     */
+    private static class AssistantSession extends SettingsSession<String> {
+        AssistantSession() {
+            super(Settings.Secure.getUriFor(Settings.Secure.VOICE_INTERACTION_SERVICE),
+                    Settings.Secure::getString, Settings.Secure::putString);
+        }
+
+        void setVoiceInteractionService(ComponentName assistantName) throws Exception {
+            super.set(getActivityName(assistantName));
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
new file mode 100644
index 0000000..db81483
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ComponentNameUtils.getLogTag;
+import static android.server.am.Components.FONT_SCALE_ACTIVITY;
+import static android.server.am.Components.FONT_SCALE_NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerConfigChangeTests
+ */
+public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
+
+    private static final float EXPECTED_FONT_SIZE_SP = 10.0f;
+
+    @Test
+    public void testRotation90Relaunch() throws Exception{
+        // Should relaunch on every rotation and receive no onConfigurationChanged()
+        testRotation(TEST_ACTIVITY, 1, 1, 0);
+    }
+
+    @Test
+    public void testRotation90NoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() on every rotation and no relaunch
+        testRotation(NO_RELAUNCH_ACTIVITY, 1, 0, 1);
+    }
+
+    @Test
+    public void testRotation180Relaunch() throws Exception {
+        // Should receive nothing
+        testRotation(TEST_ACTIVITY, 2, 0, 0);
+    }
+
+    @Test
+    public void testRotation180NoRelaunch() throws Exception {
+        // Should receive nothing
+        testRotation(NO_RELAUNCH_ACTIVITY, 2, 0, 0);
+    }
+
+    @FlakyTest(bugId = 73701185)
+    @Presubmit
+    @Test
+    public void testChangeFontScaleRelaunch() throws Exception {
+        // Should relaunch and receive no onConfigurationChanged()
+        testChangeFontScale(FONT_SCALE_ACTIVITY, true /* relaunch */);
+    }
+
+    @FlakyTest(bugId = 73812451)
+    @Presubmit
+    @Test
+    public void testChangeFontScaleNoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() and no relaunch
+        testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY, false /* relaunch */);
+    }
+
+    private void testRotation(ComponentName activityName, int rotationStep, int numRelaunch,
+            int numConfigChange) throws Exception {
+        launchActivity(activityName);
+
+        mAmWmState.computeState(activityName);
+
+        final int initialRotation = 4 - rotationStep;
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(initialRotation);
+            mAmWmState.computeState(activityName);
+            final int actualStackId =
+                    mAmWmState.getAmState().getTaskByActivity(activityName).mStackId;
+            final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
+            final int newDeviceRotation = getDeviceRotation(displayId);
+            if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
+                logE("Got an invalid device rotation value. "
+                        + "Continuing the test despite of that, but it is likely to fail.");
+            } else if (newDeviceRotation != initialRotation) {
+                log("This device doesn't support user rotation "
+                        + "mode. Not continuing the rotation checks.");
+                return;
+            }
+
+            for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+                final LogSeparator logSeparator = clearLogcat();
+                rotationSession.set(rotation);
+                mAmWmState.computeState(activityName);
+                assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
+                        logSeparator);
+            }
+        }
+    }
+
+    /** Helper class to save, set, and restore font_scale preferences. */
+    private static class FontScaleSession extends SettingsSession<Float> {
+        FontScaleSession() {
+            super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
+                    Settings.System::getFloat,
+                    Settings.System::putFloat);
+        }
+    }
+
+    private void testChangeFontScale(
+            ComponentName activityName, boolean relaunch) throws Exception {
+        try (final FontScaleSession fontScaleSession = new FontScaleSession()) {
+            fontScaleSession.set(1.0f);
+            LogSeparator logSeparator = clearLogcat();
+            launchActivity(activityName);
+            mAmWmState.computeState(activityName);
+
+            final int densityDpi = getActivityDensityDpi(activityName, logSeparator);
+
+            for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+                logSeparator = clearLogcat();
+                fontScaleSession.set(fontScale);
+                mAmWmState.computeState(activityName);
+                assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
+                        logSeparator);
+
+                // Verify that the display metrics are updated, and therefore the text size is also
+                // updated accordingly.
+                assertExpectedFontPixelSize(activityName,
+                        scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
+                        logSeparator);
+            }
+        }
+    }
+
+    /**
+     * Test updating application info when app is running. An activity with matching package name
+     * must be recreated and its asset sequence number must be incremented.
+     */
+    @Test
+    public void testUpdateApplicationInfo() throws Exception {
+        final LogSeparator firstLogSeparator = clearLogcat();
+
+        // Launch an activity that prints applied config.
+        launchActivity(TEST_ACTIVITY);
+        final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY, firstLogSeparator);
+
+        final LogSeparator logSeparator = clearLogcat();
+        // Update package info.
+        executeShellCommand("am update-appinfo all " + TEST_ACTIVITY.getPackageName());
+        mAmWmState.waitForWithAmState((amState) -> {
+            // Wait for activity to be resumed and asset seq number to be updated.
+            try {
+                return readAssetSeqNumber(TEST_ACTIVITY, logSeparator) == assetSeq + 1
+                        && amState.hasActivityState(TEST_ACTIVITY, STATE_RESUMED);
+            } catch (Exception e) {
+                logE("Error waiting for valid state: " + e.getMessage());
+                return false;
+            }
+        }, "Waiting asset sequence number to be updated and for activity to be resumed.");
+
+        // Check if activity is relaunched and asset seq is updated.
+        assertRelaunchOrConfigChanged(TEST_ACTIVITY, 1 /* numRelaunch */,
+                0 /* numConfigChange */, logSeparator);
+        final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY, logSeparator);
+        assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
+    }
+
+    private static final Pattern sConfigurationPattern = Pattern.compile(
+            "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
+
+    /** Read asset sequence number in last applied configuration from logs. */
+    private int readAssetSeqNumber(ComponentName activityName, LogSeparator logSeparator)
+            throws Exception {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sConfigurationPattern.matcher(line);
+            if (matcher.matches()) {
+                final String assetSeqNumber = matcher.group(3);
+                try {
+                    return Integer.valueOf(assetSeqNumber);
+                } catch (NumberFormatException e) {
+                    // Ignore, asset seq number is not printed when not set.
+                }
+            }
+        }
+        return 0;
+    }
+
+    // Calculate the scaled pixel size just like the device is supposed to.
+    private static int scaledPixelsToPixels(float sp, float fontScale, int densityDpi) {
+        final int DEFAULT_DENSITY = 160;
+        float f = densityDpi * (1.0f / DEFAULT_DENSITY) * fontScale * sp;
+        return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
+    }
+
+    private static Pattern sDeviceDensityPattern = Pattern.compile("^(.+): fontActivityDpi=(.+)$");
+
+    private int getActivityDensityDpi(ComponentName activityName, LogSeparator logSeparator)
+            throws Exception {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sDeviceDensityPattern.matcher(line);
+            if (matcher.matches()) {
+                return Integer.parseInt(matcher.group(2));
+            }
+        }
+        fail("No fontActivityDpi reported from activity " + activityName);
+        return -1;
+    }
+
+    private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
+
+    /** Read the font size in the last log line. */
+    private void assertExpectedFontPixelSize(ComponentName activityName, int fontPixelSize,
+            LogSeparator logSeparator) throws Exception {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sFontSizePattern.matcher(line);
+            if (matcher.matches()) {
+                assertEquals("Expected font pixel size does not match", fontPixelSize,
+                        Integer.parseInt(matcher.group(2)));
+                return;
+            }
+        }
+        fail("No fontPixelSize reported from activity " + activityName);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
new file mode 100644
index 0000000..32262d5
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a keyguard.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayKeyguardTests
+ */
+public class ActivityManagerDisplayKeyguardTests extends ActivityManagerDisplayTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(supportsMultiDisplay());
+        assumeTrue(isHandheld());
+    }
+
+    /**
+     * Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display is visible (for an
+     * insecure keyguard).
+     */
+    @Test
+    public void testDismissKeyguardActivity_secondaryDisplay() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
new file mode 100644
index 0000000..a68dafd
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a locked keyguard.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayLockedKeyguardTests
+ */
+public class ActivityManagerDisplayLockedKeyguardTests extends ActivityManagerDisplayTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(supportsMultiDisplay());
+        assumeTrue(isHandheld());
+    }
+
+    /**
+     * Test that virtual display content is hidden when device is locked.
+     */
+    @Test
+    public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+
+            // Create new usual virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+
+            // Lock the device.
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
+
+            // Unlock and check if visibility is back.
+            lockScreenSession.unlockDevice()
+                    .enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+        }
+    }
+
+    /**
+     * Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display dismisses the keyguard.
+     */
+    @Test
+    public void testDismissKeyguard_secondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            final ActivityDisplay newDisplay = virtualDisplaySession.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);
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
new file mode 100644
index 0000000..837ae05
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.provider.Settings;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.settings.SettingsSession;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for ActivityManager display tests.
+ *
+ * @see ActivityManagerDisplayTests
+ * @see ActivityManagerDisplayLockedKeyguardTests
+ */
+class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
+
+    static final int CUSTOM_DENSITY_DPI = 222;
+    private static final int INVALID_DENSITY_DPI = -1;
+
+    ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
+        for (ActivityDisplay display : displays) {
+            if (display.mId == displayId) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    /** Return the display state with width, height, dpi. Always not default display. */
+    ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
+            int dpi) {
+        for (ActivityDisplay display : displays) {
+            if (display.mId == DEFAULT_DISPLAY_ID) {
+                continue;
+            }
+            final Configuration config = display.mFullConfiguration;
+            if (config.densityDpi == dpi && config.screenWidthDp == width
+                    && config.screenHeightDp == height) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    List<ActivityDisplay> getDisplaysStates() {
+        mAmWmState.getAmState().computeState();
+        return mAmWmState.getAmState().getDisplays();
+    }
+
+    /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
+    List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
+            List<ActivityDisplay> newDisplays) {
+        final ArrayList<ActivityDisplay> result = new ArrayList<>();
+
+        for (ActivityDisplay newDisplay : newDisplays) {
+            if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
+                result.add(newDisplay);
+            }
+        }
+
+        return result;
+    }
+
+    static class ReportedDisplayMetrics {
+        private static final String WM_SIZE = "wm size";
+        private static final String WM_DENSITY = "wm density";
+        private static final Pattern PHYSICAL_SIZE =
+                Pattern.compile("Physical size: (\\d+)x(\\d+)");
+        private static final Pattern OVERRIDE_SIZE =
+                Pattern.compile("Override size: (\\d+)x(\\d+)");
+        private static final Pattern PHYSICAL_DENSITY =
+                Pattern.compile("Physical density: (\\d+)");
+        private static final Pattern OVERRIDE_DENSITY =
+                Pattern.compile("Override density: (\\d+)");
+
+        @NonNull
+        final Size physicalSize;
+        final int physicalDensity;
+
+        @Nullable
+        final Size overrideSize;
+        @Nullable
+        final Integer overrideDensity;
+
+        /** Get physical and override display metrics from WM. */
+        static ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+            return new ReportedDisplayMetrics(
+                    executeShellCommand(WM_SIZE) + executeShellCommand(WM_DENSITY));
+        }
+
+        void setDisplayMetrics(final Size size, final int density) {
+            setSize(size);
+            setDensity(density);
+        }
+
+        void restoreDisplayMetrics() {
+            if (overrideSize != null) {
+                setSize(overrideSize);
+            } else {
+                executeShellCommand(WM_SIZE + " reset");
+            }
+            if (overrideDensity != null) {
+                setDensity(overrideDensity);
+            } else {
+                executeShellCommand(WM_DENSITY + " reset");
+            }
+        }
+
+        private void setSize(final Size size) {
+            executeShellCommand(WM_SIZE + " " + size.getWidth() + "x" + size.getHeight());
+        }
+
+        private void setDensity(final int density) {
+            executeShellCommand(WM_DENSITY + " " + density);
+        }
+
+        /** Get display size that WM operates with. */
+        Size getSize() {
+            return overrideSize != null ? overrideSize : physicalSize;
+        }
+
+        /** Get density that WM operates with. */
+        int getDensity() {
+            return overrideDensity != null ? overrideDensity : physicalDensity;
+        }
+
+        private ReportedDisplayMetrics(final String lines) throws Exception {
+            Matcher matcher = PHYSICAL_SIZE.matcher(lines);
+            assertTrue("Physical display size must be reported", matcher.find());
+            log(matcher.group());
+            physicalSize = new Size(
+                    Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
+
+            matcher = PHYSICAL_DENSITY.matcher(lines);
+            assertTrue("Physical display density must be reported", matcher.find());
+            log(matcher.group());
+            physicalDensity = Integer.parseInt(matcher.group(1));
+
+            matcher = OVERRIDE_SIZE.matcher(lines);
+            if (matcher.find()) {
+                log(matcher.group());
+                overrideSize = new Size(
+                        Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
+            } else {
+                overrideSize = null;
+            }
+
+            matcher = OVERRIDE_DENSITY.matcher(lines);
+            if (matcher.find()) {
+                log(matcher.group());
+                overrideDensity = Integer.parseInt(matcher.group(1));
+            } else {
+                overrideDensity = null;
+            }
+        }
+    }
+
+    protected class VirtualDisplaySession implements AutoCloseable {
+        private int mDensityDpi = CUSTOM_DENSITY_DPI;
+        private boolean mLaunchInSplitScreen = false;
+        private boolean mCanShowWithInsecureKeyguard = false;
+        private boolean mPublicDisplay = false;
+        private boolean mResizeDisplay = true;
+        private ComponentName mLaunchActivity = null;
+        private boolean mSimulateDisplay = false;
+        private boolean mMustBeCreated = true;
+
+        private boolean mVirtualDisplayCreated = false;
+        private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
+                new OverlayDisplayDevicesSession();
+
+        public VirtualDisplaySession setDensityDpi(int densityDpi) {
+            mDensityDpi = densityDpi;
+            return this;
+        }
+
+        public VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
+            mLaunchInSplitScreen = launchInSplitScreen;
+            return this;
+        }
+
+        public VirtualDisplaySession setCanShowWithInsecureKeyguard(
+                boolean canShowWithInsecureKeyguard) {
+            mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
+            return this;
+        }
+
+        public VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
+            mPublicDisplay = publicDisplay;
+            return this;
+        }
+
+        public VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
+            mResizeDisplay = resizeDisplay;
+            return this;
+        }
+
+        public VirtualDisplaySession setLaunchActivity(ComponentName launchActivity) {
+            mLaunchActivity = launchActivity;
+            return this;
+        }
+
+        public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
+            mSimulateDisplay = simulateDisplay;
+            return this;
+        }
+
+        public VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
+            mMustBeCreated = mustBeCreated;
+            return this;
+        }
+
+        @Nullable
+        public ActivityDisplay createDisplay() throws Exception {
+            return createDisplays(1).stream().findFirst().orElse(null);
+        }
+
+        @NonNull
+        public List<ActivityDisplay> createDisplays(int count) throws Exception {
+            if (mSimulateDisplay) {
+                return simulateDisplay();
+            } else {
+                return createVirtualDisplays(count);
+            }
+        }
+
+        @Override
+        public void close() throws Exception {
+            mOverlayDisplayDeviceSession.close();
+            if (mVirtualDisplayCreated) {
+                destroyVirtualDisplays();
+                mVirtualDisplayCreated = false;
+            }
+        }
+
+        /**
+         * Simulate new display.
+         * <pre>
+         * <code>mDensityDpi</code> provide custom density for the display.
+         * </pre>
+         * @return {@link ActivityDisplay} of newly created display.
+         */
+        private List<ActivityDisplay> simulateDisplay() throws Exception {
+            final List<ActivityDisplay> originalDs = getDisplaysStates();
+
+            // Create virtual display with custom density dpi.
+            mOverlayDisplayDeviceSession.set("1024x768/" + mDensityDpi);
+
+            return assertAndGetNewDisplays(1, originalDs);
+        }
+
+        /**
+         * Create new virtual display.
+         * <pre>
+         * <code>mDensityDpi</code> provide custom density for the display.
+         * <code>mLaunchInSplitScreen</code> start {@link VirtualDisplayActivity} to side from
+         *     {@link LaunchingActivity} on primary display.
+         * <code>mCanShowWithInsecureKeyguard</code>  allow showing content when device is
+         *     showing an insecure keyguard.
+         * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
+         * <code>mPublicDisplay</code> make display public.
+         * <code>mResizeDisplay</code> should resize display when surface size changes.
+         * <code>LaunchActivity</code> should launch test activity immediately after display
+         *     creation.
+         * </pre>
+         * @param displayCount number of displays to be created.
+         * @return A list of {@link ActivityDisplay} that represent newly created displays.
+         * @throws Exception
+         */
+        private List<ActivityDisplay> createVirtualDisplays(int displayCount) throws Exception {
+            // Start an activity that is able to create virtual displays.
+            if (mLaunchInSplitScreen) {
+                getLaunchActivityBuilder()
+                        .setToSide(true)
+                        .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
+                        .execute();
+            } else {
+                launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
+            }
+            mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                    new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Focus must be on virtual display host activity",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            final List<ActivityDisplay> originalDS = getDisplaysStates();
+
+            // Create virtual display with custom density dpi.
+            final StringBuilder createVirtualDisplayCommand = new StringBuilder(
+                    getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
+                    .append(" -f 0x20000000")
+                    .append(" --es command create_display");
+            if (mDensityDpi != INVALID_DENSITY_DPI) {
+                createVirtualDisplayCommand
+                        .append(" --ei density_dpi ")
+                        .append(mDensityDpi);
+            }
+            createVirtualDisplayCommand.append(" --ei count ").append(displayCount)
+                    .append(" --ez can_show_with_insecure_keyguard ")
+                    .append(mCanShowWithInsecureKeyguard)
+                    .append(" --ez public_display ").append(mPublicDisplay)
+                    .append(" --ez resize_display ").append(mResizeDisplay);
+            if (mLaunchActivity != null) {
+                createVirtualDisplayCommand
+                        .append(" --es launch_target_component ")
+                        .append(getActivityName(mLaunchActivity));
+            }
+            executeShellCommand(createVirtualDisplayCommand.toString());
+            mVirtualDisplayCreated = true;
+
+            return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
+        }
+
+        /**
+         * Destroy existing virtual display.
+         */
+        void destroyVirtualDisplays() throws Exception {
+            final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
+                    + " -f 0x20000000"
+                    + " --es command destroy_display";
+            executeShellCommand(destroyVirtualDisplayCommand);
+            waitForDisplaysDestroyed();
+        }
+
+        private void waitForDisplaysDestroyed() throws Exception {
+            int tries = 0;
+            boolean done;
+            do {
+                done = !isHostedVirtualDisplayPresent();
+                if (!done && tries > 0) {
+                    log("Waiting for hosted displays destruction");
+                    try {
+                        Thread.sleep(500);
+                    } catch (InterruptedException e) {
+                        // Oh well
+                    }
+                }
+
+                tries++;
+            } while (tries < 10 && !done);
+
+            assertTrue(done);
+        }
+
+        private boolean isHostedVirtualDisplayPresent() throws Exception {
+            mAmWmState.computeState(true);
+            return mAmWmState.getWmState().getDisplays().stream().anyMatch(
+                    d -> d.getName() != null && d.getName().contains("HostedVirtualDisplay"));
+        }
+
+        /**
+         * Wait for desired number of displays to be created and get their properties.
+         * @param newDisplayCount expected display count, -1 if display should not be created.
+         * @param originalDS display states before creation of new display(s).
+         * @return list of new displays, empty list if no new display is created.
+         */
+        private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
+                List<ActivityDisplay> originalDS) throws Exception {
+            final int originalDisplayCount = originalDS.size();
+
+            // Wait for the display(s) to be created and get configurations.
+            final List<ActivityDisplay> ds = getDisplayStateAfterChange(
+                    originalDisplayCount + newDisplayCount);
+            if (newDisplayCount != -1) {
+                assertEquals("New virtual display(s) must be created",
+                        originalDisplayCount + newDisplayCount, ds.size());
+            } else {
+                assertEquals("New virtual display must not be created",
+                        originalDisplayCount, ds.size());
+                return Collections.emptyList();
+            }
+
+            // Find the newly added display(s).
+            final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
+            assertTrue("New virtual display must be created",
+                    newDisplayCount == newDisplays.size());
+
+            return newDisplays;
+        }
+    }
+
+    /** Helper class to save, set, and restore overlay_display_devices preference. */
+    private static class OverlayDisplayDevicesSession extends SettingsSession<String> {
+        OverlayDisplayDevicesSession() {
+            super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
+                    Settings.Global::getString,
+                    Settings.Global::putString);
+        }
+    }
+
+    /** Wait for provided number of displays and report their configurations. */
+    List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
+        List<ActivityDisplay> ds = getDisplaysStates();
+
+        int retriesLeft = 5;
+        while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
+            log("***Waiting for the correct number of displays...");
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                log(e.toString());
+            }
+            ds = getDisplaysStates();
+        }
+
+        return ds;
+    }
+
+    private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
+        if (displays.size() != expectedDisplayCount) {
+            return false;
+        }
+        for (ActivityDisplay display : displays) {
+            if (display.mOverrideConfiguration.densityDpi == 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** Checks if the device supports multi-display. */
+    boolean supportsMultiDisplay() {
+        return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
new file mode 100644
index 0000000..6a0291f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.TEST_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.util.Size;
+
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayTests
+ */
+public class ActivityManagerDisplayTests extends ActivityManagerDisplayTestBase {
+
+    /**
+     * Tests that the global configuration is equal to the default display's override configuration.
+     */
+    @Test
+    public void testDefaultDisplayOverrideConfiguration() throws Exception {
+        final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+        final ActivityDisplay primaryDisplay = getDisplayState(reportedDisplays,
+                DEFAULT_DISPLAY_ID);
+        assertEquals("Primary display's configuration should be equal to global configuration.",
+                primaryDisplay.mOverrideConfiguration, primaryDisplay.mFullConfiguration);
+        assertEquals("Primary display's configuration should be equal to global configuration.",
+                primaryDisplay.mOverrideConfiguration, primaryDisplay.mMergedOverrideConfiguration);
+    }
+
+    /**
+     * Tests that secondary display has override configuration set.
+     */
+    @Test
+    public void testCreateVirtualDisplayWithCustomConfig() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // Find the density of created display.
+            final int newDensityDpi = newDisplay.mFullConfiguration.densityDpi;
+            assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
+        }
+    }
+
+    /**
+     * Tests that launch on secondary display is not permitted if device has the feature disabled.
+     * Activities requested to be launched on a secondary display in this case should land on the
+     * default display.
+     */
+    @Test
+    public void testMultiDisplayDisabled() throws Exception {
+        // Only check devices with the feature disabled.
+        assumeFalse(supportsMultiDisplay());
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.computeState(TEST_ACTIVITY);
+
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    TEST_ACTIVITY);
+
+            // Check that activity is on the right display.
+            final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+            final ActivityManagerState.ActivityStack frontStack =
+                    mAmWmState.getAmState().getStackById(frontStackId);
+            assertEquals("Launched activity must be resumed",
+                    getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
+            assertEquals("Front stack must be on the default display", DEFAULT_DISPLAY_ID,
+                    frontStack.mDisplayId);
+            mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
+        }
+    }
+
+    @Test
+    public void testCreateMultipleVirtualDisplays() throws Exception {
+        final List<ActivityDisplay> originalDs = getDisplaysStates();
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual displays
+            virtualDisplaySession.createDisplays(3);
+            getDisplayStateAfterChange(originalDs.size() + 3);
+        }
+        getDisplayStateAfterChange(originalDs.size());
+    }
+
+    /**
+     * Test that display overrides apply correctly and won't be affected by display changes.
+     * This sets overrides to display size and density, initiates a display changed event by locking
+     * and unlocking the phone and verifies that overrides are kept.
+     */
+    @Presubmit
+    @Test
+    public void testForceDisplayMetrics() throws Exception {
+        launchHomeActivity();
+
+        try (final DisplayMetricsSession displayMetricsSession = new DisplayMetricsSession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            // Read initial sizes.
+            final ReportedDisplayMetrics originalDisplayMetrics =
+                    displayMetricsSession.getInitialDisplayMetrics();
+
+            // Apply new override values that don't match the physical metrics.
+            final Size overrideSize = new Size(
+                    (int) (originalDisplayMetrics.physicalSize.getWidth() * 1.5),
+                    (int) (originalDisplayMetrics.physicalSize.getHeight() * 1.5));
+            final Integer overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
+            displayMetricsSession.overrideDisplayMetrics(overrideSize, overrideDensity);
+
+            // Check if overrides applied correctly.
+            ReportedDisplayMetrics displayMetrics = displayMetricsSession.getDisplayMetrics();
+            assertEquals(overrideSize, displayMetrics.overrideSize);
+            assertEquals(overrideDensity, displayMetrics.overrideDensity);
+
+            // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
+            // might update the metrics.
+            lockScreenSession.sleepDevice()
+                    .wakeUpDevice()
+                    .unlockDevice();
+            mAmWmState.waitForHomeActivityVisible();
+
+            // Check if overrides are still applied.
+            displayMetrics = displayMetricsSession.getDisplayMetrics();
+            assertEquals(overrideSize, displayMetrics.overrideSize);
+            assertEquals(overrideDensity, displayMetrics.overrideDensity);
+        }
+    }
+
+    private static class DisplayMetricsSession implements AutoCloseable {
+
+        private final ReportedDisplayMetrics mInitialDisplayMetrics;
+
+        DisplayMetricsSession() throws Exception {
+            mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics();
+        }
+
+        ReportedDisplayMetrics getInitialDisplayMetrics() {
+            return mInitialDisplayMetrics;
+        }
+
+        ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+            return ReportedDisplayMetrics.getDisplayMetrics();
+        }
+
+        void overrideDisplayMetrics(final Size size, final int density) {
+            mInitialDisplayMetrics.setDisplayMetrics(size, density);
+        }
+
+        @Override
+        public void close() throws Exception {
+            mInitialDisplayMetrics.restoreDisplayMetrics();
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
new file mode 100644
index 0000000..d8808ea
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.am.Components.FREEFORM_ACTIVITY;
+import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerFreeformStackTests
+ */
+public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
+
+    private static final int TEST_TASK_OFFSET = 20;
+    private static final int TEST_TASK_OFFSET_2 = 100;
+    private static final int TEST_TASK_SIZE_1 = 900;
+    private static final int TEST_TASK_SIZE_2 = TEST_TASK_SIZE_1 * 2;
+    private static final int TEST_TASK_SIZE_DP_1 = 220;
+    private static final int TEST_TASK_SIZE_DP_2 = TEST_TASK_SIZE_DP_1 * 2;
+
+    // NOTE: Launching the FreeformActivity will automatically launch the TestActivity
+    // with bounds (0, 0, 900, 900)
+
+    @Test
+    public void testFreeformWindowManagementSupport() throws Exception {
+
+        launchActivity(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
+
+        if (!supportsFreeform()) {
+            mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+                    WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+            return;
+        }
+
+        mAmWmState.assertFrontStack("Freeform stack must be the front stack.",
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+        mAmWmState.assertFocusedActivity(
+                TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
+        assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
+                mAmWmState.getAmState().getTaskByActivity(TEST_ACTIVITY).getBounds());
+    }
+
+    @Test
+    public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
+        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        mAmWmState.computeState(NON_RESIZEABLE_ACTIVITY);
+
+        final ActivityTask task =
+                mAmWmState.getAmState().getTaskByActivity(NON_RESIZEABLE_ACTIVITY);
+        final ActivityStack stack = mAmWmState.getAmState().getStackById(task.mStackId);
+
+        if (task.isFullscreen()) {
+            // If the task is on the fullscreen stack, then we know that it will have bounds that
+            // fill the entire display.
+            return;
+        }
+
+        // If the task is not on the fullscreen stack, then compare the task bounds to the display
+        // bounds.
+        assertEquals(mAmWmState.getWmState().getDisplay(stack.mDisplayId).getDisplayRect(),
+                task.getBounds());
+    }
+
+    @Test
+    public void testActivityLifeCycleOnResizeFreeformTask() throws Exception {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FREEFORM);
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
+
+        if (!supportsFreeform()) {
+            mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+                    WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+            return;
+        }
+
+        final int displayId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_FREEFORM).mDisplayId;
+        final int densityDpi =
+                mAmWmState.getWmState().getDisplay(displayId).getDpi();
+        final int testTaskSize1 =
+                ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_1, densityDpi);
+        final int testTaskSize2 =
+                ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_2, densityDpi);
+
+        resizeActivityTask(TEST_ACTIVITY,
+                TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize1, testTaskSize2);
+        resizeActivityTask(NO_RELAUNCH_ACTIVITY,
+                TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize1, testTaskSize2);
+
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+        final LogSeparator logSeparator = clearLogcat();
+        resizeActivityTask(TEST_ACTIVITY,
+                TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize2, testTaskSize1);
+        resizeActivityTask(NO_RELAUNCH_ACTIVITY,
+                TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize2, testTaskSize1);
+        mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
+
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java
new file mode 100644
index 0000000..7f431c8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.nano.DeviceConfigurationProto;
+import android.content.nano.GlobalConfigurationProto;
+import android.content.nano.LocaleProto;
+import android.content.nano.ResourcesConfigurationProto;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.opengl.GLES10;
+import android.os.Build;
+import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.Display;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class ActivityManagerGetConfigTests {
+    Context mContext;
+    ActivityManager mAm;
+    PackageManager mPm;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mAm = mContext.getSystemService(ActivityManager.class);
+        mPm = mContext.getPackageManager();
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * Adds all supported GL extensions for a provided EGLConfig to a set by creating an EGLContext
+     * and EGLSurface and querying extensions.
+     *
+     * @param egl An EGL API object
+     * @param display An EGLDisplay to create a context and surface with
+     * @param config The EGLConfig to get the extensions for
+     * @param surfaceSize eglCreatePbufferSurface generic parameters
+     * @param contextAttribs eglCreateContext generic parameters
+     * @param glExtensions A Set<String> to add GL extensions to
+     */
+    private static void addExtensionsForConfig(
+            EGL10 egl,
+            EGLDisplay display,
+            EGLConfig config,
+            int[] surfaceSize,
+            int[] contextAttribs,
+            Set<String> glExtensions) {
+        // Create a context.
+        EGLContext context =
+                egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttribs);
+        // No-op if we can't create a context.
+        if (context == EGL10.EGL_NO_CONTEXT) {
+            return;
+        }
+
+        // Create a surface.
+        EGLSurface surface = egl.eglCreatePbufferSurface(display, config, surfaceSize);
+        if (surface == EGL10.EGL_NO_SURFACE) {
+            egl.eglDestroyContext(display, context);
+            return;
+        }
+
+        // Update the current surface and context.
+        egl.eglMakeCurrent(display, surface, surface, context);
+
+        // Get the list of extensions.
+        String extensionList = GLES10.glGetString(GLES10.GL_EXTENSIONS);
+        if (!TextUtils.isEmpty(extensionList)) {
+            // The list of extensions comes from the driver separated by spaces.
+            // Split them apart and add them into a Set for deduping purposes.
+            for (String extension : extensionList.split(" ")) {
+                glExtensions.add(extension);
+            }
+        }
+
+        // Tear down the context and surface for this config.
+        egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+        egl.eglDestroySurface(display, surface);
+        egl.eglDestroyContext(display, context);
+    }
+
+
+    Set<String> getGlExtensionsFromDriver() {
+        Set<String> glExtensions = new HashSet<>();
+
+        // Get the EGL implementation.
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        if (egl == null) {
+            throw new RuntimeException("Warning: couldn't get EGL");
+        }
+
+        // Get the default display and initialize it.
+        EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        int[] version = new int[2];
+        egl.eglInitialize(display, version);
+
+        // Call getConfigs() in order to find out how many there are.
+        int[] numConfigs = new int[1];
+        if (!egl.eglGetConfigs(display, null, 0, numConfigs)) {
+            throw new RuntimeException("Warning: couldn't get EGL config count");
+        }
+
+        // Allocate space for all configs and ask again.
+        EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+        if (!egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
+            throw new RuntimeException("Warning: couldn't get EGL configs");
+        }
+
+        // Allocate surface size parameters outside of the main loop to cut down
+        // on GC thrashing.  1x1 is enough since we are only using it to get at
+        // the list of extensions.
+        int[] surfaceSize =
+                new int[] {
+                        EGL10.EGL_WIDTH, 1,
+                        EGL10.EGL_HEIGHT, 1,
+                        EGL10.EGL_NONE
+                };
+
+        // For when we need to create a GLES2.0 context.
+        final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        int[] gles2 = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+
+        // For getting return values from eglGetConfigAttrib
+        int[] attrib = new int[1];
+
+        for (int i = 0; i < numConfigs[0]; i++) {
+            // Get caveat for this config in order to skip slow (i.e. software) configs.
+            egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, attrib);
+            if (attrib[0] == EGL10.EGL_SLOW_CONFIG) {
+                continue;
+            }
+
+            // If the config does not support pbuffers we cannot do an eglMakeCurrent
+            // on it in addExtensionsForConfig(), so skip it here. Attempting to make
+            // it current with a pbuffer will result in an EGL_BAD_MATCH error
+            egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_SURFACE_TYPE, attrib);
+            if ((attrib[0] & EGL10.EGL_PBUFFER_BIT) == 0) {
+                continue;
+            }
+
+            final int EGL_OPENGL_ES_BIT = 0x0001;
+            final int EGL_OPENGL_ES2_BIT = 0x0004;
+            egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_RENDERABLE_TYPE, attrib);
+            if ((attrib[0] & EGL_OPENGL_ES_BIT) != 0) {
+                addExtensionsForConfig(egl, display, configs[i], surfaceSize, null, glExtensions);
+            }
+            if ((attrib[0] & EGL_OPENGL_ES2_BIT) != 0) {
+                addExtensionsForConfig(egl, display, configs[i], surfaceSize, gles2, glExtensions);
+            }
+        }
+
+        // Release all EGL resources.
+        egl.eglTerminate(display);
+
+        return glExtensions;
+    }
+
+    private void checkResourceConfig(Configuration config, DisplayMetrics metrics,
+            ResourcesConfigurationProto resConfig) {
+        final int width, height;
+        if (metrics.widthPixels >= metrics.heightPixels) {
+            width = metrics.widthPixels;
+            height = metrics.heightPixels;
+        } else {
+            //noinspection SuspiciousNameCombination
+            width = metrics.heightPixels;
+            //noinspection SuspiciousNameCombination
+            height = metrics.widthPixels;
+        }
+
+        assertEquals("Expected SDK version does not match",
+                Build.VERSION.RESOURCES_SDK_INT, resConfig.sdkVersion);
+        assertEquals("Expected screen width px does not match",
+                width, resConfig.screenWidthPx);
+        assertEquals("Expected screen width px does not match",
+                height, resConfig.screenHeightPx);
+
+        assertEquals("Expected font scale does not match",
+                config.fontScale, resConfig.configuration.fontScale, Float.MIN_VALUE*5);
+        assertEquals("Expected mcc does not match",
+                config.mcc, resConfig.configuration.mcc);
+        assertEquals("Expected mnc does not match",
+                config.mnc, resConfig.configuration.mnc);
+        LocaleList llist = config.getLocales();
+        LocaleProto[] lprotos = resConfig.configuration.locales;
+        assertEquals("Expected number of locales does not match",
+                llist.size(), lprotos.length);
+        for (int i = 0; i < llist.size(); i++) {
+            assertEquals("Expected locale #" + i + " language does not match",
+                    llist.get(i).getLanguage(), lprotos[i].language);
+            assertEquals("Expected locale #" + i + " country does not match",
+                    llist.get(i).getCountry(), lprotos[i].country);
+            assertEquals("Expected locale #" + i + " variant does not match",
+                    llist.get(i).getVariant(), lprotos[i].variant);
+        }
+        assertEquals("Expected screen layout does not match",
+                config.screenLayout, resConfig.configuration.screenLayout);
+        assertEquals("Expected color mode does not match",
+                config.colorMode, resConfig.configuration.colorMode);
+        assertEquals("Expected touchscreen does not match",
+                config.touchscreen, resConfig.configuration.touchscreen);
+        assertEquals("Expected keyboard does not match",
+                config.keyboard, resConfig.configuration.keyboard);
+        assertEquals("Expected keyboard hidden does not match",
+                config.keyboardHidden, resConfig.configuration.keyboardHidden);
+        assertEquals("Expected hard keyboard hidden does not match",
+                config.hardKeyboardHidden, resConfig.configuration.hardKeyboardHidden);
+        assertEquals("Expected navigation does not match",
+                config.navigation, resConfig.configuration.navigation);
+        assertEquals("Expected navigation hidden does not match",
+                config.navigationHidden, resConfig.configuration.navigationHidden);
+        assertEquals("Expected orientation does not match",
+                config.orientation, resConfig.configuration.orientation);
+        assertEquals("Expected UI mode does not match",
+                config.uiMode, resConfig.configuration.uiMode);
+        assertEquals("Expected screen width dp does not match",
+                config.screenWidthDp, resConfig.configuration.screenWidthDp);
+        assertEquals("Expected screen hight dp does not match",
+                config.screenHeightDp, resConfig.configuration.screenHeightDp);
+        assertEquals("Expected smallest screen width dp does not match",
+                config.smallestScreenWidthDp, resConfig.configuration.smallestScreenWidthDp);
+        assertEquals("Expected density dpi does not match",
+                config.densityDpi, resConfig.configuration.densityDpi);
+        // XXX not comparing windowConfiguration, since by definition this is contextual.
+    }
+
+    private void checkDeviceConfig(DisplayManager dm, DeviceConfigurationProto deviceConfig) {
+        Point stableSize = dm.getStableDisplaySize();
+        assertEquals("Expected stable screen width does not match",
+                stableSize.x, deviceConfig.stableScreenWidthPx);
+        assertEquals("Expected stable screen height does not match",
+                stableSize.y, deviceConfig.stableScreenHeightPx);
+        assertEquals("Expected stable screen density does not match",
+                DisplayMetrics.DENSITY_DEVICE_STABLE, deviceConfig.stableDensityDpi);
+
+        assertEquals("Expected total RAM does not match",
+                mAm.getTotalRam(), deviceConfig.totalRam);
+        assertEquals("Expected low RAM does not match",
+                mAm.isLowRamDevice(), deviceConfig.lowRam);
+        assertEquals("Expected max cores does not match",
+                Runtime.getRuntime().availableProcessors(), deviceConfig.maxCores);
+        KeyguardManager kgm = mContext.getSystemService(KeyguardManager.class);
+        assertEquals("Expected has secure screen lock does not match",
+                kgm.isDeviceSecure(), deviceConfig.hasSecureScreenLock);
+
+        ConfigurationInfo configInfo = mAm.getDeviceConfigurationInfo();
+        if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
+            assertEquals("Expected opengl version does not match",
+                    configInfo.reqGlEsVersion, deviceConfig.openglVersion);
+        }
+
+        Set<String> glExtensionsSet = getGlExtensionsFromDriver();
+        String[] glExtensions = new String[glExtensionsSet.size()];
+        glExtensions = glExtensionsSet.toArray(glExtensions);
+        Arrays.sort(glExtensions);
+        assertArrayEquals("Expected opengl extensions does not match",
+                glExtensions, deviceConfig.openglExtensions);
+
+        List<SharedLibraryInfo> slibs = mPm.getSharedLibraries(0);
+        Collections.sort(slibs, Comparator.comparing(SharedLibraryInfo::getName));
+        String[] slibNames = new String[slibs.size()];
+        for (int i = 0; i < slibs.size(); i++) {
+            slibNames[i] = slibs.get(i).getName();
+        }
+        assertArrayEquals("Expected shared libraries does not match",
+                slibNames, deviceConfig.sharedLibraries);
+
+        FeatureInfo[] features = mPm.getSystemAvailableFeatures();
+        Arrays.sort(features, (o1, o2) ->
+                (o1.name == o2.name ? 0 : (o1.name == null ? -1 : o1.name.compareTo(o2.name))));
+        int size = 0;
+        for (int i = 0; i < features.length; i++) {
+            if (features[i].name != null) {
+                size++;
+            }
+        }
+        String[] featureNames = new String[size];
+        for (int i = 0, j = 0; i < features.length; i++) {
+            if (features[i].name != null) {
+                featureNames[j] = features[i].name;
+                j++;
+            }
+        }
+        assertArrayEquals("Expected features does not match",
+                featureNames, deviceConfig.features);
+    }
+
+    @Test
+    public void testDeviceConfig() {
+        byte[] dump = executeShellCommand("cmd activity get-config --proto --device");
+        GlobalConfigurationProto globalConfig;
+        try {
+            globalConfig = GlobalConfigurationProto.parseFrom(dump);
+        } catch (InvalidProtocolBufferNanoException ex) {
+            throw new RuntimeException("Failed to parse get-config:\n"
+                    + new String(dump, StandardCharsets.UTF_8), ex);
+        }
+
+        Configuration config = mContext.getResources().getConfiguration();
+        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+        DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
+
+        checkResourceConfig(config, metrics, globalConfig.resources);
+        checkDeviceConfig(dm, globalConfig.device);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
new file mode 100644
index 0000000..3aa514a
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityAndWindowManagersState.dpToPx;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.Components.BOTTOM_LEFT_LAYOUT_ACTIVITY;
+import static android.server.am.Components.BOTTOM_RIGHT_LAYOUT_ACTIVITY;
+import static android.server.am.Components.TOP_LEFT_LAYOUT_ACTIVITY;
+import static android.server.am.Components.TOP_RIGHT_LAYOUT_ACTIVITY;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.server.am.WindowManagerState.Display;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerManifestLayoutTests
+ */
+public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
+
+    // Test parameters
+    private static final int DEFAULT_WIDTH_DP = 240;
+    private static final int DEFAULT_HEIGHT_DP = 160;
+    private static final float DEFAULT_WIDTH_FRACTION = 0.25f;
+    private static final float DEFAULT_HEIGHT_FRACTION = 0.35f;
+    private static final int MIN_WIDTH_DP = 100;
+    private static final int MIN_HEIGHT_DP = 80;
+
+    private static final int GRAVITY_VER_CENTER = 0x01;
+    private static final int GRAVITY_VER_TOP    = 0x02;
+    private static final int GRAVITY_VER_BOTTOM = 0x04;
+    private static final int GRAVITY_HOR_CENTER = 0x10;
+    private static final int GRAVITY_HOR_LEFT   = 0x20;
+    private static final int GRAVITY_HOR_RIGHT  = 0x40;
+
+    private List<WindowState> mTempWindowList = new ArrayList();
+    private Display mDisplay;
+    private WindowState mWindowState;
+
+    @Test
+    public void testGravityAndDefaultSizeTopLeft() throws Exception {
+        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/);
+    }
+
+    @Test
+    public void testGravityAndDefaultSizeTopRight() throws Exception {
+        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/);
+    }
+
+    @Test
+    public void testGravityAndDefaultSizeBottomLeft() throws Exception {
+        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/);
+    }
+
+    @Test
+    public void testGravityAndDefaultSizeBottomRight() throws Exception {
+        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/);
+    }
+
+    @Test
+    public void testMinimalSizeFreeform() throws Exception {
+        assumeTrue("Skipping test: no freeform support", supportsFreeform());
+
+        testMinimalSize(WINDOWING_MODE_FREEFORM);
+    }
+
+    @Test
+    public void testMinimalSizeDocked() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        testMinimalSize(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+    }
+
+    private void testMinimalSize(int windowingMode) throws Exception {
+        // Issue command to resize to <0,0,1,1>. We expect the size to be floored at
+        // MIN_WIDTH_DPxMIN_HEIGHT_DP.
+        if (windowingMode == WINDOWING_MODE_FREEFORM) {
+            launchActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY, WINDOWING_MODE_FREEFORM);
+            resizeActivityTask(BOTTOM_RIGHT_LAYOUT_ACTIVITY, 0, 0, 1, 1);
+        } else { // stackId == DOCKED_STACK_ID
+            launchActivityInSplitScreenWithRecents(BOTTOM_RIGHT_LAYOUT_ACTIVITY);
+            resizeDockedStack(1, 1, 1, 1);
+        }
+        getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
+
+        final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
+        final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
+        final Rect containingRect = mWindowState.getContainingFrame();
+
+        assertEquals("Min width is incorrect", minWidth, containingRect.width());
+        assertEquals("Min height is incorrect", minHeight, containingRect.height());
+    }
+
+    private void testLayout(
+            int vGravity, int hGravity, boolean fraction) throws Exception {
+        assumeTrue("Skipping test: no freeform support", supportsFreeform());
+
+        final ComponentName activityName;
+        if (vGravity == GRAVITY_VER_TOP) {
+            activityName = (hGravity == GRAVITY_HOR_LEFT) ? TOP_LEFT_LAYOUT_ACTIVITY
+                    : TOP_RIGHT_LAYOUT_ACTIVITY;
+        } else {
+            activityName = (hGravity == GRAVITY_HOR_LEFT) ? BOTTOM_LEFT_LAYOUT_ACTIVITY
+                    : BOTTOM_RIGHT_LAYOUT_ACTIVITY;
+        }
+
+        // Launch in freeform stack
+        launchActivity(activityName, WINDOWING_MODE_FREEFORM);
+
+        getDisplayAndWindowState(activityName, true);
+
+        final Rect containingRect = mWindowState.getContainingFrame();
+        final Rect appRect = mDisplay.getAppRect();
+        final int expectedWidthPx, expectedHeightPx;
+        // Evaluate the expected window size in px. If we're using fraction dimensions,
+        // calculate the size based on the app rect size. Otherwise, convert the expected
+        // size in dp to px.
+        if (fraction) {
+            expectedWidthPx = (int) (appRect.width() * DEFAULT_WIDTH_FRACTION);
+            expectedHeightPx = (int) (appRect.height() * DEFAULT_HEIGHT_FRACTION);
+        } else {
+            final int densityDpi = mDisplay.getDpi();
+            expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
+            expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
+        }
+
+        verifyFrameSizeAndPosition(
+                vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
+    }
+
+    private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
+            throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(activityName);
+
+        if (checkFocus) {
+            mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+        } else {
+            mAmWmState.assertVisibility(activityName, true);
+        }
+
+        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mTempWindowList);
+
+        assertEquals("Should have exactly one window state for the activity.",
+                1, mTempWindowList.size());
+
+        mWindowState = mTempWindowList.get(0);
+        assertNotNull("Should have a valid window", mWindowState);
+
+        mDisplay = mAmWmState.getWmState().getDisplay(mWindowState.getDisplayId());
+        assertNotNull("Should be on a display", mDisplay);
+    }
+
+    private void verifyFrameSizeAndPosition(
+            int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
+            Rect containingFrame, Rect parentFrame) {
+        assertEquals("Width is incorrect", expectedWidthPx, containingFrame.width());
+        assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height());
+
+        if (vGravity == GRAVITY_VER_TOP) {
+            assertEquals("Should be on the top", parentFrame.top, containingFrame.top);
+        } else if (vGravity == GRAVITY_VER_BOTTOM) {
+            assertEquals("Should be on the bottom", parentFrame.bottom, containingFrame.bottom);
+        }
+
+        if (hGravity == GRAVITY_HOR_LEFT) {
+            assertEquals("Should be on the left", parentFrame.left, containingFrame.left);
+        } else if (hGravity == GRAVITY_HOR_RIGHT){
+            assertEquals("Should be on the right", parentFrame.right, containingFrame.right);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
new file mode 100644
index 0000000..fa88fb0
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+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.BROADCAST_RECEIVER_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.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.TEST_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.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 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 static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.displayservice.DisplayHelper;
+import android.support.annotation.Nullable;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests
+ */
+public class ActivityManagerMultiDisplayTests extends ActivityManagerDisplayTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(supportsMultiDisplay());
+    }
+
+    /**
+     * Tests launching an activity on virtual display.
+     */
+    @Presubmit
+    @Test
+    public void testLaunchActivityOnSecondaryDisplay() 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 = clearLogcat();
+            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);
+
+            // 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);
+        }
+    }
+
+    /**
+     * Tests launching a non-resizeable activity on virtual display. It should land on the
+     * default display.
+     */
+    @Test
+    public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.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_ID);
+            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);
+        }
+    }
+
+    /**
+     * 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.
+        launchActivityInSplitScreenWithRecents(LAUNCHING_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_ID);
+            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.
+     */
+    @Test
+    public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            // Launch a non-resizeable activity on a primary display.
+            launchActivityInNewTask(NON_RESIZEABLE_ACTIVITY);
+            // Launch a resizeable activity on new secondary display to create a new stack there.
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            final int externalFrontStackId = mAmWmState.getAmState()
+                    .getFrontStackId(newDisplay.mId);
+
+            // Try to move the non-resizeable activity to new secondary display.
+            moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
+            mAmWmState.computeState(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_ID);
+            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);
+        }
+    }
+
+    /**
+     * Tests launching a non-resizeable activity on virtual display from activity there. It should
+     * land on the secondary display based on the resizeability of the root activity of the task.
+     */
+    @Test
+    public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            // 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);
+
+            // Launch non-resizeable activity from secondary display.
+            executeShellCommand("am broadcast -a trigger_broadcast --ez " + KEY_LAUNCH_ACTIVITY
+                    + " true --ez " + KEY_NEW_TASK + " true --es " + KEY_TARGET_COMPONENT + " "
+                    + 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);
+        }
+    }
+
+    /**
+     * 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).
+     */
+    @Test
+    public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertFocusedActivity(
+                    "Activity launched on secondary display must be focused",
+                    LAUNCHING_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(LAUNCHING_ACTIVITY),
+                    frontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
+
+            // Launch non-resizeable activity from secondary display.
+            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);
+            assertEquals("Launched activity must be resumed",
+                    getActivityName(NON_RESIZEABLE_ACTIVITY),
+                    frontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on a just launched activity",
+                    frontStackId);
+        }
+    }
+
+    /**
+     * Tests launching an activity on a virtual display without special permission must not be
+     * allowed.
+     */
+    @Test
+    public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            final LogSeparator logSeparator = clearLogcat();
+
+            // Try to launch an activity and check it security exception was triggered.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+                            SECOND_LAUNCH_BROADCAST_ACTION)
+                    .setDisplayId(newDisplay.mId)
+                    .setTargetActivity(TEST_ACTIVITY)
+                    .execute();
+
+            assertSecurityException("ActivityLauncher", logSeparator);
+
+            mAmWmState.computeState(TEST_ACTIVITY);
+            assertFalse("Restricted activity must not be launched",
+                    mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
+        }
+    }
+
+    /**
+     * Tests launching an activity on a virtual display without special permission must be allowed
+     * for activities with same UID.
+     */
+    @Test
+    public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // Try to launch an activity and check it security exception was triggered.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+                    .setDisplayId(newDisplay.mId)
+                    .setTargetActivity(TEST_ACTIVITY)
+                    .execute();
+
+            mAmWmState.waitForValidState(TEST_ACTIVITY);
+
+            final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            final ActivityManagerState.ActivityStack focusedStack =
+                    mAmWmState.getAmState().getStackById(externalFocusedStackId);
+            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+                    focusedStack.mDisplayId);
+
+            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());
+        }
+    }
+
+    /**
+     * Tests launching an activity on virtual display and then launching another activity via shell
+     * command and without specifying the display id - the second activity must appear on the
+     * primary display.
+     */
+    @Presubmit
+    @Test
+    public void testConsequentLaunchActivity() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // 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);
+
+            // 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_ID);
+            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_ID, frontStack.mDisplayId);
+        }
+    }
+
+    /**
+     * Tests launching an activity on simulated display and then launching another activity from the
+     * first one - it must appear on the secondary display, because it was launched from there.
+     */
+    @FlakyTest(bugId = 71564456)
+    @Presubmit
+    @Test
+    public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            // 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);
+
+            // 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);
+        }
+    }
+
+    /**
+     * Tests launching an activity on virtual display and then launching another activity from the
+     * first one - it must appear on the secondary display, because it was launched from there.
+     */
+    @Test
+    public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // 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);
+
+            // 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);
+        }
+    }
+
+    /**
+     * Tests launching an activity on virtual display and then launching another activity from the
+     * first one with specifying the target display - it must appear on the secondary display.
+     */
+    @Test
+    public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // 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);
+
+            // 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);
+
+            // Launch other activity with different uid and check if it has launched successfully.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+                            SECOND_LAUNCH_BROADCAST_ACTION)
+                    .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);
+        }
+    }
+
+    /**
+     * Tests launching an activity on virtual display and then launching another activity that
+     * doesn't allow embedding - it should fail with security exception.
+     */
+    @Test
+    public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            // 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);
+
+            final LogSeparator logSeparator = clearLogcat();
+
+            // Launch second activity from app on secondary display specifying same display id.
+            getLaunchActivityBuilder()
+                    .setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY)
+                    .setDisplayId(newDisplay.mId)
+                    .execute();
+
+            assertSecurityException("ActivityLauncher", logSeparator);
+        }
+    }
+
+    /**
+     * Tests launching an activity to secondary display from activity on primary display.
+     */
+    @Test
+    public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
+        // Start launching activity.
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            // Launch activity on secondary display from the app on primary display.
+            getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                    .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);
+        }
+    }
+
+    /**
+     * Tests launching activities on secondary and then on primary display to see if the stack
+     * visibility is not affected.
+     */
+    @Presubmit
+    @Test
+    public void testLaunchActivitiesAffectsVisibility() throws Exception {
+        // Start launching activity.
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch activity on primary display and check if it doesn't affect activity on
+            // secondary display.
+            getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
+            mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+        }
+    }
+
+    /**
+     * Test that move-task works when moving between displays.
+     */
+    @FlakyTest(bugId = 72231060)
+    @Presubmit
+    @Test
+    public void testMoveTaskBetweenDisplays() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
+            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+                    defaultDisplayStackId);
+            assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+                    focusedStack.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);
+
+            // 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_ID,
+                    focusedStack.mDisplayId);
+        }
+    }
+
+    /**
+     * Tests launching activities on secondary display and then removing it to see if stack focus
+     * is moved correctly.
+     * This version launches virtual display creator to fullscreen stack in split-screen.
+     */
+    @FlakyTest(bugId = 69573940)
+    @Presubmit
+    @Test
+    public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Start launching activity into docked stack.
+        launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+        tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
+    /**
+     * Tests launching activities on secondary display and then removing it to see if stack focus
+     * is moved correctly.
+     * This version launches virtual display creator to docked stack in split-screen.
+     */
+    @Test
+    public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Setup split-screen.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY));
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+        tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
+    /**
+     * Tests launching activities on secondary display and then removing it to see if stack focus
+     * is moved correctly.
+     * This version works without split-screen.
+     */
+    @Test
+    public void testStackFocusSwitchOnDisplayRemoved3() throws Exception {
+        // Start an activity on default display to determine default stack.
+        launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+        final int focusedStackWindowingMode = mAmWmState.getAmState().getFrontStackWindowingMode(
+                DEFAULT_DISPLAY_ID);
+        // Finish probing activity.
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */,
+                focusedStackWindowingMode);
+    }
+
+    /**
+     * Create a virtual display, launch a test activity there, destroy the display and check if test
+     * activity is moved to a stack on the default display.
+     */
+    private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode)
+            throws Exception {
+        LogSeparator logSeparator;
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setPublicDisplay(true)
+                    .setLaunchInSplitScreen(splitScreen)
+                    .createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            if (splitScreen) {
+                mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+            }
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+                    RESIZEABLE_ACTIVITY);
+            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+            // Destroy virtual display.
+            logSeparator = clearLogcat();
+        }
+
+        mAmWmState.computeState(true);
+        assertActivityLifecycle(RESIZEABLE_ACTIVITY, false /* relaunched */, logSeparator);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(RESIZEABLE_ACTIVITY)
+                .setWindowingMode(windowingMode)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.assertSanity();
+        mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */);
+
+        // Check if the focus is switched back to primary display.
+        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+        mAmWmState.assertFocusedStack(
+                "Default stack on primary display must be focused after display removed",
+                windowingMode, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertFocusedActivity(
+                "Focus must be switched back to activity on primary display",
+                RESIZEABLE_ACTIVITY);
+    }
+
+    /**
+     * Tests launching activities on secondary display and then removing it to see if stack focus
+     * is moved correctly.
+     */
+    @Test
+    public void testStackFocusSwitchOnStackEmptied() 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_ID);
+
+            // Launch activity on new secondary display.
+            launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+                    BROADCAST_RECEIVER_ACTIVITY);
+
+            // 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);
+        }
+    }
+
+    /**
+     * Tests that input events on the primary display take focus from the virtual display.
+     */
+    @Test
+    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",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+            mAmWmState.computeState(TEST_ACTIVITY);
+            mAmWmState.assertFocusedActivity(
+                    "Activity launched on secondary display must be focused",
+                    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));
+
+            mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY);
+            mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+        }
+    }
+
+    /** Test that shell is allowed to launch on secondary displays. */
+    @Test
+    public void testPermissionLaunchFromShell() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+                    defaultDisplayFocusedStackId);
+            assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+                    focusedStack.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);
+
+            // Launch other activity with different uid and check it is launched on dynamic stack 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);
+            assertEquals("Activity launched by system must be on external display",
+                    externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+        }
+    }
+
+    /** Test that launching from app that is on external display is allowed. */
+    @Test
+    public void testPermissionLaunchFromAppOnSecondary() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            // Launch 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();
+            ActivityManagerState.ActivityStack focusedStack
+                    = mAmWmState.getAmState().getStackById(externalFocusedStackId);
+            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+                    focusedStack.mDisplayId);
+
+            // Launch another activity with third different uid from app on secondary display and
+            // check it is launched on secondary display.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+                            SECOND_LAUNCH_BROADCAST_ACTION)
+                    .setDisplayId(newDisplay.mId)
+                    .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());
+        }
+    }
+
+    /** Tests that an activity can launch an activity from a different UID into its own task. */
+    @Test
+    public void testPermissionLaunchMultiUidTask() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+            // 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(
+                    frontStackId);
+            assertEquals("Activity launched on secondary display must be resumed",
+                    getActivityName(LAUNCHING_ACTIVITY),
+                    frontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus 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);
+            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());
+        }
+    }
+
+    /**
+     * Test that launching from display owner is allowed even when the the display owner
+     * doesn't have anything on the display.
+     */
+    @Test
+    public void testPermissionLaunchFromOwner() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            ActivityManagerState.ActivityStack focusedStack
+                    = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
+            assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+                    focusedStack.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);
+
+            // Check that owner uid can launch its own activity on secondary display.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+                    .setNewTask(true)
+                    .setMultipleTask(true)
+                    .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());
+        }
+    }
+
+    /**
+     * Test that launching from app that is not present on external display and doesn't own it to
+     * that external display is not allowed.
+     */
+    @Test
+    public void testPermissionLaunchFromDifferentApp() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+                    defaultDisplayFocusedStackId);
+            assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+                    focusedStack.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);
+
+            final LogSeparator logSeparator = clearLogcat();
+
+            // Launch other activity with different uid and check security exception is triggered.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
+                            SECOND_LAUNCH_BROADCAST_ACTION)
+                    .setDisplayId(newDisplay.mId)
+                    .setTargetActivity(THIRD_ACTIVITY)
+                    .execute();
+
+            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());
+        }
+    }
+
+    private void assertSecurityException(String component, LogSeparator logSeparator)
+            throws Exception {
+        final Pattern pattern = Pattern.compile(".*SecurityException launching activity.*");
+        for (int retry = 1; retry <= 5; retry++) {
+            String[] logs = getDeviceLogsForComponents(logSeparator, component);
+            for (String line : logs) {
+                Matcher m = pattern.matcher(line);
+                if (m.matches()) {
+                    return;
+                }
+            }
+            logAlways("***Waiting for SecurityException for " + component + " ... retry=" + retry);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+            }
+        }
+        fail("Expected exception for " + component + " not found");
+    }
+
+    /**
+     * Test that only private virtual display can show content with insecure keyguard.
+     */
+    @Test
+    public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Try to create new show-with-insecure-keyguard public virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setPublicDisplay(true)
+                    .setCanShowWithInsecureKeyguard(true)
+                    .setMustBeCreated(false)
+                    .createDisplay();
+
+            // Check that the display is not created.
+            assertNull(newDisplay);
+        }
+    }
+
+    /**
+     * Test that all activities that were on the private display are destroyed on display removal.
+     */
+    @FlakyTest(bugId = 63404575)
+    @Presubmit
+    @Test
+    public void testContentDestroyOnDisplayRemoved() throws Exception {
+        LogSeparator logSeparator;
+        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.
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    TEST_ACTIVITY);
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    RESIZEABLE_ACTIVITY);
+
+            // Destroy the display and check if activities are removed from system.
+            logSeparator = clearLogcat();
+        }
+
+        mAmWmState.waitForWithAmState(
+                (state) -> !state.containsActivity(TEST_ACTIVITY)
+                        && !state.containsActivity(RESIZEABLE_ACTIVITY),
+                "Waiting for activity to be removed");
+        mAmWmState.waitForWithWmState(
+                (state) -> !state.containsWindow(getWindowName(TEST_ACTIVITY))
+                        && !state.containsWindow(getWindowName(RESIZEABLE_ACTIVITY)),
+                "Waiting for activity window to be gone");
+
+        // Check AM state.
+        assertFalse("Activity from removed display must be destroyed",
+                mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
+        assertFalse("Activity from removed display must be destroyed",
+                mAmWmState.getAmState().containsActivity(RESIZEABLE_ACTIVITY));
+        // Check WM state.
+        assertFalse("Activity windows from removed display must be destroyed",
+                mAmWmState.getWmState().containsWindow(getWindowName(TEST_ACTIVITY)));
+        assertFalse("Activity windows from removed display must be destroyed",
+                mAmWmState.getWmState().containsWindow(getWindowName(RESIZEABLE_ACTIVITY)));
+        // Check activity logs.
+        assertActivityDestroyed(TEST_ACTIVITY, logSeparator);
+        assertActivityDestroyed(RESIZEABLE_ACTIVITY, logSeparator);
+    }
+
+    /**
+     * Test that the update of display metrics updates all its content.
+     */
+    @Presubmit
+    @Test
+    public void testDisplayResize() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch a resizeable activity on new secondary display.
+            final LogSeparator initialLogSeparator = clearLogcat();
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    RESIZEABLE_ACTIVITY);
+
+            // Grab reported sizes and compute new with slight size change.
+            final ReportedSizes initialSize = getLastReportedSizesForActivity(
+                    RESIZEABLE_ACTIVITY, initialLogSeparator);
+
+            // Resize the docked stack, so that activity with virtual display will also be resized.
+            final LogSeparator logSeparator = clearLogcat();
+            executeShellCommand(getResizeVirtualDisplayCommand());
+
+            mAmWmState.waitForWithAmState(amState -> {
+                try {
+                    return readConfigChangeNumber(RESIZEABLE_ACTIVITY, logSeparator) == 1
+                            && amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
+                } catch (Exception e) {
+                    logE("Error waiting for valid state: " + e.getMessage());
+                    return false;
+                }
+            }, "Wait for the configuration change to happen and for activity to be resumed.");
+
+            mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                    new WaitForValidActivityState(RESIZEABLE_ACTIVITY),
+                    new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true);
+
+            // Check if activity in virtual display was resized properly.
+            assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
+                    1 /* numConfigChange */, logSeparator);
+
+            final ReportedSizes updatedSize = getLastReportedSizesForActivity(
+                    RESIZEABLE_ACTIVITY, logSeparator);
+            assertTrue(updatedSize.widthDp <= initialSize.widthDp);
+            assertTrue(updatedSize.heightDp <= initialSize.heightDp);
+            assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
+            assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
+        }
+    }
+
+    /** Read the number of configuration changes sent to activity from logs. */
+    private int readConfigChangeNumber(ComponentName activityName, LogSeparator logSeparator)
+            throws Exception {
+        return (new ActivityLifecycleCounts(activityName, logSeparator)).mConfigurationChangedCount;
+    }
+
+    /**
+     * Tests that when an activity is launched with displayId specified and there is an existing
+     * matching task on some other display - that task will moved to the target display.
+     */
+    @Test
+    public void testMoveToDisplayOnLaunch() throws Exception {
+        // Launch activity with unique affinity, so it will the only one in its task.
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+            // Launch something to that display so that a new stack is created. We need this to be
+            // able to compare task numbers in stacks later.
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+
+            final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+                    .mStacks.size();
+            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+            final int taskNumOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
+                    .getTasks().size();
+
+            // Launch activity on new secondary display.
+            // Using custom command here, because normally we add flags
+            // {@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)
+                    + " --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);
+
+            // Check that task has moved from primary display to secondary.
+            final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+                    .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);
+        }
+    }
+
+    /**
+     * Tests that when an activity is launched with displayId specified and there is an existing
+     * matching task on some other display - that task will moved to the target display.
+     */
+    @Test
+    public void testMoveToEmptyDisplayOnLaunch() throws Exception {
+        // Launch activity with unique affinity, so it will the only one in its task.
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+                    .mStacks.size();
+
+            // Launch activity on new secondary display.
+            // Using custom command here, because normally we add flags
+            // {@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)
+                    + " --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);
+
+            // Check that task has moved from primary display to secondary.
+            final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+                    .mStacks.size();
+            assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
+                    stackNumFinal);
+        }
+    }
+
+    /**
+     * Tests that when primary display is rotated secondary displays are not affected.
+     */
+    @Test
+    public void testRotationNotAffectingSecondaryScreen() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setResizeDisplay(false)
+                    .createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch activity on new secondary display.
+            LogSeparator logSeparator = clearLogcat();
+            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);
+
+            try (final RotationSession rotationSession = new RotationSession()) {
+                // Rotate primary display and check that activity on secondary display is not
+                // affected.
+
+                rotateAndCheckSameSizes(rotationSession, RESIZEABLE_ACTIVITY);
+
+                // Launch activity to secondary display when primary one is rotated.
+                final int initialRotation = mAmWmState.getWmState().getRotation();
+                rotationSession.set((initialRotation + 1) % 4);
+
+                logSeparator = clearLogcat();
+                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);
+            }
+        }
+    }
+
+    private void rotateAndCheckSameSizes(
+            RotationSession rotationSession, ComponentName activityName) throws Exception {
+        for (int rotation = 3; rotation >= 0; --rotation) {
+            final LogSeparator logSeparator = clearLogcat();
+            rotationSession.set(rotation);
+            final ReportedSizes rotatedSizes = getLastReportedSizesForActivity(activityName,
+                    logSeparator);
+            assertNull("Sizes must not change after rotation", rotatedSizes);
+        }
+    }
+
+    /**
+     * Tests that task affinity does affect what display an activity is launched on but that
+     * matching the task component root does.
+     */
+    @Test
+    public void testTaskMatchAcrossDisplays() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+            // Check that activity is on 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(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+            executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY));
+            mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
+
+            // Check that second activity gets launched on the default display despite
+            // the affinity match on the secondary display.
+            final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
+                    DEFAULT_DISPLAY_ID);
+            final ActivityManagerState.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",
+                    defaultDisplayFrontStackId);
+
+            executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY));
+            mAmWmState.waitForFocusedStack(frontStackId);
+
+            // 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
+                    = 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",
+                    1, secondFrontStack.getTasks().size());
+            assertEquals("Focused task must only contain 1 activity",
+                    1, secondFrontStack.getTasks().get(0).mActivities.size());
+        }
+    }
+
+    /**
+     * Tests that the task affinity search respects the launch display id.
+     */
+    @Test
+    public void testLaunchDisplayAffinityMatch() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+
+            // Check that activity is on 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(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+            // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay
+            executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY)
+                    + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK
+                    + " --display " + newDisplay.mId);
+            mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY);
+
+            // Check that second activity gets launched into the affinity matching
+            // task on the secondary display
+            final int secondFrontStackId =
+                    mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+            final ActivityManagerState.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",
+                    secondFrontStackId);
+            assertEquals("Focused stack must only contain 1 task",
+                    1, secondFrontStack.getTasks().size());
+            assertEquals("Focused 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
+     * even if the focused stack is not on that activity's display.
+     */
+    @Test
+    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);
+
+            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_ID,
+                    focusedStack.mDisplayId);
+
+            executeShellCommand("am broadcast -a trigger_broadcast --ez " + KEY_LAUNCH_ACTIVITY
+                    + " true --ez " + KEY_NEW_TASK + " true --es " + KEY_TARGET_COMPONENT + " "
+                    + 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());
+        }
+    }
+
+    /**
+     * Tests than an immediate launch after new display creation is handled correctly.
+     */
+    @Test
+    public void testImmediateLaunchOnNewDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display and immediately launch an activity on it.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setLaunchActivity(TEST_ACTIVITY)
+                    .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);
+            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(TEST_ACTIVITY), firstFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+        }
+    }
+
+    /**
+     * Tests that turning the primary display off does not affect the activity running
+     * on an external secondary display.
+     */
+    @Test
+    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_ID,
+                "Activity launched on primary display must be resumed");
+
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
+             final PrimaryDisplayStateSession displayStateSession =
+                     new PrimaryDisplayStateSession()) {
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+            // Check that the activity is launched onto the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on external display must be resumed");
+
+            displayStateSession.turnScreenOff();
+
+            // Wait for the fullscreen stack to start sleeping, and then make sure the
+            // test activity is still resumed.
+            waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY,
+                    "Activity launched on primary display must be stopped after turning off");
+            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on external display must be resumed");
+        }
+    }
+
+    /**
+     * Tests that an activity can be launched on a secondary display while the primary
+     * display is off.
+     */
+    @Test
+    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_ID,
+                "Activity launched on primary display must be resumed");
+
+        try (final PrimaryDisplayStateSession displayStateSession =
+                     new PrimaryDisplayStateSession();
+             final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            displayStateSession.turnScreenOff();
+
+            // Make sure there is no resumed activity when the primary display is off
+            waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY,
+                    "Activity launched on primary display must be stopped after turning off");
+            assertEquals("Unexpected resumed activity",
+                    0, mAmWmState.getAmState().getResumedActivitiesCount());
+
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+            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");
+        }
+    }
+
+    /**
+     * Tests that turning the secondary display off stops activities running on that display.
+     */
+    @Test
+    public void testExternalDisplayToggleState() throws Exception {
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+
+            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");
+
+            externalDisplaySession.turnDisplayOff();
+
+            // Check that turning off the external display stops the activity
+            waitAndAssertActivityStopped(TEST_ACTIVITY,
+                    "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,
+                    "Activity launched on external display must be resumed");
+        }
+    }
+
+    /**
+     * Tests that tapping on the primary display after showing the keyguard resumes the
+     * activity on the primary display.
+     */
+    @Test
+    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_ID,
+                "Activity launched on primary display must be resumed");
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            lockScreenSession.sleepDevice();
+
+            // Make sure there is no resumed activity when the primary display is off
+            waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY,
+                    "Activity launched on primary display must be stopped after turning off");
+            assertEquals("Unexpected resumed activity",
+                    0, mAmWmState.getAmState().getResumedActivitiesCount());
+
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+            // Check that the test activity is resumed on the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on external display must be resumed");
+
+            // Unlock the device and tap on the middle of the primary display
+            lockScreenSession.wakeUpDevice();
+            executeShellCommand("wm dismiss-keyguard");
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.waitForValidState(TEST_ACTIVITY);
+            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+            final int width = displayMetrics.getSize().getWidth();
+            final int height = displayMetrics.getSize().getHeight();
+            executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
+
+            // Check that the activity on the primary display is resumed
+            waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY_ID,
+                    "Activity launched on primary display must be resumed");
+            assertEquals("Unexpected resumed activity",
+                    1, mAmWmState.getAmState().getResumedActivitiesCount());
+        }
+    }
+
+    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.
+     */
+    @Test
+    public void testSecondaryDisplayShowWhenLocked() throws Exception {
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+
+            launchActivity(TEST_ACTIVITY);
+
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+            launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId);
+
+            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));
+        }
+    }
+
+    /** 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);
+        }
+    }
+
+    private static String getResizeVirtualDisplayCommand() {
+        return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
+                " --es command resize_display";
+    }
+
+    private class ExternalDisplaySession implements AutoCloseable {
+
+        @Nullable
+        private DisplayHelper mExternalDisplayHelper;
+
+        /**
+         * Creates a private virtual display with the external and show with insecure
+         * keyguard flags set.
+         */
+        ActivityDisplay createVirtualDisplay(boolean showContentWhenLocked)
+                throws Exception {
+            final List<ActivityDisplay> originalDS = getDisplaysStates();
+            final int originalDisplayCount = originalDS.size();
+
+            mExternalDisplayHelper = new DisplayHelper();
+            mExternalDisplayHelper.createAndWaitForDisplay(true /* external */,
+                    showContentWhenLocked);
+
+            // Wait for the virtual display to be created and get configurations.
+            final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
+            assertEquals("New virtual display must be created", originalDisplayCount + 1,
+                    ds.size());
+
+            // Find the newly added display.
+            final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
+            return newDisplays.get(0);
+        }
+
+        void turnDisplayOff() {
+            if (mExternalDisplayHelper == null) {
+                throw new RuntimeException("No external display created");
+            }
+            mExternalDisplayHelper.turnDisplayOff();
+        }
+
+        void turnDisplayOn() {
+            if (mExternalDisplayHelper == null) {
+                throw new RuntimeException("No external display created");
+            }
+            mExternalDisplayHelper.turnDisplayOn();
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (mExternalDisplayHelper != null) {
+                mExternalDisplayHelper.releaseDisplay();
+                mExternalDisplayHelper = null;
+            }
+        }
+    }
+
+    private class PrimaryDisplayStateSession implements AutoCloseable {
+
+        void turnScreenOff() {
+            setPrimaryDisplayState(false);
+        }
+
+        @Override
+        public void close() throws Exception {
+            setPrimaryDisplayState(true);
+        }
+
+        /** Turns the primary display on/off by pressing the power key */
+        private void setPrimaryDisplayState(boolean wantOn) {
+            if (wantOn) {
+                pressWakeupButton();
+            } else {
+                pressSleepButton();
+            }
+            DisplayHelper.waitForDefaultDisplayState(wantOn);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
new file mode 100644
index 0000000..28fb44e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
@@ -0,0 +1,1621 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.ActivityManager.StackId.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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ActivityManagerState.STATE_DESTROYED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.ComponentNameUtils.getLogTag;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.LAUNCH_ENTER_PIP_ACTIVITY;
+import static android.server.am.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
+import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.PIP_ACTIVITY;
+import static android.server.am.Components.PIP_ACTIVITY2;
+import static android.server.am.Components.PIP_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.am.Components.PIP_ON_STOP_ACTIVITY;
+import static android.server.am.Components.RESUME_WHILE_PAUSING_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.am.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.am.UiDeviceUtils.pressWindowButton;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+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.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+import android.server.am.settings.SettingsSession;
+import android.support.test.filters.FlakyTest;
+import android.util.Size;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerPinnedStackTests
+ */
+@FlakyTest(bugId = 71792368)
+public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
+
+    private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+    private static final String EXTRA_ENTER_PIP = "enter_pip";
+    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
+            "enter_pip_aspect_ratio_numerator";
+    private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
+            "enter_pip_aspect_ratio_denominator";
+    private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
+    private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
+    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
+            "set_aspect_ratio_with_delay_numerator";
+    private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
+            "set_aspect_ratio_with_delay_denominator";
+    private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+    private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+    private static final String EXTRA_START_ACTIVITY = "start_activity";
+    private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+    private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+    private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+    private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+    private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
+            "android.server.am.PipActivity.enter_pip";
+    private static final String PIP_ACTIVITY_ACTION_MOVE_TO_BACK =
+            "android.server.am.PipActivity.move_to_back";
+    private static final String PIP_ACTIVITY_ACTION_EXPAND_PIP =
+            "android.server.am.PipActivity.expand_pip";
+    private static final String PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION =
+            "android.server.am.PipActivity.set_requested_orientation";
+    private static final String PIP_ACTIVITY_ACTION_FINISH =
+            "android.server.am.PipActivity.finish";
+
+    private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
+    private static final int APP_OPS_MODE_ALLOWED = 0;
+    private static final int APP_OPS_MODE_IGNORED = 1;
+    private static final int APP_OPS_MODE_ERRORED = 2;
+
+    private static final int ROTATION_0 = 0;
+    private static final int ROTATION_90 = 1;
+    private static final int ROTATION_180 = 2;
+    private static final int ROTATION_270 = 3;
+
+    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+    private static final int ORIENTATION_LANDSCAPE = 0;
+    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+    private static final int ORIENTATION_PORTRAIT = 1;
+
+    private static final float FLOAT_COMPARE_EPSILON = 0.005f;
+
+    // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
+    private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
+    private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
+    private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
+    // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
+    private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
+    private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
+    private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
+
+    @Test
+    public void testMinimumDeviceSize() throws Exception {
+        assumeTrue(supportsPip());
+
+        mAmWmState.assertDeviceDefaultDisplaySize(
+                "Devices supporting picture-in-picture must be larger than the default minimum"
+                        + " task size");
+    }
+
+    @Presubmit
+    @Test
+    public void testEnterPictureInPictureMode() throws Exception {
+        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"),
+                PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
+                false /* isFocusable */);
+    }
+
+    @FlakyTest(bugId = 71444628)
+    @Presubmit
+    @Test
+    public void testMoveTopActivityToPinnedStack() throws Exception {
+        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
+                true /* moveTopToPinnedStack */, false /* isFocusable */);
+    }
+
+    // This test is back-listed in cts-known-failures.xml.
+    @Test
+    public void testAlwaysFocusablePipActivity() throws Exception {
+        pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
+                ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
+                false /* moveTopToPinnedStack */, true /* isFocusable */);
+    }
+
+    // This test is back-listed in cts-known-failures.xml.
+    @Presubmit
+    @Test
+    public void testLaunchIntoPinnedStack() throws Exception {
+        pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
+                LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
+                false /* moveTopToPinnedStack */, true /* isFocusable */);
+    }
+
+    @Test
+    public void testNonTappablePipActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the tap-to-finish activity at a specific place
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_TAP_TO_FINISH, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.waitForAppTransitionIdle();
+        assertPinnedStackExists();
+
+        // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
+        // not passed down to the top task
+        tapToFinishPip();
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(PIP_ACTIVITY));
+        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+    }
+
+    @Test
+    public void testPinnedStackDefaultBounds() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a PIP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+
+            WindowManagerState wmState = mAmWmState.getWmState();
+            wmState.computeState();
+            Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+            Rect stableBounds = wmState.getStableBounds();
+            assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+            assertTrue(stableBounds.contains(defaultPipBounds));
+
+            rotationSession.set(ROTATION_90);
+            wmState = mAmWmState.getWmState();
+            wmState.computeState();
+            defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+            stableBounds = wmState.getStableBounds();
+            assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+            assertTrue(stableBounds.contains(defaultPipBounds));
+        }
+    }
+
+    @Test
+    public void testPinnedStackMovementBounds() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a PIP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+            WindowManagerState wmState = mAmWmState.getWmState();
+            wmState.computeState();
+            Rect pipMovementBounds = wmState.getPinnedStackMomentBounds();
+            Rect stableBounds = wmState.getStableBounds();
+            assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+            assertTrue(stableBounds.contains(pipMovementBounds));
+
+            rotationSession.set(ROTATION_90);
+            wmState = mAmWmState.getWmState();
+            wmState.computeState();
+            pipMovementBounds = wmState.getPinnedStackMomentBounds();
+            stableBounds = wmState.getStableBounds();
+            assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+            assertTrue(stableBounds.contains(pipMovementBounds));
+        }
+    }
+
+    @Test
+    @FlakyTest // TODO: Reintroduce to presubmit once b/71508234 is resolved.
+    public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
+        assumeTrue(supportsPip());
+
+        final WindowManagerState wmState = mAmWmState.getWmState();
+
+        // Launch an activity into the pinned stack
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_TAP_TO_FINISH, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.waitForAppTransitionIdle();
+
+        // Get the display dimensions
+        WindowManagerState.WindowState windowState = getWindowState(PIP_ACTIVITY);
+        WindowManagerState.Display display = wmState.getDisplay(windowState.getDisplayId());
+        Rect displayRect = display.getDisplayRect();
+
+        // Move the pinned stack offscreen
+        final int stackId = getPinnedStack().mStackId;
+        final int top = 0;
+        final int left = displayRect.width() - 200;
+        resizeStack(stackId, left, top, left + 500, top + 500);
+
+        // Ensure that the surface insets are not negative
+        windowState = getWindowState(PIP_ACTIVITY);
+        Rect contentInsets = windowState.getContentInsets();
+        if (contentInsets != null) {
+            assertTrue(contentInsets.left >= 0 && contentInsets.top >= 0
+                    && contentInsets.width() >= 0 && contentInsets.height() >= 0);
+        }
+    }
+
+    @Test
+    public void testPinnedStackInBoundsAfterRotation() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch an activity into the pinned stack
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_TAP_TO_FINISH, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+
+        // Ensure that the PIP stack is fully visible in each orientation
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_0);
+            assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+            rotationSession.set(ROTATION_90);
+            assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+            rotationSession.set(ROTATION_180);
+            assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+            rotationSession.set(ROTATION_270);
+            assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+        }
+    }
+
+    @Test
+    public void testEnterPipToOtherOrientation() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a portrait only app on the fullscreen stack
+        launchActivity(TEST_ACTIVITY,
+                EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
+        // Launch the PiP activity fixed as landscape
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_FIXED_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 " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+        assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+    }
+
+    @Test
+    public void testEnterPipAspectRatioMin() throws Exception {
+        testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Test
+    public void testEnterPipAspectRatioMax() throws Exception {
+        testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    private void testEnterPipAspectRatio(int num, int denom) throws Exception {
+        assumeTrue(supportsPip());
+
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+        assertPinnedStackExists();
+
+        // Assert that we have entered PIP and that the aspect ratio is correct
+        Rect pinnedStackBounds = getPinnedStackBounds();
+        assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
+                (float) num / denom);
+    }
+
+    @Test
+    public void testResizePipAspectRatioMin() throws Exception {
+        testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Test
+    public void testResizePipAspectRatioMax() throws Exception {
+        testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    private void testResizePipAspectRatio(int num, int denom) throws Exception {
+        assumeTrue(supportsPip());
+
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+        assertPinnedStackExists();
+        waitForValidAspectRatio(num, denom);
+        Rect bounds = getPinnedStackBounds();
+        assertFloatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
+    }
+
+    @Test
+    public void testEnterPipExtremeAspectRatioMin() throws Exception {
+        testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
+                BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Test
+    public void testEnterPipExtremeAspectRatioMax() throws Exception {
+        testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
+                MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
+        assumeTrue(supportsPip());
+
+        // Assert that we could not create a pinned stack with an extreme aspect ratio
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Test
+    public void testSetPipExtremeAspectRatioMin() throws Exception {
+        testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
+                BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Test
+    public void testSetPipExtremeAspectRatioMax() throws Exception {
+        testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
+                MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
+        assumeTrue(supportsPip());
+
+        // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
+        // fails (the aspect ratio remains the same)
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
+                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
+                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+        assertPinnedStackExists();
+        Rect pinnedStackBounds = getPinnedStackBounds();
+        assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
+                (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Test
+    public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the bottom pip activity
+        launchActivity(PIP_ON_STOP_ACTIVITY);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+
+        // Wait for the bottom pip activity to be stopped
+        mAmWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
+
+        // Assert that there is no pinned stack (that enterPictureInPicture() failed)
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Test
+    public void testAutoEnterPictureInPicture() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a test activity so that we're not over home
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch the PIP activity on pause
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        assertPinnedStackDoesNotExist();
+
+        // Go home and ensure that there is a pinned stack
+        launchHomeActivity();
+        assertPinnedStackExists();
+    }
+
+    @Test
+    public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a test activity so that we're not over home
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch the PIP activity on pause, and have it start another activity on
+        // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
+        // was not created in the process
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP_ON_PAUSE, "true",
+                EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY));
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY));
+        assertPinnedStackDoesNotExist();
+
+        // Go home while the pip activity is open and ensure the previous activity is not PIPed
+        launchHomeActivity();
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Test
+    public void testAutoEnterPictureInPictureFinish() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a test activity so that we're not over home
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch the PIP activity on pause, and set it to finish itself after
+        // some period.  Wait for the previous activity to be visible, and ensure that the pinned
+        // stack was not created in the process
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP_ON_PAUSE, "true",
+                EXTRA_FINISH_SELF_ON_RESUME, "true");
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the PIP activity on pause, and set the aspect ratio
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP_ON_PAUSE, "true",
+                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
+                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
+
+        // Go home while the pip activity is open to trigger auto-PIP
+        launchHomeActivity();
+        assertPinnedStackExists();
+
+        waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+        Rect bounds = getPinnedStackBounds();
+        assertFloatEquals((float) bounds.width() / bounds.height(),
+                (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoEnterPictureInPictureOverPip() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch another PIP activity
+        launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+
+        // Launch the PIP activity on pause
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+
+        // Go home while the PIP activity is open to trigger auto-enter PIP
+        launchHomeActivity();
+        assertPinnedStackExists();
+
+        // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
+        // still the first activity
+        final ActivityStack pinnedStack = getPinnedStack();
+        assertEquals(1, pinnedStack.getTasks().size());
+        assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
+                pinnedStack.getTasks().get(0).mRealActivity);
+    }
+
+    @Presubmit
+    @Test
+    public void testDisallowMultipleTasksInPinnedStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a test activity so that we have multiple fullscreen tasks
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch first PIP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+        // Launch second PIP activity
+        launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
+
+        final ActivityStack pinnedStack = getPinnedStack();
+        assertEquals(1, pinnedStack.getTasks().size());
+        assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+                PIP_ACTIVITY2, WINDOWING_MODE_PINNED));
+        assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+                PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+    }
+
+    @Test
+    public void testPipUnPipOverHome() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Go home
+        launchHomeActivity();
+        // Launch an auto pip activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_REENTER_PIP_ON_EXIT, "true");
+        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_ID) == WINDOWING_MODE_FULLSCREEN,
+                "Waiting for PIP to exit to fullscreen");
+        mAmWmState.waitForWithAmState(amState ->
+                amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID) == WINDOWING_MODE_PINNED,
+                "Waiting to re-enter PIP");
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    @Test
+    public void testPipUnPipOverApp() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a test activity so that we're not over home
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch an auto pip activity
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_REENTER_PIP_ON_EXIT, "true");
+        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_ID) == WINDOWING_MODE_FULLSCREEN,
+                "Waiting for PIP to exit to fullscreen");
+        mAmWmState.waitForWithAmState(amState ->
+                amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID) == WINDOWING_MODE_PINNED,
+                "Waiting to re-enter PIP");
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+    }
+
+    @Presubmit
+    @Test
+    public void testRemovePipWithNoFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Start with a clean slate, remove all the stacks but home
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+        // Launch a pip activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Remove the stack and ensure that the task is now in the fullscreen stack (when no
+        // fullscreen stack existed before)
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+    }
+
+    @Presubmit
+    @Test
+    public void testRemovePipWithVisibleFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity, and a pip activity over that
+        launchActivity(TEST_ACTIVITY);
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
+        // top fullscreen activity
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @FlakyTest(bugId = 70746098)
+    @Presubmit
+    @Test
+    public void testRemovePipWithHiddenFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
+        // launch a pip activity over home
+        launchActivity(TEST_ACTIVITY);
+        launchHomeActivity();
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // 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
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+    }
+
+    @Test
+    public void testMovePipToBackWithNoFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Start with a clean slate, remove all the stacks but home
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+        // Launch a pip activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Remove the stack and ensure that the task is now in the fullscreen stack (when no
+        // fullscreen stack existed before)
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+    }
+
+    @FlakyTest(bugId = 70906499)
+    @Presubmit
+    @Test
+    public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity, and a pip activity over that
+        launchActivity(TEST_ACTIVITY);
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
+        // top fullscreen activity
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+        assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @FlakyTest(bugId = 70906499)
+    @Presubmit
+    @Test
+    public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
+        // launch a pip activity over home
+        launchActivity(TEST_ACTIVITY);
+        launchHomeActivity();
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // 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 " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+        assertPinnedStackStateOnMoveToFullscreen(
+                PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+    }
+
+    @Test
+    public void testPinnedStackAlwaysOnTop() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch activity into pinned stack and assert it's on top.
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+        assertPinnedStackIsOnTop();
+
+        // Launch another activity in fullscreen stack and check that pinned stack is still on top.
+        launchActivity(TEST_ACTIVITY);
+        assertPinnedStackExists();
+        assertPinnedStackIsOnTop();
+
+        // Launch home and check that pinned stack is still on top.
+        launchHomeActivity();
+        assertPinnedStackExists();
+        assertPinnedStackIsOnTop();
+    }
+
+    @Test
+    public void testAppOpsDenyPipOnPause() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final AppOpsSession appOpsSession = new AppOpsSession(PIP_ACTIVITY)) {
+            // Disable enter-pip and try to enter pip
+            appOpsSession.setOpToMode(APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
+
+            // Launch the PIP activity on pause
+            launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+            assertPinnedStackDoesNotExist();
+
+            // Go home and ensure that there is no pinned stack
+            launchHomeActivity();
+            assertPinnedStackDoesNotExist();
+        }
+    }
+
+    @Test
+    public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Try to enter picture-in-picture from an activity that has more than one activity in the
+        // task and ensure that it works
+        launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+    }
+
+    @Test
+    public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
+        assumeTrue(supportsPip());
+
+        /*
+         * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
+         * stopped and actually went into the pinned stack.
+         *
+         * Note that this is a workaround because to trigger the path that we want to happen in
+         * activity manager, we need to add the leaving activity to the stopping state, which only
+         * happens when a hidden stack is brought forward. Normally, this happens when you go home,
+         * 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
+         *    fullscreen stack
+         * 4) Bring the activity in the dynamic stack forward to trigger PiP
+         */
+        int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
+        resizeStack(stackId, 0, 0, 500, 500);
+        // 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)
+        launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                EXTRA_ENTER_PIP_ON_PAUSE, "true",
+                EXTRA_ON_PAUSE_DELAY, "350",
+                EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+        assertPinnedStackExists();
+    }
+
+    @Test
+    public void testDisallowEnterPipActivityLocked() throws Exception {
+        assumeTrue(supportsPip());
+
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        ActivityTask task = mAmWmState.getAmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).getTopTask();
+
+        // 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 " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackDoesNotExist();
+        launchHomeActivity();
+        assertPinnedStackDoesNotExist();
+        executeShellCommand("am task lock stop");
+    }
+
+    @FlakyTest(bugId = 70328524)
+    @Presubmit
+    @Test
+    public void testConfigurationChangeOrderDuringTransition() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a PiP activity and ensure configuration change only happened once, and that the
+        // configuration change happened after the picture-in-picture and multi-window callbacks
+        launchActivity(PIP_ACTIVITY);
+        LogSeparator logSeparator = clearLogcat();
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
+        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+
+        // Trigger it to go back to fullscreen and ensure that only triggered one configuration
+        // change as well
+        logSeparator = clearLogcat();
+        launchActivity(PIP_ACTIVITY);
+        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
+        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+    }
+
+    /** Helper class to save, set, and restore transition_animation_scale preferences. */
+    private static class TransitionAnimationScaleSession extends SettingsSession<Float> {
+        TransitionAnimationScaleSession() {
+            super(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
+                    Settings.Global::getFloat,
+                    Settings.Global::putFloat);
+        }
+    }
+
+    @Test
+    public void testEnterPipInterruptedCallbacks() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final TransitionAnimationScaleSession transitionAnimationScaleSession =
+                new TransitionAnimationScaleSession()) {
+            // Slow down the transition animations for this test
+            transitionAnimationScaleSession.set(20f);
+
+            // Launch a PiP activity
+            launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+            // Wait until the PiP activity has moved into the pinned stack (happens before the
+            // transition has started)
+            mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                    .setWindowingMode(WINDOWING_MODE_PINNED)
+                    .setActivityType(ACTIVITY_TYPE_STANDARD)
+                    .build());
+            assertPinnedStackExists();
+
+            // Relaunch the PiP activity back into fullscreen
+            LogSeparator logSeparator = clearLogcat();
+            launchActivity(PIP_ACTIVITY);
+            // Wait until the PiP activity is reparented into the fullscreen stack (happens after
+            // the transition has finished)
+            mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                    .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                    .setActivityType(ACTIVITY_TYPE_STANDARD)
+                    .build());
+
+            // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
+            // configuration change (since none was sent)
+            final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
+                    PIP_ACTIVITY, logSeparator);
+            assertEquals("onConfigurationChanged", 0, lifecycleCounts.mConfigurationChangedCount);
+            assertEquals("onPictureInPictureModeChanged", 1,
+                    lifecycleCounts.mPictureInPictureModeChangedCount);
+            assertEquals("onMultiWindowModeChanged", 1,
+                    lifecycleCounts.mMultiWindowModeChangedCount);
+        }
+    }
+
+    @FlakyTest(bugId = 71564769)
+    @Presubmit
+    @Test
+    public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a PiP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Dismiss it
+        LogSeparator logSeparator = clearLogcat();
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+
+        // Confirm that we get stop before the multi-window and picture-in-picture mode change
+        // callbacks
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
+                logSeparator);
+        assertEquals("onStop", 1, lifecycleCounts.mStopCount);
+        assertEquals("onPictureInPictureModeChanged", 1,
+                lifecycleCounts.mPictureInPictureModeChangedCount);
+        assertEquals("onMultiWindowModeChanged", 1, lifecycleCounts.mMultiWindowModeChangedCount);
+        final int lastStopLine = lifecycleCounts.mLastStopLineIndex;
+        final int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
+        final int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
+        assertThat("onStop should be before onPictureInPictureModeChanged",
+                lastStopLine, lessThan(lastPipLine));
+        assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
+                lastPipLine, lessThan(lastMwLine));
+    }
+
+    @Test
+    public void testPreventSetAspectRatioWhileExpanding() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the PiP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+        // 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 " + PIP_ACTIVITY_ACTION_EXPAND_PIP
+                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR + " 123456789"
+                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR + " 100000000");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Test
+    public void testSetRequestedOrientationWhilePinned() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the PiP activity fixed as portrait, and enter picture-in-picture
+        launchActivity(PIP_ACTIVITY,
+                EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
+                EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+
+        // Request that the orientation is set to landscape
+        executeShellCommand("am broadcast -a "
+                + PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION + " -e "
+                + EXTRA_FIXED_ORIENTATION + " " + String.valueOf(ORIENTATION_LANDSCAPE));
+
+        // Launch the activity back into fullscreen and ensure that it is now in landscape
+        launchActivity(PIP_ACTIVITY);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackDoesNotExist();
+        assertEquals(ORIENTATION_LANDSCAPE, mAmWmState.getWmState().getLastOrientation());
+    }
+
+    @Test
+    public void testWindowButtonEntersPip() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch the PiP activity trigger the window button, ensure that we have entered PiP
+        launchActivity(PIP_ACTIVITY);
+        pressWindowButton();
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+    }
+
+    @Test
+    public void testFinishPipActivityWithTaskOverlay() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch PiP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+        int taskId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_PINNED).getTopTask().mTaskId;
+
+        // Ensure that we don't any any other overlays as a result of launching into PIP
+        launchHomeActivity();
+
+        // Launch task overlay activity into PiP activity task
+        launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
+
+        // Finish the PiP activity and ensure that there is no pinned stack
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
+        mAmWmState.waitForWithAmState(amState -> getPinnedStack() == null,
+                "Waiting for pinned stack to be removed...");
+        assertPinnedStackDoesNotExist();
+    }
+
+    @Test
+    public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch PiP activity
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+        int taskId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_PINNED).getTopTask().mTaskId;
+
+        // Launch task overlay activity into PiP activity task
+        launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
+
+        // Finish the task overlay activity while animating and ensure that the PiP activity never
+        // got resumed
+        LogSeparator logSeparator = clearLogcat();
+        executeShellCommand("am stack resize-animated 4 20 20 500 500");
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mAmWmState.waitFor((amState, wmState) ->
+                        !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
+                "Waiting for test activity to finish...");
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
+                logSeparator);
+        assertEquals("onResume", 0, lifecycleCounts.mResumeCount);
+        assertEquals("onPause", 0, lifecycleCounts.mPauseCount);
+    }
+
+    @Test
+    public void testPinnedStackWithDockedStack() throws Exception {
+        assumeTrue(supportsPip());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                        .setRandomData(true)
+                        .setMultipleTask(false)
+        );
+        mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+        // Launch the activities again to take focus and make sure nothing is hidden
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                        .setRandomData(true)
+                        .setMultipleTask(false)
+        );
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+        // Go to recents to make sure that fullscreen stack is invisible
+        // Some devices do not support recents or implement it differently (instead of using a
+        // separate stack id or as an activity), for those cases the visibility asserts will be
+        // ignored
+        pressAppSwitchButtonAndWaitForRecents();
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
+        // affinity
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+        launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
+        assertPinnedStackExists();
+
+        // Launch the root activity again...
+        int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivity(
+                TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
+        launchHomeActivity();
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+
+        // ...and ensure that the root activity task is found and reused, and that the pinned stack
+        // is unaffected
+        assertPinnedStackExists();
+        mAmWmState.assertFocusedActivity("Expected root activity focused",
+                TEST_ACTIVITY_WITH_SAME_AFFINITY);
+        assertEquals(rootActivityTaskId, mAmWmState.getAmState().getTaskByActivity(
+                TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
+    }
+
+    @Test
+    public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
+        // affinity, and also launch another activity in the same task, while finishing itself. As
+        // a result, the task will not have a component matching the same activity as what it was
+        // started with
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
+                EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY),
+                EXTRA_FINISH_SELF_ON_RESUME, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
+        mAmWmState.waitForValidState(
+                new WaitForValidActivityState.Builder(PIP_ACTIVITY_WITH_SAME_AFFINITY)
+                        .setWindowingMode(WINDOWING_MODE_PINNED)
+                        .setActivityType(ACTIVITY_TYPE_STANDARD)
+                        .build());
+        assertPinnedStackExists();
+
+        // Launch the root activity again...
+        int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivity(
+                TEST_ACTIVITY).mTaskId;
+        launchHomeActivity();
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+
+        // ...and ensure that even while matching purely by task affinity, the root activity task is
+        // found and reused, and that the pinned stack is unaffected
+        assertPinnedStackExists();
+        mAmWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
+        assertEquals(rootActivityTaskId, mAmWmState.getAmState().getTaskByActivity(
+                TEST_ACTIVITY).mTaskId);
+    }
+
+    @Test
+    public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch an activity into the pinned stack with a fixed affinity
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
+                EXTRA_ENTER_PIP, "true",
+                EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY),
+                EXTRA_FINISH_SELF_ON_RESUME, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackExists();
+
+        // Launch the root activity again, of the matching task and ensure that we expand to
+        // fullscreen
+        int activityTaskId = mAmWmState.getAmState().getTaskByActivity(PIP_ACTIVITY).mTaskId;
+        launchHomeActivity();
+        launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        assertPinnedStackDoesNotExist();
+        assertEquals(activityTaskId, mAmWmState.getAmState().getTaskByActivity(
+                PIP_ACTIVITY).mTaskId);
+    }
+
+    /** Test that reported display size corresponds to fullscreen after exiting PiP. */
+    @FlakyTest
+    @Presubmit
+    @Test
+    public void testDisplayMetricsPinUnpin() throws Exception {
+        assumeTrue(supportsPip());
+
+        LogSeparator logSeparator = clearLogcat();
+        launchActivity(TEST_ACTIVITY);
+        final int defaultWindowingMode = mAmWmState.getAmState()
+                .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
+        final ReportedSizes initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
+                logSeparator);
+        final Rect initialAppBounds = readAppBounds(TEST_ACTIVITY, logSeparator);
+        assertNotNull("Must report display dimensions", initialSizes);
+        assertNotNull("Must report app bounds", initialAppBounds);
+
+        logSeparator = clearLogcat();
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.waitForAppTransitionIdle();
+        final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+                logSeparator);
+        final Rect pinnedAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+        assertNotEquals("Reported display size when pinned must be different from default",
+                initialSizes, pinnedSizes);
+        final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
+        final Size pinnedAppSize = new Size(pinnedAppBounds.width(), pinnedAppBounds.height());
+        assertNotEquals("Reported app size when pinned must be different from default",
+                initialAppSize, pinnedAppSize);
+
+        logSeparator = clearLogcat();
+        launchActivity(PIP_ACTIVITY, defaultWindowingMode);
+        final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+                logSeparator);
+        final Rect finalAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+        final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
+        assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
+        assertEquals("Must report default app size after exiting PiP", initialAppSize,
+                finalAppSize);
+    }
+
+    @Presubmit
+    @Test
+    public void testEnterPictureInPictureSavePosition() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch PiP activity with auto-enter PiP, save the default position of the PiP
+        // (while the PiP is still animating sleep)
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        mAmWmState.waitForAppTransitionIdle();
+        assertPinnedStackExists();
+
+        // Move the PiP to a new position on screen
+        final int stackId = getPinnedStack().mStackId;
+        final Rect initialDefaultBounds = mAmWmState.getWmState().getDefaultPinnedStackBounds();
+        final Rect offsetStackBounds = getPinnedStackBounds();
+        offsetStackBounds.offset(0, -100);
+        resizeStack(stackId, offsetStackBounds.left, offsetStackBounds.top, offsetStackBounds.right,
+                offsetStackBounds.bottom);
+
+        // 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 " + PIP_ACTIVITY_ACTION_EXPAND_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.computeState(true);
+        // Due to rounding in how we save and apply the snap fraction we may be a pixel off, so just
+        // account for that in this check
+        offsetStackBounds.inset(-1, -1);
+        assertTrue("Expected offsetBounds=" + offsetStackBounds + " to contain bounds="
+                + getPinnedStackBounds(), offsetStackBounds.contains(getPinnedStackBounds()));
+
+        // 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 " + PIP_ACTIVITY_ACTION_EXPAND_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        launchActivity(TEST_ACTIVITY);
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_RESUMED);
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(PIP_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.computeState(true);
+        assertTrue("Expected initialBounds=" + initialDefaultBounds + " to equal bounds="
+                + getPinnedStackBounds(), initialDefaultBounds.equals(getPinnedStackBounds()));
+    }
+
+    @Presubmit
+    @Test
+    @FlakyTest(bugId = 71792368)
+    public void testEnterPictureInPictureDiscardSavedPositionOnFinish() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch PiP activity with auto-enter PiP, save the default position of the PiP
+        // (while the PiP is still animating sleep)
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+        mAmWmState.waitForAppTransitionIdle();
+
+        // Move the PiP to a new position on screen
+        final int stackId = getPinnedStack().mStackId;
+        final Rect initialDefaultBounds = mAmWmState.getWmState().getDefaultPinnedStackBounds();
+        final Rect offsetStackBounds = getPinnedStackBounds();
+        offsetStackBounds.offset(0, -100);
+        resizeStack(stackId, offsetStackBounds.left, offsetStackBounds.top, offsetStackBounds.right,
+                offsetStackBounds.bottom);
+
+        // Finish the activity
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
+        mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_DESTROYED);
+        mAmWmState.waitForWithAmState(amState -> getPinnedStack() == null,
+                "Waiting for pinned stack to be removed...");
+        assertPinnedStackDoesNotExist();
+
+        // Ensure that starting the same PiP activity after it finished will go to the default
+        // bounds
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        assertPinnedStackExists();
+        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.computeState(true);
+        assertTrue("Expected initialBounds=" + initialDefaultBounds + " to equal bounds="
+                + getPinnedStackBounds(), initialDefaultBounds.equals(getPinnedStackBounds()));
+    }
+
+    private static final Pattern sAppBoundsPattern = Pattern.compile(
+            "(.+)mAppBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)(.*)");
+
+    /** Read app bounds in last applied configuration from logs. */
+    private Rect readAppBounds(ComponentName activityName, LogSeparator logSeparator)
+            throws Exception {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sAppBoundsPattern.matcher(line);
+            if (matcher.matches()) {
+                final int left = Integer.parseInt(matcher.group(2));
+                final int top = Integer.parseInt(matcher.group(3));
+                final int right = Integer.parseInt(matcher.group(4));
+                final int bottom = Integer.parseInt(matcher.group(5));
+                return new Rect(left, top, right - left, bottom - top);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
+     * that the stack matching the {@param windowingMode} and {@param activityType} is focused, and
+     * checks the top and/or bottom tasks in the fullscreen stack if
+     * {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity} are set
+     * respectively.
+     */
+    private void assertPinnedStackStateOnMoveToFullscreen(ComponentName activityName,
+            int windowingMode, int activityType) throws Exception {
+        mAmWmState.waitForFocusedStack(windowingMode, activityType);
+        mAmWmState.assertFocusedStack("Wrong focused stack", windowingMode, activityType);
+        mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
+        assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
+        assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+                activityName, WINDOWING_MODE_FULLSCREEN));
+        assertPinnedStackDoesNotExist();
+    }
+
+    /**
+     * Asserts that the pinned stack bounds does not intersect with the IME bounds.
+     */
+    private void assertPinnedStackDoesNotIntersectIME() throws Exception {
+        // Ensure that the IME is visible
+        WindowManagerState wmState = mAmWmState.getWmState();
+        wmState.computeState();
+        WindowManagerState.WindowState imeWinState = wmState.getInputMethodWindowState();
+        assertTrue(imeWinState != null);
+
+        // Ensure that the PIP movement is constrained by the display bounds intersecting the
+        // non-IME bounds
+        Rect imeContentFrame = imeWinState.getContentFrame();
+        Rect imeContentInsets = imeWinState.getGivenContentInsets();
+        Rect imeBounds = new Rect(imeContentFrame.left + imeContentInsets.left,
+                imeContentFrame.top + imeContentInsets.top,
+                imeContentFrame.right - imeContentInsets.width(),
+                imeContentFrame.bottom - imeContentInsets.height());
+        wmState.computeState();
+        Rect pipMovementBounds = wmState.getPinnedStackMomentBounds();
+        assertTrue(!Rect.intersects(pipMovementBounds, imeBounds));
+    }
+
+    /**
+     * Asserts that the pinned stack bounds is contained in the display bounds.
+     */
+    private void assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName)
+            throws Exception {
+        final WindowManagerState.WindowState windowState = getWindowState(activityName);
+        final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(
+                windowState.getDisplayId());
+        final Rect displayRect = display.getDisplayRect();
+        final Rect pinnedStackBounds = getPinnedStackBounds();
+        assertTrue(displayRect.contains(pinnedStackBounds));
+    }
+
+    /**
+     * Asserts that the pinned stack exists.
+     */
+    private void assertPinnedStackExists() throws Exception {
+        mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * Asserts that the pinned stack does not exist.
+     */
+    private void assertPinnedStackDoesNotExist() throws Exception {
+        mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * Asserts that the pinned stack is the front stack.
+     */
+    private void assertPinnedStackIsOnTop() throws Exception {
+        mAmWmState.assertFrontStack("Pinned stack must always be on top.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * Asserts that the activity received exactly one of each of the callbacks when entering and
+     * exiting picture-in-picture.
+     */
+    private void assertValidPictureInPictureCallbackOrder(
+            ComponentName activityName, LogSeparator logSeparator) throws Exception {
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+                logSeparator);
+
+        assertEquals(getActivityName(activityName) + " onConfigurationChanged()",
+                1, lifecycleCounts.mConfigurationChangedCount);
+        assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
+                1, lifecycleCounts.mPictureInPictureModeChangedCount);
+        assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
+                1, lifecycleCounts.mMultiWindowModeChangedCount);
+        int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
+        int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
+        int lastConfigLine = lifecycleCounts.mLastConfigurationChangedLineIndex;
+        assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
+                lastPipLine, lessThan(lastMwLine));
+        assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
+                lastMwLine, lessThan(lastConfigLine));
+    }
+
+    /**
+     * Waits until the expected picture-in-picture callbacks have been made.
+     */
+    private void waitForValidPictureInPictureCallbacks(
+            ComponentName activityName, LogSeparator logSeparator) throws Exception {
+        mAmWmState.waitFor((amState, wmState) -> {
+            final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
+                    activityName, logSeparator);
+            return lifecycleCounts.mConfigurationChangedCount == 1 &&
+                    lifecycleCounts.mPictureInPictureModeChangedCount == 1 &&
+                    lifecycleCounts.mMultiWindowModeChangedCount == 1;
+        }, "Waiting for picture-in-picture activity callbacks...");
+    }
+
+    private void waitForValidAspectRatio(int num, int denom) throws Exception {
+        // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
+        // and before we can check the pinned stack bounds
+        mAmWmState.waitForWithAmState((state) -> {
+            Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
+            return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
+        }, "waitForValidAspectRatio");
+    }
+
+    /**
+     * @return the window state for the given {@param activityName}'s window.
+     */
+    private WindowManagerState.WindowState getWindowState(ComponentName activityName)
+            throws Exception {
+        String windowName = getWindowName(activityName);
+        mAmWmState.computeState(activityName);
+        final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
+        return tempWindowList.get(0);
+    }
+
+    /**
+     * @return the current pinned stack.
+     */
+    private ActivityStack getPinnedStack() {
+        return mAmWmState.getAmState().getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
+    }
+
+    /**
+     * @return the current pinned stack bounds.
+     */
+    private Rect getPinnedStackBounds() {
+        return getPinnedStack().getBounds();
+    }
+
+    /**
+     * Compares two floats with a common epsilon.
+     */
+    private void assertFloatEquals(float actual, float expected) {
+        if (!floatEquals(actual, expected)) {
+            fail(expected + " not equal to " + actual);
+        }
+    }
+
+    private boolean floatEquals(float a, float b) {
+        return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
+    }
+
+    /**
+     * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
+     */
+    private void tapToFinishPip() throws Exception {
+        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));
+    }
+
+    /**
+     * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
+     */
+    private void launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId)
+            throws Exception {
+        executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
+
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+    }
+
+    private static class AppOpsSession implements AutoCloseable {
+
+        private final String mPackageName;
+
+        AppOpsSession(ComponentName activityName) {
+            mPackageName = activityName.getPackageName();
+        }
+
+        void setOpToMode(String op, int mode) throws Exception {
+            setAppOpsOpToMode(mPackageName, op, mode);
+        }
+
+        @Override
+        public void close() throws Exception {
+            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) throws Exception {
+            executeShellCommand(String.format("appops set %s %s %d", packageName, op, mode));
+        }
+    }
+
+    /**
+     * TODO: Improve tests check to actually check that apps are not interactive instead of checking
+     *       if the stack is focused.
+     */
+    private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
+            ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable)
+            throws Exception {
+        executeShellCommand(startActivityCmd);
+        mAmWmState.waitForValidState(startActivity);
+
+        if (moveTopToPinnedStack) {
+            final int stackId = mAmWmState.getAmState().getStackIdByActivity(topActivityName);
+
+            assertNotEquals(stackId, INVALID_STACK_ID);
+            executeShellCommand(getMoveToPinnedStackCommand(stackId));
+        }
+
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .build());
+        mAmWmState.computeState(true);
+
+        if (supportsPip()) {
+            final String windowName = getWindowName(topActivityName);
+            assertPinnedStackExists();
+            mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+            mAmWmState.assertVisibility(topActivityName, true);
+
+            if (isFocusable) {
+                mAmWmState.assertFocusedStack("Pinned stack must be the focused stack.",
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+                mAmWmState.assertFocusedActivity(
+                        "Pinned activity must be focused activity.", topActivityName);
+                mAmWmState.assertFocusedWindow(
+                        "Pinned window must be focused window.", windowName);
+                // Not checking for resumed state here because PiP overlay can be launched on top
+                // in different task by SystemUI.
+            } else {
+                // Don't assert that the stack is not focused as a focusable PiP overlay can be
+                // launched on top as a task overlay by SystemUI.
+                mAmWmState.assertNotFocusedActivity(
+                        "Pinned activity can't be the focused activity.", topActivityName);
+                mAmWmState.assertNotResumedActivity(
+                        "Pinned activity can't be the resumed activity.", topActivityName);
+                mAmWmState.assertNotFocusedWindow(
+                        "Pinned window can't be focused window.", windowName);
+            }
+        } else {
+            mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java
new file mode 100644
index 0000000..0ef9364
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.SLOW_CREATE_ACTIVITY;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerReplaceWindowTests
+ */
+public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+    }
+
+    @Test
+    public void testReplaceWindow_Dock_Relaunch() throws Exception {
+        testReplaceWindow_Dock(true);
+    }
+
+    @Test
+    public void testReplaceWindow_Dock_NoRelaunch() throws Exception {
+        testReplaceWindow_Dock(false);
+    }
+
+    private void testReplaceWindow_Dock(boolean relaunch) throws Exception {
+        final ComponentName activityName = relaunch ? SLOW_CREATE_ACTIVITY : NO_RELAUNCH_ACTIVITY;
+        final String windowName = getWindowName(activityName);
+        final String amStartCmd = getAmStartCmd(activityName);
+
+        executeShellCommand(amStartCmd);
+
+        // Sleep 2 seconds, then check if the window is started properly. SlowCreateActivity
+        // will do a sleep inside its onCreate() to simulate a slow-starting app. So instead of
+        // relying on WindowManagerState's retrying mechanism, we do an explicit sleep to avoid
+        // excess spews from WindowManagerState.
+        if (relaunch) {
+            SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+        }
+
+        log("==========Before Docking========");
+        final String oldToken = getWindowToken(windowName, activityName);
+
+        // Move to docked stack
+        setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Sleep 5 seconds, then check if the window is replaced properly.
+        SystemClock.sleep(TimeUnit.SECONDS.toMillis(5));
+
+        log("==========After Docking========");
+        final String newToken = getWindowToken(windowName, activityName);
+
+        // For both relaunch and not relaunch case, we'd like the window to be kept.
+        assertEquals("Window replaced while docking.", oldToken, newToken);
+    }
+
+    private String getWindowToken(String windowName, ComponentName activityName) throws Exception {
+        mAmWmState.computeState(activityName);
+
+        mAmWmState.assertVisibility(activityName, true);
+
+        final List<String> windowTokens = new ArrayList<>();
+        mAmWmState.getWmState().getMatchingWindowTokens(windowName, windowTokens);
+
+        assertEquals("Should have exactly one window for the activity.",
+                1, windowTokens.size());
+
+        return windowTokens.get(0);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
new file mode 100644
index 0000000..82237ad
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
@@ -0,0 +1,695 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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.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_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+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.DOCKED_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.am.Components.RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.SINGLE_INSTANCE_ACTIVITY;
+import static android.server.am.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.Matchers.lessThan;
+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.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerSplitScreenTests
+ */
+public class ActivityManagerSplitScreenTests extends ActivityManagerTestBase {
+
+    private static final int TASK_SIZE = 600;
+    private static final int STACK_SIZE = 300;
+
+    private boolean mIsHomeRecentsComponent;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mAmWmState.getAmState().computeState();
+        mIsHomeRecentsComponent = mAmWmState.getAmState().isHomeRecentsComponent();
+
+        assumeTrue("Skipping test: no split multi-window support",
+                supportsSplitScreenMultiWindow());
+    }
+
+    @Test
+    public void testMinimumDeviceSize() throws Exception {
+        mAmWmState.assertDeviceDefaultDisplaySize(
+                "Devices supporting multi-window must be larger than the default minimum"
+                        + " task size");
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71792393)
+    public void testStackList() throws Exception {
+        launchActivity(TEST_ACTIVITY);
+        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.assertContainsStack("Must contain home stack.",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Test
+    @Presubmit
+    public void testDockActivity() throws Exception {
+        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
+        mAmWmState.assertContainsStack("Must contain home stack.",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Test
+    @Presubmit
+    public void testNonResizeableNotDocked() throws Exception {
+        launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
+
+        mAmWmState.assertContainsStack("Must contain home stack.",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+        mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertFrontStack("Fullscreen stack must be front stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchToSide() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchToSideMultiWindowCallbacks() throws Exception {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
+        final LogSeparator logSeparator = clearLogcat();
+        removeStacksInWindowingModes(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                TEST_ACTIVITY, logSeparator);
+        assertEquals(1, lifecycleCounts.mMultiWindowModeChangedCount);
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 72956284)
+    public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Move to docked stack.
+        LogSeparator logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                TEST_ACTIVITY, logSeparator);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.mMultiWindowModeChangedCount);
+        assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+
+        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Move activity back to fullscreen stack.
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY, logSeparator);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.mMultiWindowModeChangedCount);
+        assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchToSideAndBringToFront() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+
+        // Launch another activity to side to cover first one.
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        int taskNumberCovered = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertEquals("Fullscreen stack must have one task added.",
+                taskNumberInitial + 1, taskNumberCovered);
+        mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                NO_RELAUNCH_ACTIVITY);
+
+        // Launch activity that was first launched to side. It should be brought to front.
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setToSide(true)
+                .setWaitForLaunched(true)
+                .execute();
+        int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertEquals("Task number in fullscreen stack must remain the same.",
+                taskNumberCovered, taskNumberFinal);
+        mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71792393)
+    public void testLaunchToSideMultiple() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertNotNull("Launched to side activity must be in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again.
+        getLaunchActivityBuilder().setToSide(true).execute();
+        mAmWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
+        int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
+        mAmWmState.assertFocusedActivity("Launched to side activity must remain in front.",
+                TEST_ACTIVITY);
+        assertNotNull("Launched to side activity must remain in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 73808815)
+    public void testLaunchToSideSingleInstance() throws Exception {
+        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchToSideSingleTask() throws Exception {
+        launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
+    }
+
+    @Presubmit
+    @FlakyTest(bugId = 71792393)
+    @Test
+    public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
+        launchTargetToSide(TEST_ACTIVITY, true);
+    }
+
+    private void launchTargetToSide(ComponentName targetActivityName,
+            boolean taskCountMustIncrement) throws Exception {
+        // Launch in fullscreen first
+        getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+
+        // Move to split-screen primary
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
+        mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+                true /* onTop */, false /* animate */, null /* initialBounds */,
+                true /* showRecents */);
+        mAmWmState.waitForRecentsActivityVisible();
+
+        // Launch target to side
+        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+                .setTargetActivity(targetActivityName)
+                .setToSide(true)
+                .setRandomData(true)
+                .setMultipleTask(false);
+        targetActivityLauncher.execute();
+
+        mAmWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertNotNull("Launched to side activity must be in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again with different data.
+        targetActivityLauncher.execute();
+        mAmWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        int taskNumberSecondLaunch = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+                    taskNumberSecondLaunch);
+        } else {
+            assertEquals("Task number must not change.", taskNumberInitial,
+                    taskNumberSecondLaunch);
+        }
+        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again with different random data. Note that null
+        // cannot be used here, since the first instance of TestActivity is launched with no data
+        // in order to launch into split screen.
+        targetActivityLauncher.execute();
+        mAmWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
+                    taskNumberFinal);
+        } else {
+            assertEquals("Task number must not change.", taskNumberSecondLaunch,
+                    taskNumberFinal);
+        }
+        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Presubmit
+    @Test
+    public void testLaunchToSideMultipleWithFlag() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertNotNull("Launched to side activity must be in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
+        getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
+        mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+        int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+                taskNumberFinal);
+        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+        assertNotNull("Launched to side activity must remain in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivity(
+                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Test
+    public void testRotationWhenDocked() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        // Rotate device single steps (90°) 0-1-2-3.
+        // Each time we compute the state we implicitly assert valid bounds.
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                rotationSession.set(i);
+                mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            }
+            // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+            // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
+            rotationSession.set(ROTATION_90);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            rotationSession.set(ROTATION_270);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            rotationSession.set(ROTATION_180);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testRotationWhenDockedWhileLocked() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        mAmWmState.assertSanity();
+        mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        try (final RotationSession rotationSession = new RotationSession();
+             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            for (int i = 0; i < 4; i++) {
+                lockScreenSession.sleepDevice();
+                rotationSession.set(i);
+                lockScreenSession.wakeUpDevice()
+                        .unlockDevice();
+                mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
+            }
+        }
+    }
+
+    @Test
+    public void testMinimizedFromEachDockedSide() throws Exception {
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 2; i++) {
+                rotationSession.set(i);
+                launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
+                if (!mAmWmState.isScreenPortrait() && isTablet()) {
+                    // Test minimize to the right only on tablets in landscape
+                    removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+                    launchActivityInDockStackAndMinimize(TEST_ACTIVITY,
+                            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+                }
+                removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+            }
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testRotationWhileDockMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
+
+        // Rotate device single steps (90°) 0-1-2-3.
+        // Each time we compute the state we implicitly assert valid bounds in minimized mode.
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                rotationSession.set(i);
+                mAmWmState.computeState(TEST_ACTIVITY);
+            }
+
+            // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+            // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in
+            // minimized mode.
+            rotationSession.set(ROTATION_90);
+            mAmWmState.computeState(TEST_ACTIVITY);
+            rotationSession.set(ROTATION_270);
+            mAmWmState.computeState(TEST_ACTIVITY);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(TEST_ACTIVITY);
+            rotationSession.set(ROTATION_180);
+            mAmWmState.computeState(TEST_ACTIVITY);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(TEST_ACTIVITY);
+        }
+    }
+
+    @Test
+    public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
+        // Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
+        // going home has the correct app transition
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                rotationSession.set(i);
+                launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY);
+
+                if (mIsHomeRecentsComponent) {
+                    launchActivity(TEST_ACTIVITY,
+                            WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+                } else {
+                    // Unminimize the docked stack
+                    pressAppSwitchButtonAndWaitForRecents();
+                    waitForDockNotMinimized();
+                    assertDockNotMinimized();
+                }
+
+                // Dismiss the dock stack
+                setActivityTaskWindowingMode(DOCKED_ACTIVITY,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+                mAmWmState.computeState(DOCKED_ACTIVITY);
+
+                // Go home and check the app transition
+                assertNotEquals(
+                        TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+                pressHomeButton();
+                mAmWmState.computeState(true);
+                assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+            }
+        }
+    }
+
+    @FlakyTest(bugId = 73813034)
+    @Test
+    @Presubmit
+    public void testFinishDockActivityWhileMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
+
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        waitForDockNotMinimized();
+        mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        assertDockNotMinimized();
+    }
+
+    @Test
+    @Presubmit
+    public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
+        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
+        mAmWmState.computeState(TEST_ACTIVITY);
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice()
+                    .wakeUpDevice()
+                    .unlockDevice();
+            mAmWmState.computeState(TEST_ACTIVITY);
+            assertDockMinimized();
+        }
+    }
+
+    @Test
+    public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice()
+                    .wakeUpDevice()
+                    .unlockDevice();
+            mAmWmState.computeState(TEST_ACTIVITY);
+
+            // Unminimized back to splitscreen
+            if (mIsHomeRecentsComponent) {
+                launchActivity(RESIZEABLE_ACTIVITY,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+            } else {
+                pressAppSwitchButtonAndWaitForRecents();
+            }
+            mAmWmState.computeState(TEST_ACTIVITY);
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testResizeDockedStack() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+        resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(TEST_ACTIVITY),
+                new WaitForValidActivityState(DOCKED_ACTIVITY));
+        mAmWmState.assertContainsStack("Must contain secondary split-screen stack.",
+                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);
+    }
+
+    @Test
+    public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
+        launchActivity(TEST_ACTIVITY);
+        mAmWmState.computeState(TEST_ACTIVITY);
+        final Rect fullScreenBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).getBounds();
+
+        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(TEST_ACTIVITY);
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+
+        mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
+        final Rect initialDockBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
+
+        final LogSeparator logSeparator = clearLogcat();
+
+        Rect newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
+        resizeDockedStack(
+                newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
+        mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
+
+        // We resize twice to make sure we cross an orientation change threshold for both
+        // activities.
+        newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, false);
+        resizeDockedStack(
+                newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
+        mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+    }
+
+    private Rect computeNewDockBounds(
+            Rect fullscreenBounds, Rect dockBounds, boolean reduceSize) {
+        final boolean inLandscape = fullscreenBounds.width() > dockBounds.width();
+        // We are either increasing size or reducing it.
+        final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
+        final Rect newBounds = new Rect(dockBounds);
+        if (inLandscape) {
+            // In landscape we change the width.
+            newBounds.right = (int) (newBounds.left + (newBounds.width() * sizeChangeFactor));
+        } else {
+            // In portrait we change the height
+            newBounds.bottom = (int) (newBounds.top + (newBounds.height() * sizeChangeFactor));
+        }
+
+        return newBounds;
+    }
+
+    @Test
+    @Presubmit
+    public void testStackListOrderLaunchDockedActivity() throws Exception {
+        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
+
+        final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+        final int recentsStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
+        assertThat("Recents stack should be on top of home stack",
+                recentsStackIndex, lessThan(homeStackIndex));
+    }
+
+    @Test
+    @Presubmit
+    public void testStackListOrderOnSplitScreenDismissed() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        setActivityTaskWindowingMode(DOCKED_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(DOCKED_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .build());
+
+        final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+        final int prevSplitScreenPrimaryIndex =
+                mAmWmState.getAmState().getStackIndexByActivity(DOCKED_ACTIVITY);
+        final int prevSplitScreenSecondaryIndex =
+                mAmWmState.getAmState().getStackIndexByActivity(TEST_ACTIVITY);
+
+        final int expectedHomeStackIndex =
+                (prevSplitScreenPrimaryIndex > prevSplitScreenSecondaryIndex
+                        ? prevSplitScreenPrimaryIndex : prevSplitScreenSecondaryIndex) - 1;
+        assertEquals("Home stack needs to be directly behind the top stack",
+                expectedHomeStackIndex, homeStackIndex);
+    }
+
+    private void launchActivityInDockStackAndMinimize(ComponentName activityName) throws Exception {
+        launchActivityInDockStackAndMinimize(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+    }
+
+    private void launchActivityInDockStackAndMinimize(ComponentName activityName, int createMode)
+            throws Exception {
+        launchActivityInSplitScreenWithRecents(activityName, createMode);
+        pressHomeButton();
+        waitForAndAssertDockMinimized();
+    }
+
+    private void assertDockMinimized() {
+        assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
+    }
+
+    private void waitForAndAssertDockMinimized() throws Exception {
+        waitForDockMinimized();
+        assertDockMinimized();
+        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.assertContainsStack("Must contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+    }
+
+    private void assertDockNotMinimized() {
+        assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
+    }
+
+    private void waitForDockMinimized() throws Exception {
+        mAmWmState.waitForWithWmState(state -> state.isDockedStackMinimized(),
+                "***Waiting for Dock stack to be minimized");
+    }
+
+    private void waitForDockNotMinimized() throws Exception {
+        mAmWmState.waitForWithWmState(state -> !state.isDockedStackMinimized(),
+                "***Waiting for Dock stack to not be minimized");
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
new file mode 100644
index 0000000..925f9a4
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.Components.BOTTOM_ACTIVITY;
+import static android.server.am.Components.TOP_ACTIVITY;
+import static android.server.am.Components.TRANSLUCENT_TOP_ACTIVITY;
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_INTRA_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_INTRA_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+/**
+ * This test tests the transition type selection logic in ActivityManager/WindowManager.
+ * BottomActivity is started first, then TopActivity, and we check the transition type that the
+ * system selects when TopActivity enters or exits under various setups.
+ *
+ * Note that we only require the correct transition type to be reported (eg. TRANSIT_ACTIVITY_OPEN,
+ * TRANSIT_TASK_CLOSE, TRANSIT_WALLPAPER_OPEN, etc.). The exact animation is unspecified and can be
+ * overridden.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerTransitionSelectionTests
+ */
+@Presubmit
+@FlakyTest(bugId = 71792333)
+public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
+
+    // Test activity open/close under normal timing
+    @Test
+    public void testOpenActivity_NeitherWallpaper() throws Exception {
+        testOpenActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_ACTIVITY_OPEN);
+    }
+
+    @Test
+    public void testCloseActivity_NeitherWallpaper() throws Exception {
+        testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
+    }
+
+    @Test
+    public void testOpenActivity_BottomWallpaper() throws Exception {
+        testOpenActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
+    }
+
+    @Test
+    public void testCloseActivity_BottomWallpaper() throws Exception {
+        testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testOpenActivity_BothWallpaper() throws Exception {
+        testOpenActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
+    }
+
+    @Test
+    public void testCloseActivity_BothWallpaper() throws Exception {
+        testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    //------------------------------------------------------------------------//
+
+    // Test task open/close under normal timing
+    @Test
+    public void testOpenTask_NeitherWallpaper() throws Exception {
+        testOpenTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_TASK_OPEN);
+    }
+
+    @FlakyTest(bugId = 71792333)
+    @Test
+    public void testCloseTask_NeitherWallpaper() throws Exception {
+        testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_TASK_CLOSE);
+    }
+
+    @Test
+    public void testOpenTask_BottomWallpaper() throws Exception {
+        testOpenTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
+    }
+
+    @Test
+    public void testCloseTask_BottomWallpaper() throws Exception {
+        testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testOpenTask_BothWallpaper() throws Exception {
+        testOpenTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
+    }
+
+    @Test
+    public void testCloseTask_BothWallpaper() throws Exception {
+        testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    //------------------------------------------------------------------------//
+
+    // Test activity close -- bottom activity slow in stopping
+    // These simulate the case where the bottom activity is resumed
+    // before AM receives its activitiyStopped
+    @Test
+    public void testCloseActivity_NeitherWallpaper_SlowStop() throws Exception {
+        testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
+    }
+
+    @Test
+    public void testCloseActivity_BottomWallpaper_SlowStop() throws Exception {
+        testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testCloseActivity_BothWallpaper_SlowStop() throws Exception {
+        testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    //------------------------------------------------------------------------//
+
+    // Test task close -- bottom task top activity slow in stopping
+    // These simulate the case where the bottom activity is resumed
+    // before AM receives its activitiyStopped
+    @FlakyTest(bugId = 71792333)
+    @Test
+    public void testCloseTask_NeitherWallpaper_SlowStop() throws Exception {
+        testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_TASK_CLOSE);
+    }
+
+    @Test
+    public void testCloseTask_BottomWallpaper_SlowStop() throws Exception {
+        testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testCloseTask_BothWallpaper_SlowStop() throws Exception {
+        testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    //------------------------------------------------------------------------//
+
+    /// Test closing of translucent activity/task
+    @Test
+    public void testCloseActivity_NeitherWallpaper_Translucent() throws Exception {
+        testCloseActivityTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                TRANSIT_ACTIVITY_CLOSE);
+    }
+
+    @Test
+    public void testCloseActivity_BottomWallpaper_Translucent() throws Exception {
+        testCloseActivityTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testCloseActivity_BothWallpaper_Translucent() throws Exception {
+        testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    @Test
+    public void testCloseTask_NeitherWallpaper_Translucent() throws Exception {
+        testCloseTaskTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
+                TRANSIT_TASK_CLOSE);
+    }
+
+    @FlakyTest(bugId = 71792333)
+    @Test
+    public void testCloseTask_BottomWallpaper_Translucent() throws Exception {
+        testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
+                TRANSIT_WALLPAPER_OPEN);
+    }
+
+    @Test
+    public void testCloseTask_BothWallpaper_Translucent() throws Exception {
+        testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
+                TRANSIT_WALLPAPER_INTRA_CLOSE);
+    }
+
+    //------------------------------------------------------------------------//
+
+    private void testOpenActivity(boolean bottomWallpaper,
+            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+        testTransitionSelection(true /*testOpen*/, false /*testNewTask*/,
+                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+    }
+
+    private void testCloseActivity(boolean bottomWallpaper,
+            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+        testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
+                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+    }
+
+    private void testOpenTask(boolean bottomWallpaper,
+            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+        testTransitionSelection(true /*testOpen*/, true /*testNewTask*/,
+                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+    }
+
+    private void testCloseTask(boolean bottomWallpaper,
+            boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+        testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
+                bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+    }
+
+    private void testCloseActivityTranslucent(boolean bottomWallpaper,
+            boolean topWallpaper, String expectedTransit) throws Exception {
+        testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
+                bottomWallpaper, topWallpaper, true /*topTranslucent*/,
+                false /*slowStop*/, expectedTransit);
+    }
+
+    private void testCloseTaskTranslucent(boolean bottomWallpaper,
+            boolean topWallpaper, String expectedTransit) throws Exception {
+        testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
+                bottomWallpaper, topWallpaper, true /*topTranslucent*/,
+                false /*slowStop*/, expectedTransit);
+    }
+
+    //------------------------------------------------------------------------//
+
+    private void testTransitionSelection(
+            boolean testOpen, boolean testNewTask,
+            boolean bottomWallpaper, boolean topWallpaper, boolean topTranslucent,
+            boolean testSlowStop, String expectedTransit) throws Exception {
+        String bottomStartCmd = getAmStartCmd(BOTTOM_ACTIVITY);
+        if (bottomWallpaper) {
+            bottomStartCmd += " --ez USE_WALLPAPER true";
+        }
+        if (testSlowStop) {
+            bottomStartCmd += " --ei STOP_DELAY 3000";
+        }
+        executeShellCommand(bottomStartCmd);
+
+        mAmWmState.computeState(BOTTOM_ACTIVITY);
+
+        final ComponentName topActivity = topTranslucent ? TRANSLUCENT_TOP_ACTIVITY : TOP_ACTIVITY;
+        String topStartCmd = getAmStartCmd(topActivity);
+        if (testNewTask) {
+            topStartCmd += " -f 0x18000000";
+        }
+        if (topWallpaper) {
+            topStartCmd += " --ez USE_WALLPAPER true";
+        }
+        if (!testOpen) {
+            topStartCmd += " --ei FINISH_DELAY 1000";
+        }
+        executeShellCommand(topStartCmd);
+
+        Thread.sleep(5000);
+        if (testOpen) {
+            mAmWmState.computeState(topActivity);
+        } else {
+            mAmWmState.computeState(BOTTOM_ACTIVITY);
+        }
+
+        assertEquals("Picked wrong transition", expectedTransit,
+                mAmWmState.getWmState().getLastTransition());
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
new file mode 100644
index 0000000..82576e8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.ALT_LAUNCHING_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.RESIZEABLE_ACTIVITY;
+import static android.server.am.Components.VR_TEST_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerVrDisplayTests
+ */
+public class ActivityManagerVrDisplayTests extends ActivityManagerDisplayTestBase {
+    private static final int VR_VIRTUAL_DISPLAY_WIDTH = 700;
+    private static final int VR_VIRTUAL_DISPLAY_HEIGHT = 900;
+    private static final int VR_VIRTUAL_DISPLAY_DPI = 320;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(supportsVrMode());
+    }
+
+    private static class VrModeSession implements AutoCloseable {
+
+        void enablePersistentVrMode() throws Exception {
+            executeShellCommand("setprop vr_virtualdisplay true");
+            executeShellCommand("vr set-persistent-vr-mode-enabled true");
+        }
+
+        @Override
+        public void close() throws Exception {
+            executeShellCommand("vr set-persistent-vr-mode-enabled false");
+            executeShellCommand("setprop vr_virtualdisplay false");
+        }
+    }
+
+    /**
+     * 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()) {
+            // Put the device in persistent vr mode.
+            vrModeSession.enablePersistentVrMode();
+
+            // Launch the VR activity.
+            launchActivity(VR_TEST_ACTIVITY);
+            mAmWmState.computeState(VR_TEST_ACTIVITY);
+            mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
+
+            // Launch the non-VR 2D activity and check where it ends up.
+            launchActivity(LAUNCHING_ACTIVITY);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+            // Ensure that the subsequent activity is visible
+            mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+            // Check that activity is launched in focused stack on primary display.
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    LAUNCHING_ACTIVITY);
+            final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            final ActivityManagerState.ActivityStack focusedStack
+                    = mAmWmState.getAmState().getStackById(focusedStackId);
+            assertEquals("Launched activity must be resumed in focused stack",
+                    getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+
+            // Check if the launch activity is in Vr virtual display id.
+            final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+            final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+                    VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+            assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+            // Check if the focused activity is on this virtual stack.
+            assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+                    focusedStack.mDisplayId);
+        }
+    }
+
+    /**
+     * Tests that any activity already present is re-launched in Vr display in vr mode.
+     */
+    @Test
+    public void testVrActivityReLaunch() throws Exception {
+        assumeTrue(supportsMultiDisplay());
+
+        // Launch a 2D activity.
+        launchActivity(LAUNCHING_ACTIVITY);
+
+        try (final VrModeSession vrModeSession = new VrModeSession()) {
+            // Put the device in persistent vr mode.
+            vrModeSession.enablePersistentVrMode();
+
+            // Launch the VR activity.
+            launchActivity(VR_TEST_ACTIVITY);
+            mAmWmState.computeState(VR_TEST_ACTIVITY);
+            mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
+
+            // Re-launch the non-VR 2D activity and check where it ends up.
+            launchActivity(LAUNCHING_ACTIVITY);
+            mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+            // Ensure that the subsequent activity is visible
+            mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+            // Check that activity is launched in focused stack on primary display.
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    LAUNCHING_ACTIVITY);
+            final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            final ActivityManagerState.ActivityStack focusedStack
+                    = mAmWmState.getAmState().getStackById(focusedStackId);
+            assertEquals("Launched activity must be resumed in focused stack",
+                    getActivityName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+
+            // Check if the launch activity is in Vr virtual display id.
+            final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+            final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+                    VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+            assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+            // Check if the focused activity is on this virtual stack.
+            assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+                    focusedStack.mDisplayId);
+        }
+    }
+
+    /**
+     * Tests that any new activity launch post Vr mode is in the main display.
+     */
+    @Test
+    public void testActivityLaunchPostVr() throws Exception {
+        assumeTrue(supportsMultiDisplay());
+
+        try (final VrModeSession vrModeSession = new VrModeSession()) {
+            // Put the device in persistent vr mode.
+            vrModeSession.enablePersistentVrMode();
+
+            // Launch the VR activity.
+            launchActivity(VR_TEST_ACTIVITY);
+            mAmWmState.computeState(VR_TEST_ACTIVITY);
+            mAmWmState.assertVisibility(VR_TEST_ACTIVITY, true /* visible */);
+
+            // Launch the non-VR 2D activity and check where it ends up.
+            launchActivity(ALT_LAUNCHING_ACTIVITY);
+            mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY);
+
+            // Ensure that the subsequent activity is visible
+            mAmWmState.assertVisibility(ALT_LAUNCHING_ACTIVITY, true /* visible */);
+
+            // Check that activity is launched in focused stack on primary display.
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    ALT_LAUNCHING_ACTIVITY);
+            final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+            final ActivityManagerState.ActivityStack focusedStack
+                    = mAmWmState.getAmState().getStackById(focusedStackId);
+            assertEquals("Launched activity must be resumed in focused stack",
+                    getActivityName(ALT_LAUNCHING_ACTIVITY),
+                    focusedStack.mResumedActivity);
+
+            // Check if the launch activity is in Vr virtual display id.
+            final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+            final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+                    VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT,
+                    VR_VIRTUAL_DISPLAY_DPI);
+            assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+            // Check if the focused activity is on this virtual stack.
+            assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+                    focusedStack.mDisplayId);
+
+        }
+
+        // There isn't a direct launch of activity which can take an user out of persistent VR mode.
+        // This sleep is to account for that delay and let device settle once it comes out of VR
+        // mode.
+        try {
+            Thread.sleep(2000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        // Launch the non-VR 2D activity and check where it ends up.
+        launchActivity(RESIZEABLE_ACTIVITY);
+        mAmWmState.computeState(RESIZEABLE_ACTIVITY);
+
+        // Ensure that the subsequent activity is visible
+        mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+
+        // Check that activity is launched in focused stack on primary display.
+        mAmWmState.assertFocusedActivity("Launched activity must be focused", RESIZEABLE_ACTIVITY);
+        final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+        final ActivityManagerState.ActivityStack frontStack
+                = mAmWmState.getAmState().getStackById(frontStackId);
+        assertEquals("Launched activity must be resumed in front stack",
+                getActivityName(RESIZEABLE_ACTIVITY), frontStack.mResumedActivity);
+        assertEquals("Front stack must be on primary display",
+                DEFAULT_DISPLAY_ID, frontStack.mDisplayId);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
new file mode 100644
index 0000000..c8e3fa1
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.server.am.Components.ANIMATION_TEST_ACTIVITY;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:AnimationBackgroundTests
+ */
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+    @Test
+    public void testAnimationBackground_duringAnimation() throws Exception {
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
+        getLaunchActivityBuilder()
+                .setTargetActivity(ANIMATION_TEST_ACTIVITY)
+                .setWaitForLaunched(false)
+                .execute();
+
+        // Make sure we are in the middle of the animation.
+        mAmWmState.waitForWithWmState(state -> state
+                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                        .isWindowAnimationBackgroundSurfaceShowing(),
+                "***Waiting for animation background showing");
+        assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
+                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .isWindowAnimationBackgroundSurfaceShowing());
+    }
+
+    @Test
+    public void testAnimationBackground_gone() throws Exception {
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
+        getLaunchActivityBuilder().setTargetActivity(ANIMATION_TEST_ACTIVITY).execute();
+        mAmWmState.computeState(ANIMATION_TEST_ACTIVITY);
+        assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
+                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .isWindowAnimationBackgroundSurfaceShowing());
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
new file mode 100644
index 0000000..0cb300a
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.Context.WINDOW_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+import static org.junit.Assert.fail;
+
+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;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:AspectRatioTests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioTests extends AspectRatioTestsBase {
+
+    // 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;
+
+    // Test target activity that has maxAspectRatio="true" and resizeableActivity="false".
+    public static class MaxAspectRatioActivity extends Activity {
+    }
+
+    // Test target activity that has maxAspectRatio="1.0" and resizeableActivity="true".
+    public static class MaxAspectRatioResizeableActivity extends Activity {
+    }
+
+    // Test target activity that has no maxAspectRatio defined and resizeableActivity="false".
+    public static class MaxAspectRatioUnsetActivity extends Activity {
+    }
+
+    // Test target activity that has maxAspectRatio defined as
+    //   <meta-data android:name="android.max_aspect" android:value="1.0" />
+    // and resizeableActivity="false".
+    public static class MetaDataMaxAspectRatioActivity extends Activity {
+    }
+
+    @Rule
+    public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
+            new ActivityTestRule<>(MaxAspectRatioActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
+
+    @Rule
+    public ActivityTestRule<MaxAspectRatioResizeableActivity> mMaxAspectRatioResizeableActivity =
+            new ActivityTestRule<>(MaxAspectRatioResizeableActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
+
+    @Rule
+    public ActivityTestRule<MetaDataMaxAspectRatioActivity> mMetaDataMaxAspectRatioActivity =
+            new ActivityTestRule<>(MetaDataMaxAspectRatioActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
+
+    @Rule
+    public ActivityTestRule<MaxAspectRatioUnsetActivity> mMaxAspectRatioUnsetActivity =
+            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);
+
+        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;
+
+        if (deviceAspectRatio < expectedMinAspectRatio) {
+            fail("deviceAspectRatio=" + deviceAspectRatio
+                    + " is less than expectedMinAspectRatio=" + expectedMinAspectRatio);
+        }
+    }
+
+    @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);
+        });
+    }
+
+    @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);
+        });
+    }
+
+    @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);
+
+        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);
+        });
+    }
+
+    @Test
+    public void testMaxAspectRatioUnsetActivity() throws Exception {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final float expected = getAspectRatio(context);
+
+        // 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);
+        });
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
new file mode 100644
index 0000000..6d1dc53
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+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;
+
+class AspectRatioTestsBase {
+
+    interface AssertAspectRatioCallback {
+        void assertAspectRatio(float actual);
+    }
+
+    void runAspectRatioTest(final ActivityTestRule activityRule,
+            final AssertAspectRatioCallback callback) {
+        final Activity activity = launchActivity(activityRule);
+        runTest(activity, callback);
+        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
+        // this activity if changing the orientation will cause a relaunch?
+//        activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+//        waitForIdle();
+//        callback.assertAspectRatio(getAspectRatio(activity));
+//
+//        activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+//        waitForIdle();
+//        callback.assertAspectRatio(getAspectRatio(activity));
+    }
+
+    protected void runTest(Activity activity, AssertAspectRatioCallback callback) {
+        callback.assertAspectRatio(getAspectRatio(activity));
+    }
+
+     static float getAspectRatio(Context context) {
+        final Display display =
+                context.getSystemService(WindowManager.class).getDefaultDisplay();
+        final Point size = new Point();
+        display.getSize(size);
+        final float longSide = Math.max(size.x, size.y);
+        final float shortSide = Math.min(size.x, size.y);
+        return longSide / shortSide;
+    }
+
+    protected Activity launchActivity(final ActivityTestRule activityRule) {
+        final Activity activity = activityRule.launchActivity(null);
+        waitForIdle();
+        return activity;
+    }
+
+    private void finishActivity(ActivityTestRule activityRule) {
+        final Activity activity = activityRule.getActivity();
+        if (activity != null) {
+            activity.finish();
+        }
+    }
+
+    private static void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    static boolean aspectRatioEqual(float a, float b) {
+        // Aspect ratios are considered equal if they ware within to significant digits.
+        float diff = Math.abs(a - b);
+        return diff < 0.01f;
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java b/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java
new file mode 100644
index 0000000..a86e3f8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.deprecatedsdk.Components.MAIN_ACTIVITY;
+
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Ensure that compatibility dialog is shown when launching an application
+ * targeting a deprecated version of SDK.
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:DeprecatedTargetSdkTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeprecatedTargetSdkTest extends ActivityManagerTestBase {
+
+    /** @see com.android.server.am.DeprecatedTargetSdkVersionDialog */
+    private static final String DEPRECATED_TARGET_SDK_VERSION_DIALOG =
+            "DeprecatedTargetSdkVersionDialog";
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        // Ensure app process is stopped.
+        stopTestPackage(MAIN_ACTIVITY);
+    }
+
+    @Test
+    public void testCompatibilityDialog() throws Exception {
+        // Launch target app.
+        launchActivity(MAIN_ACTIVITY);
+        mAmWmState.assertActivityDisplayed(MAIN_ACTIVITY);
+        mAmWmState.assertWindowDisplayed(DEPRECATED_TARGET_SDK_VERSION_DIALOG);
+
+        // Go back to dismiss the warning dialog.
+        pressBackButton();
+
+        // Go back again to formally stop the app. If we just kill the process, it'll attempt to
+        // resume rather than starting from scratch (as far as ActivityStack is concerned) and it
+        // won't invoke the warning dialog.
+        pressBackButton();
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
new file mode 100644
index 0000000..98e3ba7
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_SINGLE_TOP;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.displaysize.Components.SMALLEST_WIDTH_ACTIVITY;
+import static android.server.am.displaysize.Components.SmallestWidthActivity
+        .EXTRA_LAUNCH_ANOTHER_ACTIVITY;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Build;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Ensure that compatibility dialog is shown when launching an application with
+ * an unsupported smallest width.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:DisplaySizeTest
+ */
+public class DisplaySizeTest extends ActivityManagerTestBase {
+
+    /** @see com.android.server.am.UnsupportedDisplaySizeDialog */
+    private static final String UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME =
+            "UnsupportedDisplaySizeDialog";
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        // Ensure app process is stopped.
+        stopTestPackage(SMALLEST_WIDTH_ACTIVITY);
+        stopTestPackage(TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testCompatibilityDialog() throws Exception {
+        // Launch some other app (not to perform density change on launcher).
+        launchActivity(TEST_ACTIVITY);
+        mAmWmState.assertActivityDisplayed(TEST_ACTIVITY);
+
+        try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
+            screenDensitySession.setUnsupportedDensity();
+
+            // Launch target app.
+            launchActivity(SMALLEST_WIDTH_ACTIVITY);
+            mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+            mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+        }
+    }
+
+    @Test
+    public void testCompatibilityDialogWhenFocused() throws Exception {
+        launchActivity(SMALLEST_WIDTH_ACTIVITY);
+        mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+
+        try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
+            screenDensitySession.setUnsupportedDensity();
+
+            mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+        }
+    }
+
+    @Test
+    public void testCompatibilityDialogAfterReturn() throws Exception {
+        // Launch target app.
+        launchActivity(SMALLEST_WIDTH_ACTIVITY);
+        mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+        // Launch another activity.
+        final String startActivityOnTop = String.format("%s -f 0x%x --es %s %s",
+                getAmStartCmd(SMALLEST_WIDTH_ACTIVITY), FLAG_ACTIVITY_SINGLE_TOP,
+                EXTRA_LAUNCH_ANOTHER_ACTIVITY, getActivityName(TEST_ACTIVITY));
+        executeShellCommand(startActivityOnTop);
+        mAmWmState.assertActivityDisplayed(TEST_ACTIVITY);
+
+        try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
+            screenDensitySession.setUnsupportedDensity();
+
+            pressBackButton();
+
+            mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+            mAmWmState.assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+        }
+    }
+
+    private static class ScreenDensitySession implements AutoCloseable {
+        private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
+        private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
+
+        void setUnsupportedDensity() {
+            // Set device to 0.85 zoom. It doesn't matter that we're zooming out
+            // since the feature verifies that we're in a non-default density.
+            final int stableDensity = getStableDensity();
+            final int targetDensity = (int) (stableDensity * 0.85);
+            setDensity(targetDensity);
+        }
+
+        @Override
+        public void close() throws Exception {
+            resetDensity();
+        }
+
+        private int getStableDensity() {
+            final String densityProp;
+            if (Build.IS_EMULATOR) {
+                densityProp = DENSITY_PROP_EMULATOR;
+            } else {
+                densityProp = DENSITY_PROP_DEVICE;
+            }
+
+            return Integer.parseInt(executeShellCommand("getprop " + densityProp).trim());
+        }
+
+        private void setDensity(int targetDensity) {
+            executeShellCommand("wm density " + targetDensity);
+
+            // Verify that the density is changed.
+            final String output = executeShellCommand("wm density");
+            final boolean success = output.contains("Override density: " + targetDensity);
+
+            assertTrue("Failed to set density to " + targetDensity, success);
+        }
+
+        private void resetDensity() {
+            executeShellCommand("wm density reset");
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
new file mode 100644
index 0000000..3c2bac9
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+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;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:KeyguardLockedTests
+ */
+public class KeyguardLockedTests extends KeyguardTestBase {
+
+    private static final String ACTION_ENTER_PIP = "android.server.am.PipActivity.enter_pip";
+    private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(isHandheld());
+    }
+
+    @Test
+    public void testLockAndUnlock() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+            assertTrue(mKeyguardManager.isKeyguardLocked());
+            assertTrue(mKeyguardManager.isDeviceLocked());
+            assertTrue(mKeyguardManager.isDeviceSecure());
+            assertTrue(mKeyguardManager.isKeyguardSecure());
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            lockScreenSession.unlockDevice()
+                    .enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            assertFalse(mKeyguardManager.isDeviceLocked());
+            assertFalse(mKeyguardManager.isKeyguardLocked());
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_whileOccluded() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+            lockScreenSession.enterAndConfirmLockCredential();
+
+            // Make sure we stay on Keyguard.
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
+            assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertOnDismissSucceededInLogcat(logSeparator);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_cancelled() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+            pressBackButton();
+            assertOnDismissCancelledInLogcat(logSeparator);
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, false);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        }
+    }
+
+    @Test
+    public void testEnterPipOverKeyguard() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+
+            // Enter PiP on an activity on top of the keyguard, and ensure that it prompts the user
+            // for their credentials and does not enter picture-in-picture yet
+            launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+            executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+            mAmWmState.waitForKeyguardShowingAndOccluded();
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+            // Enter the credentials and ensure that the activity actually entered picture-in
+            // -picture
+            lockScreenSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+                    ACTIVITY_TYPE_STANDARD);
+        }
+    }
+
+    @Test
+    public void testShowWhenLockedActivityAndPipActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            launchActivity(PIP_ACTIVITY);
+            executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+            mAmWmState.computeState(PIP_ACTIVITY);
+            mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+                    ACTIVITY_TYPE_STANDARD);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testShowWhenLockedPipActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential();
+            launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+            executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+            mAmWmState.computeState(PIP_ACTIVITY);
+            mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+                    ACTIVITY_TYPE_STANDARD);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
new file mode 100644
index 0000000..07d125e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.fail;
+
+import android.app.KeyguardManager;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class KeyguardTestBase extends ActivityManagerTestBase {
+
+    protected KeyguardManager mKeyguardManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+    }
+
+    protected void assertOnDismissSucceededInLogcat(LogSeparator logSeparator) throws Exception {
+        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissSucceeded", logSeparator);
+    }
+
+    protected void assertOnDismissCancelledInLogcat(LogSeparator logSeparator) throws Exception {
+        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissCancelled", logSeparator);
+    }
+
+    protected void assertOnDismissErrorInLogcat(LogSeparator logSeparator) throws Exception {
+        assertInLogcat("KeyguardDismissLoggerCallback", "onDismissError", logSeparator);
+    }
+
+    private void assertInLogcat(String activityName, String entry, LogSeparator logSeparator)
+            throws Exception {
+        final Pattern pattern = Pattern.compile("(.+)" + entry);
+        int tries = 0;
+        while (tries < 5) {
+            final String[] lines = getDeviceLogsForComponents(logSeparator, activityName);
+            log("Looking at logcat");
+            for (int i = lines.length - 1; i >= 0; i--) {
+                final String line = lines[i].trim();
+                log(line);
+                Matcher matcher = pattern.matcher(line);
+                if (matcher.matches()) {
+                    return;
+                }
+            }
+            tries++;
+            Thread.sleep(500);
+        }
+        fail("Not in logcat: " + entry);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
new file mode 100644
index 0000000..56e7111
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
+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;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_DIALOG_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
+import static android.server.am.Components.TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:KeyguardTests
+ */
+public class KeyguardTests extends KeyguardTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(isHandheld());
+        assertFalse(isUiModeLockedToVrHeadset());
+    }
+
+    @Test
+    public void testKeyguardHidesActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(TEST_ACTIVITY);
+            mAmWmState.computeState(TEST_ACTIVITY);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            assertTrue(mKeyguardManager.isKeyguardLocked());
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        }
+        assertFalse(mKeyguardManager.isKeyguardLocked());
+    }
+
+    @Test
+    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();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    /**
+     * Tests whether dialogs from SHOW_WHEN_LOCKED activities are also visible if Keyguard is
+     * showing.
+     */
+    @Test
+    public void testShowWhenLockedActivity_withDialog() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            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();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
+            assertTrue(mAmWmState.getWmState().allWindowsVisible(
+                    getWindowName(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY)));
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    /**
+     * Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
+     */
+    @Test
+    public void testMultipleShowWhenLockedActivities() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY,
+                    SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    /**
+     * If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
+     */
+    @Test
+    public void testTranslucentShowWhenLockedActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            assertWallpaperShowing();
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    /**
+     * If we have a translucent SHOW_WHEN_LOCKED activity, the activity behind should not be shown.
+     */
+    @Test
+    public void testTranslucentDoesntRevealBehind() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(TEST_ACTIVITY);
+            launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.computeState(TEST_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    @Test
+    public void testDialogShowWhenLockedActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
+            assertWallpaperShowing();
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    /**
+     * Test that showWhenLocked activity is fullscreen when shown over keyguard
+     */
+    @Test
+    @Presubmit
+    public void testShowWhenLockedActivityWhileSplit() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivitiesInSplitScreen(
+                    getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                    getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_ACTIVITY)
+                            .setRandomData(true)
+                            .setMultipleTask(false)
+            );
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        }
+    }
+
+    /**
+     * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
+     */
+    @Test
+    public void testDismissKeyguardActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.waitForKeyguardShowingAndOccluded();
+            mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
+            mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
+            assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertOnDismissSucceededInLogcat(logSeparator);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_notTop() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+            launchActivity(TEST_ACTIVITY);
+            executeShellCommand(
+                    "am broadcast -a trigger_broadcast --ez dismissKeyguardMethod true");
+            assertOnDismissErrorInLogcat(logSeparator);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.sleepDevice();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.computeState(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY, true);
+            assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertOnDismissSucceededInLogcat(logSeparator);
+            assertTrue(isDisplayOn());
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+        }
+    }
+
+    @Test
+    public void testKeyguardLock() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(KEYGUARD_LOCK_ACTIVITY);
+            mAmWmState.computeState(KEYGUARD_LOCK_ACTIVITY);
+            mAmWmState.assertVisibility(KEYGUARD_LOCK_ACTIVITY, true);
+            executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+        }
+    }
+
+    @Test
+    public void testUnoccludeRotationChange() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final RotationSession rotationSession = new RotationSession()) {
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+            rotationSession.set(ROTATION_90);
+            pressHomeButton();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.waitForDisplayUnfrozen();
+            mAmWmState.assertSanity();
+            mAmWmState.assertHomeActivityVisible(false);
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
+        }
+    }
+
+    private void assertWallpaperShowing() {
+        WindowState wallpaper =
+                mAmWmState.getWmState().findFirstWindowWithType(TYPE_WALLPAPER);
+        assertNotNull(wallpaper);
+        assertTrue(wallpaper.isShown());
+    }
+
+    @Test
+    public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.sleepDevice();
+
+            final LogSeparator logSeparator = clearLogcat();
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, true);
+            assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertOnDismissSucceededInLogcat(logSeparator);
+            assertTrue(isDisplayOn());
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard()
+            throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .sleepDevice();
+
+            mAmWmState.computeState(true);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, false);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertTrue(isDisplayOn());
+        }
+    }
+
+    @Test
+    public void testScreenOffWhileOccludedStopsActivity() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            final LogSeparator logSeparator = clearLogcat();
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            lockScreenSession.sleepDevice();
+            assertSingleLaunchAndStop(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testScreenOffCausesSingleStop() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(TEST_ACTIVITY);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+            lockScreenSession.sleepDevice();
+            assertSingleLaunchAndStop(TEST_ACTIVITY, logSeparator);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
new file mode 100644
index 0000000..5e7f309
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.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;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.WALLPAPAER_ACTIVITY;
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_UNOCCLUDE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:KeyguardTransitionTests
+ */
+public class KeyguardTransitionTests extends ActivityManagerTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(isHandheld());
+        assumeFalse(isUiModeLockedToVrHeadset());
+    }
+
+    @Test
+    public void testUnlock() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(TEST_ACTIVITY);
+            lockScreenSession.gotoKeyguard()
+                    .unlockDevice();
+            mAmWmState.computeState(TEST_ACTIVITY);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
+                    mAmWmState.getWmState().getLastTransition());
+        }
+    }
+
+    @Test
+    public void testUnlockWallpaper() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(WALLPAPAER_ACTIVITY);
+            lockScreenSession.gotoKeyguard()
+                    .unlockDevice();
+            mAmWmState.computeState(WALLPAPAER_ACTIVITY);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    mAmWmState.getWmState().getLastTransition());
+        }
+    }
+
+    @Test
+    public void testOcclude() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                    mAmWmState.getWmState().getLastTransition());
+        }
+    }
+
+    @Test
+    public void testUnocclude() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            launchActivity(TEST_ACTIVITY);
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.computeState(true);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
+                    mAmWmState.getWmState().getLastTransition());
+        }
+    }
+
+    @Test
+    public void testNewActivityDuringOccluded() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            lockScreenSession.gotoKeyguard();
+            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());
+        }
+    }
+
+    @Test
+    public void testOccludeManifestAttr() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            final LogSeparator logSeparator = clearLogcat();
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                    mAmWmState.getWmState().getLastTransition());
+            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testOccludeAttrRemove() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            LogSeparator logSeparator = clearLogcat();
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+            assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                    mAmWmState.getWmState().getLastTransition());
+            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+
+            lockScreenSession.gotoKeyguard();
+            logSeparator = clearLogcat();
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+            assertSingleStartAndStop(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+        }
+    }
+
+    @Test
+    public void testNewActivityDuringOccludedWithAttr() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            lockScreenSession.gotoKeyguard();
+            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());
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
new file mode 100644
index 0000000..22fea64
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.prerelease.Components.MAIN_ACTIVITY;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Ensure that compatibility dialog is shown when launching an application built
+ * against a prerelease SDK.
+ * <p>Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:PrereleaseSdkTest
+ */
+public class PrereleaseSdkTest extends ActivityManagerTestBase {
+
+    /** @see com.android.server.am.UnsupportedCompileSdkDialog */
+    private static final String UNSUPPORTED_COMPILE_SDK_DIALOG = "UnsupportedCompileSdkDialog";
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        // Ensure app process is stopped.
+        stopTestPackage(MAIN_ACTIVITY);
+    }
+
+    @Test
+    public void testCompatibilityDialog() throws Exception {
+        // Launch target app.
+        launchActivity(MAIN_ACTIVITY);
+        mAmWmState.assertActivityDisplayed(MAIN_ACTIVITY);
+        mAmWmState.assertWindowDisplayed(UNSUPPORTED_COMPILE_SDK_DIALOG);
+
+        // Go back to dismiss the warning dialog.
+        pressBackButton();
+
+        // Go back again to formally stop the app. If we just kill the process, it'll attempt to
+        // resume rather than starting from scratch (as far as ActivityStack is concerned) and it
+        // won't invoke the warning dialog.
+        pressBackButton();
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
new file mode 100644
index 0000000..2daf99e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.Components.SPLASHSCREEN_ACTIVITY;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:SplashscreenTests
+ */
+public class SplashscreenTests extends ActivityManagerTestBase {
+
+    @Test
+    public void testSplashscreenContent() throws Exception {
+        launchActivityNoWait(SPLASHSCREEN_ACTIVITY);
+        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.getWmState().getStableBounds();
+        final Bitmap image = takeScreenshot();
+        // Use ratios to flexibly accomodate circular or not quite rectangular displays
+        // Note: Color.BLACK is the pixel color outside of the display region
+        assertColors(image, mAmWmState.getWmState().getStableBounds(),
+            Color.RED, 0.50f, Color.BLACK, 0.01f);
+    }
+
+    private void assertColors(Bitmap img, Rect bounds, int primaryColor,
+        float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio) {
+
+        int primaryPixels = 0;
+        int secondaryPixels = 0;
+        int wrongPixels = 0;
+        for (int x = bounds.left; x < bounds.right; x++) {
+            for (int y = bounds.top; y < bounds.bottom; y++) {
+                assertTrue(x < img.getWidth());
+                assertTrue(y < img.getHeight());
+                final int color = img.getPixel(x, y);
+                if (primaryColor == color) {
+                    primaryPixels++;
+                } else if (secondaryColor == color) {
+                    secondaryPixels++;
+                } else {
+                    wrongPixels++;
+                }
+            }
+        }
+
+        final int totalPixels = bounds.width() * bounds.height();
+        final float primaryRatio = (float) primaryPixels / totalPixels;
+        if (primaryRatio < expectedPrimaryRatio) {
+            fail("Less than " + (expectedPrimaryRatio * 100.0f)
+                    + "% of pixels have non-primary color primaryPixels=" + primaryPixels
+                    + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
+        }
+        // Some pixels might be covered by screen shape decorations, like rounded corners.
+        // On circular displays, there is an antialiased edge.
+        final float wrongRatio = (float) wrongPixels / totalPixels;
+        if (wrongRatio > acceptableWrongRatio) {
+            fail("More than " + (acceptableWrongRatio * 100.0f)
+                    + "% of pixels have wrong color primaryPixels=" + primaryPixels
+                    + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/StartActivityTests.java b/tests/framework/base/activitymanager/src/android/server/am/StartActivityTests.java
new file mode 100644
index 0000000..bcab43f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/StartActivityTests.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.server.am;
+
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
+
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:StartActivityTests
+ */
+@Presubmit
+@FlakyTest
+public class StartActivityTests extends ActivityManagerTestBase {
+
+    @Rule
+    public final ActivityTestRule<TestActivity2> mTestActivity2Rule =
+            new ActivityTestRule<>(TestActivity2.class);
+
+    /**
+     * Ensures {@link Activity} can only be launched from an {@link Activity}
+     * {@link android.content.Context}.
+     */
+    @Test
+    public void testStartActivityContexts() throws Exception {
+        // Launch Activity from application context.
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setUseApplicationContext(true)
+                .setSuppressExceptions(true)
+                .execute();
+
+        // Launch second Activity from Activity Context to ensure previous Activity has launched.
+        final Activity testActivity2 = mTestActivity2Rule.launchActivity(null);
+
+        mAmWmState.computeState(testActivity2.getComponentName());
+
+        // Verify Activity was not started.
+        assertFalse(mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
+        mAmWmState.assertResumedActivity(
+                "Activity launched from activity context should be present",
+                testActivity2.getComponentName());
+    }
+
+    /**
+     * Ensures you can start an {@link Activity} from a non {@link Activity}
+     * {@link android.content.Context} with the {@code FLAG_ACTIVITY_NEW_TASK}.
+     */
+    @Test
+    public void testStartActivityNewTask() throws Exception {
+        // Launch Activity from application context.
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setUseApplicationContext(true)
+                .setSuppressExceptions(true)
+                .setNewTask(true)
+                .execute();
+
+        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.assertResumedActivity("Test Activity should be started with new task flag",
+                TEST_ACTIVITY);
+    }
+
+    /**
+     * Ensures you can start an {@link Activity} from a non {@link Activity}
+     * {@link android.content.Context} when the target sdk is between N and O Mr1.
+     * @throws Exception
+     */
+    @Test
+    public void testLegacyStartActivityFromNonActivityContext() throws Exception {
+        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                .setLaunchingActivity(SDK_27_LAUNCHING_ACTIVITY)
+                .setUseApplicationContext(true)
+                .execute();
+
+        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.assertResumedActivity("Test Activity should be resumed without older sdk",
+                TEST_ACTIVITY);
+    }
+
+    public static class TestActivity2 extends Activity {
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
new file mode 100644
index 0000000..5553ddd
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -0,0 +1,134 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+
+/** Base class for device-side tests that verify correct activity lifecycle transitions. */
+public class ActivityLifecycleClientTestBase extends ActivityManagerTestBase {
+
+    final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule(FirstActivity.class,
+            true /* initialTouchMode */, false /* launchActivity */);
+
+    final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule(SecondActivity.class,
+            true /* initialTouchMode */, false /* launchActivity */);
+
+    final ActivityTestRule mTranslucentActivityTestRule = new ActivityTestRule(
+            TranslucentActivity.class, true /* initialTouchMode */, false /* launchActivity */);
+
+    final ActivityTestRule mLaunchForResultActivityTestRule = new ActivityTestRule(
+            LaunchForResultActivity.class, true /* initialTouchMode */, false /* launchActivity */);
+
+    private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
+            .getInstance();
+    private static LifecycleLog mLifecycleLog;
+    private LifecycleTracker mLifecycleTracker;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // Log transitions for all activities that belong to this app.
+        mLifecycleLog = new LifecycleLog();
+        mLifecycleMonitor.addLifecycleCallback(mLifecycleLog);
+
+        // Track transitions and allow waiting for pending activity states.
+        mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
+        mLifecycleMonitor.addLifecycleCallback(mLifecycleTracker);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        mLifecycleMonitor.removeLifecycleCallback(mLifecycleLog);
+        mLifecycleMonitor.removeLifecycleCallback(mLifecycleTracker);
+        super.tearDown();
+    }
+
+    /** Launch an activity given a class. */
+    protected Activity launchActivity(Class<? extends Activity> activityClass) {
+        final Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), activityClass);
+        return InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+    }
+
+    /**
+     * Blocking call that will wait for activities to reach expected states with timeout.
+     */
+    @SafeVarargs
+    final void waitAndAssertActivityStates(Pair<Activity, ActivityCallback>... activityCallbacks) {
+        log("Start waitAndAssertActivityCallbacks");
+        mLifecycleTracker.waitAndAssertActivityStates(activityCallbacks);
+    }
+
+    LifecycleLog getLifecycleLog() {
+        return mLifecycleLog;
+    }
+
+    static Pair<Activity, ActivityCallback> state(Activity activity, ActivityCallback stage) {
+        return new Pair<>(activity, stage);
+    }
+
+    // Test activity
+    public static class FirstActivity extends Activity {
+    }
+
+    // Test activity
+    public static class SecondActivity extends Activity {
+    }
+
+    // Translucent test activity
+    public static class TranslucentActivity extends Activity {
+    }
+
+    /**
+     * Base activity that records callbacks other then lifecycle transitions.
+     * Currently it only tracks {@link Activity#onActivityResult(int, int, Intent)}.
+     */
+    public static class CallbackTrackingActivity extends Activity {
+        @Override
+        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+            super.onActivityResult(requestCode, resultCode, data);
+            mLifecycleLog.onActivityCallback(this, ON_ACTIVITY_RESULT);
+        }
+    }
+
+    /**
+     * Test activity that launches {@link ResultActivity} for result.
+     */
+    public static class LaunchForResultActivity extends CallbackTrackingActivity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            startForResult();
+        }
+
+        private void startForResult() {
+            final Intent intent = new Intent(this, ResultActivity.class);
+            startActivityForResult(intent, 1 /* requestCode */);
+        }
+    }
+
+    /** Test activity that is started for result and finishes itself in ON_RESUME. */
+    public static class ResultActivity extends Activity {
+        @Override
+        protected void onResume() {
+            super.onResume();
+            setResult(RESULT_OK);
+            finish();
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
new file mode 100644
index 0000000..9555af7
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -0,0 +1,35 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleKeyguardTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityLifecycleKeyguardTests extends ActivityLifecycleClientTestBase {
+
+    @Test
+    public void testSingleLaunch() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential()
+                    .gotoKeyguard();
+
+            final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+            waitAndAssertActivityStates(state(activity, ON_STOP));
+
+            LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
+        }
+    }
+}
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
new file mode 100644
index 0000000..ad2fcde
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -0,0 +1,220 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AmUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityLifecycleTests extends ActivityLifecycleClientTestBase {
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testSingleLaunch() throws Exception {
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testLaunchOnTop() throws Exception {
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP),
+                state(secondActivity, ON_RESUME));
+
+        LifecycleVerifier.assertLaunchSequence(SecondActivity.class, FirstActivity.class,
+                getLifecycleLog());
+    }
+
+    @FlakyTest(bugId = 70649184)
+    @Test
+    public void testLaunchAndDestroy() throws Exception {
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        activity.finish();
+        waitAndAssertActivityStates(state(activity, ON_DESTROY));
+
+        LifecycleVerifier.assertLaunchAndDestroySequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testRelaunchResumed() throws Exception {
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(activity::recreate);
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog(), ON_RESUME);
+    }
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testRelaunchPaused() throws Exception {
+        final Activity pausedActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity topTranslucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+
+        waitAndAssertActivityStates(state(pausedActivity, ON_PAUSE),
+                state(topTranslucentActivity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(pausedActivity::recreate);
+        waitAndAssertActivityStates(state(pausedActivity, ON_PAUSE));
+
+        LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog(), ON_PAUSE);
+    }
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testRelaunchStopped() throws Exception {
+        final Activity stoppedActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity topActivity = mSecondActivityTestRule.launchActivity(new Intent());
+
+        waitAndAssertActivityStates(state(stoppedActivity, ON_STOP), state(topActivity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(stoppedActivity::recreate);
+        waitAndAssertActivityStates(state(stoppedActivity, ON_STOP));
+
+        LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog(), ON_STOP);
+    }
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testRelaunchConfigurationChangedWhileBecomingVisible() throws Exception {
+        final Activity becomingVisibleActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity topOpaqueActivity = mSecondActivityTestRule.launchActivity(new Intent());
+
+        waitAndAssertActivityStates(state(becomingVisibleActivity, ON_STOP),
+                state(translucentActivity, ON_STOP), state(topOpaqueActivity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        try (final RotationSession rotationSession = new RotationSession()) {
+            final int current = rotationSession.get();
+            // Set new rotation to cause a configuration change.
+            switch (current) {
+                case ROTATION_0:
+                case ROTATION_180:
+                    rotationSession.set(ROTATION_90);
+                    break;
+                case ROTATION_90:
+                case ROTATION_270:
+                    rotationSession.set(ROTATION_0);
+                    break;
+                default:
+                    fail("Unknown rotation:" + current);
+            }
+
+            // Assert that the top activity was relaunched.
+            waitAndAssertActivityStates(state(topOpaqueActivity, ON_RESUME));
+            LifecycleVerifier.assertRelaunchSequence(
+                    SecondActivity.class, getLifecycleLog(), ON_RESUME);
+
+            // Finish the top activity
+            getLifecycleLog().clear();
+            mSecondActivityTestRule.finishActivity();
+
+            // Assert that the translucent activity and the activity visible behind it were
+            // relaunched.
+            waitAndAssertActivityStates(state(becomingVisibleActivity, ON_PAUSE),
+                    state(translucentActivity, ON_RESUME));
+
+            LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                    Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME,
+                            ON_PAUSE), "becomingVisiblePaused");
+            LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
+                    Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME),
+                    "becomingVisibleResumed");
+        }
+    }
+
+    @FlakyTest(bugId = 72956507)
+    @Test
+    public void testOnActivityResult() throws Exception {
+        getLifecycleLog().clear();
+        final Intent intent = new Intent();
+        final Activity launchForResultActivity =
+                mLaunchForResultActivityTestRule.launchActivity(intent);
+
+        waitAndAssertActivityStates(state(launchForResultActivity, ON_RESUME));
+
+        LifecycleVerifier.assertSequence(LaunchForResultActivity.class,
+                getLifecycleLog(), Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME,
+                        ON_PAUSE, ON_ACTIVITY_RESULT, ON_RESUME), "activityResult");
+    }
+
+    /**
+     * The following test ensures an activity is brought back if its process is ended in the
+     * background.
+     */
+    @Presubmit
+    @FlakyTest
+    @Test
+    public void testRestoreFromKill() throws Exception {
+        final LaunchActivityBuilder builder = getLaunchActivityBuilder();
+        final ComponentName targetActivity = builder.getTargetActivity();
+
+        // Launch activity whose process will be killed
+        builder.execute();
+
+        // Start activity in another process to put original activity in background.
+        mFirstActivityTestRule.launchActivity(new Intent());
+        mAmWmState.waitForActivityState(targetActivity, STATE_STOPPED);
+
+        // Kill first activity
+        AmUtils.runKill(targetActivity.getPackageName(), true /* wait */);
+
+        // Return back to first activity
+        pressBackButton();
+
+        // Verify activity is resumed
+        mAmWmState.waitForValidState(targetActivity);
+        mAmWmState.assertResumedActivity("Originally launched activity should be resumed",
+                targetActivity);
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
new file mode 100644
index 0000000..e2d4a5d
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -0,0 +1,100 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+import static android.support.test.runner.lifecycle.Stage.CREATED;
+import static android.support.test.runner.lifecycle.Stage.DESTROYED;
+import static android.support.test.runner.lifecycle.Stage.PAUSED;
+import static android.support.test.runner.lifecycle.Stage.PRE_ON_CREATE;
+import static android.support.test.runner.lifecycle.Stage.RESUMED;
+import static android.support.test.runner.lifecycle.Stage.STARTED;
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used as a shared log storage of activity lifecycle transitions.
+ */
+class LifecycleLog implements ActivityLifecycleCallback {
+
+    enum ActivityCallback {
+        PRE_ON_CREATE,
+        ON_CREATE,
+        ON_START,
+        ON_RESUME,
+        ON_PAUSE,
+        ON_STOP,
+        ON_RESTART,
+        ON_DESTROY,
+        ON_ACTIVITY_RESULT
+    }
+
+    private List<Pair<String, ActivityCallback>> mLog = new ArrayList<>();
+
+    /** Clear the entire transition log. */
+    void clear() {
+        mLog.clear();
+    }
+
+    /** Add transition of an activity to the log. */
+    @Override
+    public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+        final String activityName = activity.getClass().getCanonicalName();
+        log("Activity " + activityName + " moved to stage " + stage);
+        mLog.add(new Pair<>(activityName, stageToCallback(stage)));
+    }
+
+    /** Add activity callback to the log. */
+    public void onActivityCallback(Activity activity, ActivityCallback callback) {
+        final String activityName = activity.getClass().getCanonicalName();
+        log("Activity " + activityName + " got a callback " + callback);
+        mLog.add(new Pair<>(activityName, callback));
+    }
+
+    /** Get logs for all recorded transitions. */
+    List<Pair<String, ActivityCallback>> getLog() {
+        // Wrap in a new list to prevent concurrent modification
+        return new ArrayList<>(mLog);
+    }
+
+    /** Get transition logs for the specified activity. */
+    List<ActivityCallback> getActivityLog(Class<? extends Activity> activityClass) {
+        final String activityName = activityClass.getCanonicalName();
+        log("Looking up log for activity: " + activityName);
+        final List<ActivityCallback> activityLog = new ArrayList<>();
+        for (Pair<String, ActivityCallback> transition : mLog) {
+            if (transition.first.equals(activityName)) {
+                activityLog.add(transition.second);
+            }
+        }
+        return activityLog;
+    }
+
+    private static ActivityCallback stageToCallback(Stage stage) {
+        switch (stage) {
+            case PRE_ON_CREATE:
+                return ActivityCallback.PRE_ON_CREATE;
+            case CREATED:
+                return ActivityCallback.ON_CREATE;
+            case STARTED:
+                return ActivityCallback.ON_START;
+            case RESUMED:
+                return ActivityCallback.ON_RESUME;
+            case PAUSED:
+                return ActivityCallback.ON_PAUSE;
+            case STOPPED:
+                return ActivityCallback.ON_STOP;
+            case RESTARTED:
+                return ActivityCallback.ON_RESTART;
+            case DESTROYED:
+                return ActivityCallback.ON_DESTROY;
+            default:
+                throw new IllegalArgumentException("Unsupported stage: " + stage);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
new file mode 100644
index 0000000..ca29280
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
@@ -0,0 +1,78 @@
+package android.server.am.lifecycle;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Gets notified about activity lifecycle updates and provides blocking mechanism to wait until
+ * expected activity states are reached.
+ */
+public class LifecycleTracker implements ActivityLifecycleCallback {
+
+    private LifecycleLog mLifecycleLog;
+
+    LifecycleTracker(LifecycleLog lifecycleLog) {
+        mLifecycleLog = lifecycleLog;
+    }
+
+    void waitAndAssertActivityStates(Pair<Activity, ActivityCallback>[] activityCallbacks) {
+        final boolean waitResult = waitForConditionWithTimeout(
+                () -> pendingCallbacks(activityCallbacks).isEmpty(), 5 * 1000);
+
+        if (!waitResult) {
+            fail("Expected lifecycle states not achieved: " + pendingCallbacks(activityCallbacks));
+        }
+    }
+
+    @Override
+    synchronized public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+        notify();
+    }
+
+    /** Get a list of activity states that were not reached yet. */
+    private List<Pair<Activity, ActivityCallback>> pendingCallbacks(Pair<Activity,
+            ActivityCallback>[] activityCallbacks) {
+        final List<Pair<Activity, ActivityCallback>> notReachedActivityCallbacks = new ArrayList<>();
+
+        for (Pair<Activity, ActivityCallback> activityCallback : activityCallbacks) {
+            final Activity activity = activityCallback.first;
+            final List<ActivityCallback> transitionList =
+                    mLifecycleLog.getActivityLog(activity.getClass());
+            if (transitionList.isEmpty()
+                    || transitionList.get(transitionList.size() - 1) != activityCallback.second) {
+                // The activity either hasn't got any state transitions yet or the current state is
+                // not the one we expect.
+                notReachedActivityCallbacks.add(activityCallback);
+            }
+        }
+        return notReachedActivityCallbacks;
+    }
+
+    /** 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(timeoutMs);
+            } catch (InterruptedException e) {
+                // Weird, let's retry.
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
new file mode 100644
index 0000000..993af42
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
@@ -0,0 +1,114 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Activity;
+import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Util class that verifies correct activity state transition sequences. */
+class LifecycleVerifier {
+
+    static void assertLaunchSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch");
+
+        final List<ActivityCallback> expectedTransitions =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
+            Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog) {
+        final List<Pair<String, ActivityCallback>> observedTransitions = lifecycleLog.getLog();
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(launchingActivity, "launch");
+
+        final List<Pair<String, ActivityCallback>> expectedTransitions = Arrays.asList(
+                transition(existingActivity, ON_PAUSE),
+                transition(launchingActivity, PRE_ON_CREATE),
+                transition(launchingActivity, ON_CREATE),
+                transition(launchingActivity, ON_START),
+                transition(launchingActivity, ON_RESUME),
+                transition(existingActivity, ON_STOP));
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchAndStopSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch and stop");
+
+        final List<ActivityCallback> expectedTransitions =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchAndDestroySequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
+
+        final List<ActivityCallback> expectedTransitions = Arrays.asList(PRE_ON_CREATE, ON_CREATE,
+                ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertRelaunchSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, ActivityCallback startState) {
+        final List<ActivityCallback> expectedTransitions;
+        if (startState == ON_PAUSE) {
+            expectedTransitions = Arrays.asList(
+                    ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE);
+        } else if (startState == ON_STOP) {
+            expectedTransitions = Arrays.asList(
+                    ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP);
+        } else {
+            expectedTransitions = Arrays.asList(
+                    ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+        }
+        assertSequence(activityClass, lifecycleLog, expectedTransitions, "relaunch");
+    }
+
+    static void assertSequence(Class<? extends Activity> activityClass, LifecycleLog lifecycleLog,
+            List<ActivityCallback> expectedTransitions, String transition) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, transition);
+
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    private static Pair<String, ActivityCallback> transition(
+            Class<? extends Activity> activityClass, ActivityCallback state) {
+        return new Pair<>(activityClass.getCanonicalName(), state);
+    }
+
+    private static String errorDuringTransition(Class<? extends Activity> activityClass,
+            String transition) {
+        return "Failed verification during moving activity: " + activityClass.getCanonicalName()
+                + " through transition: " + transition;
+    }
+}
diff --git a/tests/framework/base/activitymanager/testsdk25/Android.mk b/tests/framework/base/activitymanager/testsdk25/Android.mk
new file mode 100644
index 0000000..e55d5f0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/Android.mk
@@ -0,0 +1,34 @@
+#
+# 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 optional
+
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceSdk25TestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/am/AspectRatioTestsBase.java
+
+LOCAL_SDK_VERSION := 25
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml b/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml
new file mode 100644
index 0000000..f216411
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.server.cts.am.testsdk25">
+
+    <uses-sdk android:targetSdkVersion="25" />
+
+    <application android:label="CtsActivityManagerDeviceSdk25TestCases">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="android.server.am.AspectRatioSdk25Tests$Sdk25MaxAspectRatioActivity"
+            android:label="Sdk25MaxAspectRatioActivity"
+            android:resizeableActivity="false" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.server.cts.am.testsdk25" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
new file mode 100644
index 0000000..9bbec9e
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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 ActivityManager SDK 25 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="CtsActivityManagerDeviceSdk25TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.cts.am.testsdk25" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
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
new file mode 100644
index 0000000..6ec1df0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+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;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base/activitymanager
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceSdk25TestCases android.server.am.AspectRatioSdk25Tests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioSdk25Tests extends AspectRatioTestsBase {
+
+    // Max supported aspect ratio for pre-O apps.
+    private static final float MAX_PRE_O_ASPECT_RATIO = 1.86f;
+
+    // Test target activity that has targetSdk="25" and resizeableActivity="false".
+    public static class Sdk25MaxAspectRatioActivity extends Activity {
+    }
+
+    @Rule
+    public ActivityTestRule<Sdk25MaxAspectRatioActivity> mSdk25MaxAspectRatioActivity =
+            new ActivityTestRule<>(Sdk25MaxAspectRatioActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
+
+    @Test
+    public void testMaxAspectRatioPreOActivity() throws Exception {
+        runAspectRatioTest(mSdk25MaxAspectRatioActivity, actual -> {
+            if (MAX_PRE_O_ASPECT_RATIO >= actual) return;
+            fail("actual=" + actual + " is greater than expected=" + MAX_PRE_O_ASPECT_RATIO);
+        });
+    }
+}
diff --git a/tests/framework/base/activitymanager/translucentapp/Android.mk b/tests/framework/base/activitymanager/translucentapp/Android.mk
new file mode 100644
index 0000000..933306c
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/Android.mk
@@ -0,0 +1,35 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-named-files-under,Components.java,../translucentappsdk26) \
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceTranslucentTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
new file mode 100755
index 0000000..edf7129
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.server.am.translucentapp">
+
+    <application android:label="CtsTranslucentApp">
+        <activity
+            android:name=".TranslucentLandscapeActivity"
+            android:exported="true"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/Components.java b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/Components.java
new file mode 100644
index 0000000..47cf52b
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/Components.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.server.am.translucentapp;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName TRANSLUCENT_LANDSCAPE_ACTIVITY =
+            component(Components.class, "TranslucentLandscapeActivity");
+}
diff --git a/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
new file mode 100644
index 0000000..3d8de4e
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.translucentapp;
+
+import android.app.Activity;
+
+public class TranslucentLandscapeActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/Android.mk b/tests/framework/base/activitymanager/translucentappsdk26/Android.mk
new file mode 100644
index 0000000..3cd6c5b
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentappsdk26/Android.mk
@@ -0,0 +1,35 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../translucentapp/src) \
+
+LOCAL_SDK_VERSION := 26
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceTranslucentTestApp26
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml
new file mode 100755
index 0000000..35c86cc
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.server.am.translucentapp26">
+
+    <application android:label="CtsTranslucentApp26">
+        <activity
+            android:name="android.server.am.translucentapp.TranslucentLandscapeActivity"
+            android:exported="true"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/src/android/server/am/translucentapp26/Components.java b/tests/framework/base/activitymanager/translucentappsdk26/src/android/server/am/translucentapp26/Components.java
new file mode 100644
index 0000000..c6d09c6
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentappsdk26/src/android/server/am/translucentapp26/Components.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am.translucentapp26;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY = component(
+            Components.class,
+            "android.server.am.translucentapp.TranslucentLandscapeActivity");
+}
diff --git a/tests/framework/base/activitymanager/util/Android.mk b/tests/framework/base/activitymanager/util/Android.mk
new file mode 100644
index 0000000..22dfa02
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/Android.mk
@@ -0,0 +1,37 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-named-files-under,Components.java, ../app) \
+    $(call all-named-files-under,ComponentsBase.java, ../app_base)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    platformprotosnano \
+    compatibility-device-util \
+    android-support-test
+
+LOCAL_MODULE := cts-amwm-util
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/util/run-test b/tests/framework/base/activitymanager/util/run-test
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/util/run-test
rename to tests/framework/base/activitymanager/util/run-test
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
new file mode 100644
index 0000000..def3457
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
@@ -0,0 +1,876 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.ActivityManager.StackId.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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logAlways;
+import static android.server.am.StateLogger.logE;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+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.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+import android.server.am.WindowManagerState.Display;
+import android.server.am.WindowManagerState.WindowStack;
+import android.server.am.WindowManagerState.WindowState;
+import android.server.am.WindowManagerState.WindowTask;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
+
+/**
+ * Combined state of the activity manager and window manager.
+ */
+public class ActivityAndWindowManagersState {
+
+    // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
+    // (Needed in host-side tests to convert dp to px.)
+    private static final int DISPLAY_DENSITY_DEFAULT = 160;
+    // TODO: Change to use framework constant.
+    public static final int DEFAULT_DISPLAY_ID = 0;
+
+    // Default minimal size of resizable task, used if none is set explicitly.
+    // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base.
+    private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220;
+
+    // Default minimal size of a resizable PiP task, used if none is set explicitly.
+    // Must be kept in sync with 'default_minimal_size_pip_resizable_task' dimen from
+    // frameworks/base.
+    private static final int DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP = 108;
+
+    private ActivityManagerState mAmState = new ActivityManagerState();
+    private WindowManagerState mWmState = new WindowManagerState();
+
+    /**
+     * Compute AM and WM state of device, check sanity and bounds.
+     * WM state will include only visible windows, stack and task bounds will be compared.
+     *
+     * @param componentNames array of activity names to wait for.
+     */
+    public void computeState(ComponentName... componentNames) {
+        waitForValidState(true /* compareTaskAndStackBounds */,
+                Arrays.stream(componentNames)
+                        .map(WaitForValidActivityState::new)
+                        .toArray(WaitForValidActivityState[]::new));
+    }
+
+    /**
+     * Compute AM and WM state of device, check sanity and bounds.
+     * WM state will include only visible windows, stack and task bounds will be compared.
+     *
+     * @param waitForActivitiesVisible array of activity names to wait for.
+     */
+    public void computeState(WaitForValidActivityState... waitForActivitiesVisible) {
+        waitForValidState(true /* compareTaskAndStackBounds */, waitForActivitiesVisible);
+    }
+
+    /**
+     * Compute AM and WM state of device, check sanity and bounds.
+     *
+     * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
+     *                                  'false' otherwise.
+     * @param waitForActivitiesVisible  array of activity states to wait for.
+     */
+    void computeState(boolean compareTaskAndStackBounds,
+            WaitForValidActivityState... waitForActivitiesVisible) {
+        waitForValidState(compareTaskAndStackBounds, waitForActivitiesVisible);
+    }
+
+    /**
+     * Wait for the activities to appear and for valid state in AM and WM.
+     *
+     * @param activityNames name list of activities to wait for.
+     */
+    public void waitForValidState(ComponentName... activityNames) {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                Arrays.stream(activityNames)
+                        .map(WaitForValidActivityState::new)
+                        .toArray(WaitForValidActivityState[]::new));
+
+    }
+
+    /** Wait for the activity to appear and for valid state in AM and WM. */
+    void waitForValidState(WaitForValidActivityState... waitForActivityVisible) {
+        waitForValidState(false /* compareTaskAndStackBounds */, waitForActivityVisible);
+    }
+
+    /**
+     * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
+     *
+     * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
+     *                                  for equality.
+     * @param waitForActivitiesVisible  array of activity states to wait for.
+     */
+    private void waitForValidState(boolean compareTaskAndStackBounds,
+            WaitForValidActivityState... waitForActivitiesVisible) {
+        for (int retry = 1; retry <= 5; retry++) {
+            // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
+            // requesting dump in some intermediate state.
+            mAmState.computeState();
+            mWmState.computeState();
+            if (shouldWaitForSanityCheck(compareTaskAndStackBounds)
+                    || shouldWaitForValidStacks(compareTaskAndStackBounds)
+                    || shouldWaitForActivities(waitForActivitiesVisible)
+                    || shouldWaitForWindows()) {
+                logAlways("***Waiting for valid stacks and activities states... retry=" + retry);
+                SystemClock.sleep(1000);
+            } else {
+                return;
+            }
+        }
+        logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible));
+    }
+
+    /**
+     * Ensures all exiting windows have been removed.
+     */
+    void waitForAllExitingWindows() {
+        for (int retry = 1; retry <= 5; retry++) {
+            mWmState.computeState();
+            if (!mWmState.containsExitingWindow()) {
+                return;
+            }
+            logAlways("***Waiting for all exiting windows have been removed... retry=" + retry);
+            SystemClock.sleep(1000);
+        }
+        fail("All exiting windows have been removed");
+    }
+
+    void waitForAllStoppedActivities() {
+        for (int retry = 1; retry <= 5; retry++) {
+            mAmState.computeState();
+            if (!mAmState.containsStartedActivities()) {
+                return;
+            }
+            logAlways("***Waiting for all started activities have been removed... retry=" + retry);
+            SystemClock.sleep(1500);
+        }
+        fail("All started activities have been removed");
+    }
+
+    /**
+     * Compute AM and WM state of device, wait for the activity records to be added, and
+     * wait for debugger window to show up.
+     *
+     * This should only be used when starting with -D (debugger) option, where we pop up the
+     * waiting-for-debugger window, but real activity window won't show up since we're waiting
+     * for debugger.
+     */
+    void waitForDebuggerWindowVisible(ComponentName activityName) {
+        for (int retry = 1; retry <= 5; retry++) {
+            mAmState.computeState();
+            mWmState.computeState();
+            if (shouldWaitForDebuggerWindow()
+                    || shouldWaitForActivityRecords(activityName)) {
+                logAlways("***Waiting for debugger window... retry=" + retry);
+                SystemClock.sleep(1000);
+            } else {
+                return;
+            }
+        }
+        logE("***Waiting for debugger window failed");
+    }
+
+    boolean waitForHomeActivityVisible() {
+        ComponentName homeActivity = mAmState.getHomeActivityName();
+        // Sometimes this function is called before we know what Home Activity is
+        if (homeActivity == null) {
+            log("Computing state to determine Home Activity");
+            computeState(true);
+            homeActivity = mAmState.getHomeActivityName();
+        }
+        assertNotNull("homeActivity should not be null", homeActivity);
+        waitForValidState(new WaitForValidActivityState(homeActivity));
+        return mAmState.isHomeActivityVisible();
+    }
+
+    /**
+     * @return true if recents activity is visible. Devices without recents will return false
+     */
+    boolean waitForRecentsActivityVisible() {
+        if (mAmState.isHomeRecentsComponent()) {
+            return waitForHomeActivityVisible();
+        }
+
+        waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
+                "***Waiting for recents activity to be visible...");
+        return mAmState.isRecentsActivityVisible();
+    }
+
+    void waitForKeyguardShowingAndNotOccluded() {
+        waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+                        && !state.getKeyguardControllerState().keyguardOccluded,
+                "***Waiting for Keyguard showing...");
+    }
+
+    void waitForKeyguardShowingAndOccluded() {
+        waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+                        && state.getKeyguardControllerState().keyguardOccluded,
+                "***Waiting for Keyguard showing and occluded...");
+    }
+
+    void waitForKeyguardGone() {
+        waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
+                "***Waiting for Keyguard gone...");
+    }
+
+    void waitForRotation(int rotation) {
+        waitForWithWmState(state -> state.getRotation() == rotation,
+                "***Waiting for Rotation: " + rotation);
+    }
+
+    void waitForDisplayUnfrozen() {
+        waitForWithWmState(state -> !state.isDisplayFrozen(),
+                "***Waiting for Display unfrozen");
+    }
+
+    public void waitForActivityState(ComponentName activityName, String activityState) {
+        waitForWithAmState(state -> state.hasActivityState(activityName, activityState),
+                "***Waiting for Activity State: " + activityState);
+    }
+
+    @Deprecated
+    void waitForFocusedStack(int stackId) {
+        waitForWithAmState(state -> state.getFocusedStackId() == stackId,
+                "***Waiting for focused stack...");
+    }
+
+    void waitForFocusedStack(int windowingMode, int activityType) {
+        waitForWithAmState(state ->
+                        (activityType == ACTIVITY_TYPE_UNDEFINED
+                                || state.getFocusedStackActivityType() == activityType)
+                        && (windowingMode == WINDOWING_MODE_UNDEFINED
+                                || state.getFocusedStackWindowingMode() == windowingMode),
+                "***Waiting for focused stack...");
+    }
+
+    void waitForAppTransitionIdle() {
+        waitForWithWmState(
+                state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
+                "***Waiting for app transition idle...");
+    }
+
+    void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
+        waitFor((amState, wmState) -> waitCondition.test(amState), message);
+    }
+
+    void waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
+        waitFor((amState, wmState) -> waitCondition.test(wmState), message);
+    }
+
+    void waitFor(
+            BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message) {
+        waitFor(message, () -> {
+            mAmState.computeState();
+            mWmState.computeState();
+            return waitCondition.test(mAmState, mWmState);
+        });
+    }
+
+    void waitFor(String message, BooleanSupplier waitCondition) {
+        for (int retry = 1; retry <= 5; retry++) {
+            if (waitCondition.getAsBoolean()) {
+                return;
+            }
+            logAlways(message + " retry=" + retry);
+            SystemClock.sleep(1000);
+        }
+        logE(message + " failed");
+    }
+
+    /**
+     * @return true if should wait for valid stacks state.
+     */
+    private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) {
+        if (!taskListsInAmAndWmAreEqual()) {
+            // We want to wait for equal task lists in AM and WM in case we caught them in the
+            // middle of some state change operations.
+            logAlways("***taskListsInAmAndWmAreEqual=false");
+            return true;
+        }
+        if (!stackBoundsInAMAndWMAreEqual()) {
+            // We want to wait a little for the stacks in AM and WM to have equal bounds as there
+            // might be a transition animation ongoing when we got the states from WM AM separately.
+            logAlways("***stackBoundsInAMAndWMAreEqual=false");
+            return true;
+        }
+        try {
+            // Temporary fix to avoid catching intermediate state with different task bounds in AM
+            // and WM.
+            assertValidBounds(compareTaskAndStackBounds);
+        } catch (AssertionError e) {
+            logAlways("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage());
+            return true;
+        }
+        final int stackCount = mAmState.getStackCount();
+        if (stackCount == 0) {
+            logAlways("***stackCount=" + stackCount);
+            return true;
+        }
+        final int resumedActivitiesCount = mAmState.getResumedActivitiesCount();
+        if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) {
+            logAlways("***resumedActivitiesCount=" + resumedActivitiesCount);
+            return true;
+        }
+        if (mAmState.getFocusedActivity() == null) {
+            logAlways("***focusedActivity=null");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return true if should wait for some activities to become visible.
+     */
+    private boolean shouldWaitForActivities(WaitForValidActivityState... waitForActivitiesVisible) {
+        if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) {
+            return false;
+        }
+        // If the caller is interested in us waiting for some particular activity windows to be
+        // visible before compute the state. Check for the visibility of those activity windows
+        // and for placing them in correct stacks (if requested).
+        boolean allActivityWindowsVisible = true;
+        boolean tasksInCorrectStacks = true;
+        List<WindowState> matchingWindowStates = new ArrayList<>();
+        for (final WaitForValidActivityState state : waitForActivitiesVisible) {
+            final ComponentName activityName = state.activityName;
+            final String windowName = state.windowName;
+            final int stackId = state.stackId;
+            final int windowingMode = state.windowingMode;
+            final int activityType = state.activityType;
+
+            mWmState.getMatchingVisibleWindowState(windowName, matchingWindowStates);
+            boolean activityWindowVisible = !matchingWindowStates.isEmpty();
+            if (!activityWindowVisible) {
+                logAlways("Activity window not visible: " + windowName);
+                allActivityWindowsVisible = false;
+            } else if (activityName != null
+                    && !mAmState.isActivityVisible(activityName)) {
+                logAlways("Activity not visible: " + getActivityName(activityName));
+                allActivityWindowsVisible = false;
+            } else {
+                // Check if window is already the correct state requested by test.
+                boolean windowInCorrectState = false;
+                for (WindowState ws : matchingWindowStates) {
+                    if (stackId != INVALID_STACK_ID && ws.getStackId() != stackId) {
+                        continue;
+                    }
+                    if (windowingMode != WINDOWING_MODE_UNDEFINED
+                            && ws.getWindowingMode() != windowingMode) {
+                        continue;
+                    }
+                    if (activityType != ACTIVITY_TYPE_UNDEFINED
+                            && ws.getActivityType() != activityType) {
+                        continue;
+                    }
+                    windowInCorrectState = true;
+                    break;
+                }
+
+                if (!windowInCorrectState) {
+                    logAlways("Window in incorrect stack: " + state);
+                    tasksInCorrectStacks = false;
+                }
+            }
+        }
+        return !allActivityWindowsVisible || !tasksInCorrectStacks;
+    }
+
+    /**
+     * @return true if should wait valid windows state.
+     */
+    private boolean shouldWaitForWindows() {
+        if (mWmState.getFrontWindow() == null) {
+            logAlways("***frontWindow=null");
+            return true;
+        }
+        if (mWmState.getFocusedWindow() == null) {
+            logAlways("***focusedWindow=null");
+            return true;
+        }
+        if (mWmState.getFocusedApp() == null) {
+            logAlways("***focusedApp=null");
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean shouldWaitForDebuggerWindow() {
+        List<WindowState> matchingWindowStates = new ArrayList<>();
+        mWmState.getMatchingVisibleWindowState("android.server.am", matchingWindowStates);
+        for (WindowState ws : matchingWindowStates) {
+            if (ws.isDebuggerWindow()) {
+                return false;
+            }
+        }
+        logAlways("Debugger window not available yet");
+        return true;
+    }
+
+    private boolean shouldWaitForActivityRecords(ComponentName... activityNames) {
+        // Check if the activity records we're looking for is already added.
+        for (final ComponentName activityName : activityNames) {
+            if (!mAmState.isActivityVisible(activityName)) {
+                logAlways("ActivityRecord " + getActivityName(activityName) + " not visible yet");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean shouldWaitForSanityCheck(boolean compareTaskAndStackBounds) {
+        try {
+            assertSanity();
+            assertValidBounds(compareTaskAndStackBounds);
+        } catch (Throwable t) {
+            logAlways("Waiting for sanity check: " + t.toString());
+            return true;
+        }
+        return false;
+    }
+
+    ActivityManagerState getAmState() {
+        return mAmState;
+    }
+
+    public WindowManagerState getWmState() {
+        return mWmState;
+    }
+
+    void assertSanity() {
+        assertThat("Must have stacks", mAmState.getStackCount(), greaterThan(0));
+        if (!mAmState.getKeyguardControllerState().keyguardShowing) {
+            assertEquals("There should be one and only one resumed activity in the system.",
+                    1, mAmState.getResumedActivitiesCount());
+        }
+        assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
+
+        for (ActivityStack aStack : mAmState.getStacks()) {
+            final int stackId = aStack.mStackId;
+            for (ActivityTask aTask : aStack.getTasks()) {
+                assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId);
+            }
+        }
+
+        assertNotNull("Must have front window.", mWmState.getFrontWindow());
+        assertNotNull("Must have focused window.", mWmState.getFocusedWindow());
+        assertNotNull("Must have app.", mWmState.getFocusedApp());
+    }
+
+    void assertContainsStack(String msg, int windowingMode, int activityType) {
+        assertTrue(msg, mAmState.containsStack(windowingMode, activityType));
+        assertTrue(msg, mWmState.containsStack(windowingMode, activityType));
+    }
+
+    void assertDoesNotContainStack(String msg, int windowingMode, int activityType) {
+        assertFalse(msg, mAmState.containsStack(windowingMode, activityType));
+        assertFalse(msg, mWmState.containsStack(windowingMode, activityType));
+    }
+
+    void assertFrontStack(String msg, int stackId) {
+        assertEquals(msg, stackId, mAmState.getFrontStackId(DEFAULT_DISPLAY_ID));
+        assertEquals(msg, stackId, mWmState.getFrontStackId(DEFAULT_DISPLAY_ID));
+    }
+
+    void assertFrontStack(String msg, int windowingMode, int activityType) {
+        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+            assertEquals(msg, windowingMode,
+                    mAmState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID));
+        }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+            assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+        }
+    }
+
+    void assertFrontStackActivityType(String msg, int activityType) {
+        assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+        assertEquals(msg, activityType, mWmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+    }
+
+    @Deprecated
+    void assertFocusedStack(String msg, int stackId) {
+        assertEquals(msg, stackId, mAmState.getFocusedStackId());
+    }
+
+    void assertFocusedStack(String msg, int windowingMode, int activityType) {
+        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+            assertEquals(msg, windowingMode, mAmState.getFocusedStackWindowingMode());
+        }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+            assertEquals(msg, activityType, mAmState.getFocusedStackActivityType());
+        }
+    }
+
+    void assertFocusedActivity(final String msg, final ComponentName activityName) {
+        final String activityComponentName = getActivityName(activityName);
+        assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
+        assertEquals(msg, activityComponentName, mWmState.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());
+    }
+
+    void assertNotResumedActivity(String msg, ComponentName activityName) {
+        assertNotEquals(msg, mAmState.getResumedActivity(), getActivityName(activityName));
+    }
+
+    void assertFocusedWindow(String msg, String windowName) {
+        assertEquals(msg, windowName, mWmState.getFocusedWindow());
+    }
+
+    void assertNotFocusedWindow(String msg, String windowName) {
+        assertNotEquals(msg, mWmState.getFocusedWindow(), windowName);
+    }
+
+    void assertFrontWindow(String msg, String windowName) {
+        assertEquals(msg, windowName, mWmState.getFrontWindow());
+    }
+
+    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);
+
+        assertEquals("Activity=" + getActivityName(activityName) + " must" + (visible ? "" : " NOT")
+                + " be visible.", visible, activityVisible);
+        assertEquals("Window=" + windowName + " must" + (visible ? "" : " NOT") + " be visible.",
+                visible, windowVisible);
+    }
+
+    void assertHomeActivityVisible(boolean visible) {
+        final ComponentName homeActivity = mAmState.getHomeActivityName();
+        assertNotNull(homeActivity);
+        assertVisibility(homeActivity, visible);
+    }
+
+    /**
+     * Asserts that the device default display minimim width is larger than the minimum task width.
+     */
+    void assertDeviceDefaultDisplaySize(String errorMessage) {
+        computeState(true);
+        final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY_ID);
+        final Display display = getWmState().getDisplay(DEFAULT_DISPLAY_ID);
+        final Rect displayRect = display.getDisplayRect();
+        if (Math.min(displayRect.width(), displayRect.height()) < minTaskSizePx) {
+            fail(errorMessage);
+        }
+    }
+
+    public void assertKeyguardShowingAndOccluded() {
+        assertTrue("Keyguard is showing",
+                getAmState().getKeyguardControllerState().keyguardShowing);
+        assertTrue("Keyguard is occluded",
+                getAmState().getKeyguardControllerState().keyguardOccluded);
+    }
+
+    public void assertKeyguardShowingAndNotOccluded() {
+        assertTrue("Keyguard is showing",
+                getAmState().getKeyguardControllerState().keyguardShowing);
+        assertFalse("Keyguard is not occluded",
+                getAmState().getKeyguardControllerState().keyguardOccluded);
+    }
+
+    public void assertKeyguardGone() {
+        assertFalse("Keyguard is not shown",
+                getAmState().getKeyguardControllerState().keyguardShowing);
+    }
+
+    boolean taskListsInAmAndWmAreEqual() {
+        for (ActivityStack aStack : mAmState.getStacks()) {
+            final int stackId = aStack.mStackId;
+            final WindowStack wStack = mWmState.getStack(stackId);
+            if (wStack == null) {
+                log("Waiting for stack setup in WM, stackId=" + stackId);
+                return false;
+            }
+
+            for (ActivityTask aTask : aStack.getTasks()) {
+                if (wStack.getTask(aTask.mTaskId) == null) {
+                    log("Task is in AM but not in WM, waiting for it to settle, taskId="
+                            + aTask.mTaskId);
+                    return false;
+                }
+            }
+
+            for (WindowTask wTask : wStack.mTasks) {
+                if (aStack.getTask(wTask.mTaskId) == null) {
+                    log("Task is in WM but not in AM, waiting for it to settle, taskId="
+                            + wTask.mTaskId);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /** Get the stack position on its display. */
+    int getStackIndexByActivityType(int activityType) {
+        int wmStackIndex = mWmState.getStackIndexByActivityType(activityType);
+        int amStackIndex = mAmState.getStackIndexByActivityType(activityType);
+        assertEquals("Window and activity manager must have the same stack position index",
+                amStackIndex, wmStackIndex);
+        return wmStackIndex;
+    }
+
+    boolean stackBoundsInAMAndWMAreEqual() {
+        for (ActivityStack aStack : mAmState.getStacks()) {
+            final int stackId = aStack.mStackId;
+            final WindowStack wStack = mWmState.getStack(stackId);
+            if (aStack.isFullscreen() != wStack.isFullscreen()) {
+                log("Waiting for correct fullscreen state, stackId=" + stackId);
+                return false;
+            }
+
+            final Rect aStackBounds = aStack.getBounds();
+            final Rect wStackBounds = wStack.getBounds();
+
+            if (aStack.isFullscreen()) {
+                if (aStackBounds != null) {
+                    log("Waiting for correct stack state in AM, stackId=" + stackId);
+                    return false;
+                }
+            } else if (!Objects.equals(aStackBounds, wStackBounds)) {
+                // If stack is not fullscreen - comparing bounds. Not doing it always because
+                // for fullscreen stack bounds in WM can be either null or equal to display size.
+                log("Waiting for stack bound equality in AM and WM, stackId=" + stackId);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Check task bounds when docked to top/left.
+     */
+    void assertDockedTaskBounds(int taskWidth, int taskHeight, ComponentName activityName) {
+        // Task size can be affected by default minimal size.
+        int defaultMinimalTaskSize = defaultMinimalTaskSize(
+                mAmState.getStandardStackByWindowingMode(
+                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).mDisplayId);
+        int targetWidth = Math.max(taskWidth, defaultMinimalTaskSize);
+        int targetHeight = Math.max(taskHeight, defaultMinimalTaskSize);
+
+        assertEquals(new Rect(0, 0, targetWidth, targetHeight),
+                mAmState.getTaskByActivity(activityName).getBounds());
+    }
+
+    void assertValidBounds(boolean compareTaskAndStackBounds) {
+        // Cycle through the stacks and tasks to figure out if the home stack is resizable
+        final ActivityTask homeTask = mAmState.getHomeTask();
+        final boolean homeStackIsResizable = homeTask != null
+                && homeTask.getResizeMode() == RESIZE_MODE_RESIZEABLE;
+
+        for (ActivityStack aStack : mAmState.getStacks()) {
+            final int stackId = aStack.mStackId;
+            final WindowStack wStack = mWmState.getStack(stackId);
+            assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack);
+
+            assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId,
+                    aStack.isFullscreen(), wStack.isFullscreen());
+
+            final Rect aStackBounds = aStack.getBounds();
+            final Rect wStackBounds = wStack.getBounds();
+
+            if (aStack.isFullscreen()) {
+                assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds);
+            } else {
+                assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId,
+                        aStackBounds, wStackBounds);
+            }
+
+            for (ActivityTask aTask : aStack.getTasks()) {
+                final int taskId = aTask.mTaskId;
+                final WindowTask wTask = wStack.getTask(taskId);
+                assertNotNull(
+                        "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask);
+
+                final boolean aTaskIsFullscreen = aTask.isFullscreen();
+                final boolean wTaskIsFullscreen = wTask.isFullscreen();
+                assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId
+                        + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen);
+
+                final Rect aTaskBounds = aTask.getBounds();
+                final Rect wTaskBounds = wTask.getBounds();
+
+                if (aTaskIsFullscreen) {
+                    assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
+                            aTaskBounds);
+                } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized()
+                        && !isScreenPortrait(aStack.mDisplayId)) {
+                    // When minimized using non-resizable launcher in landscape mode, it will move
+                    // the task offscreen in the negative x direction unlike portrait that crops.
+                    // The x value in the task bounds will not match the stack bounds since the
+                    // only the task was moved.
+                    assertEquals("Task bounds in AM and WM must match width taskId=" + taskId
+                                    + ", stackId" + stackId, aTaskBounds.width(),
+                            wTaskBounds.width());
+                    assertEquals("Task bounds in AM and WM must match height taskId=" + taskId
+                                    + ", stackId" + stackId, aTaskBounds.height(),
+                            wTaskBounds.height());
+                    assertEquals("Task bounds must match stack bounds y taskId=" + taskId
+                                    + ", stackId" + stackId, aTaskBounds.top,
+                            wTaskBounds.top);
+                    assertEquals("Task and stack bounds must match width taskId=" + taskId
+                                    + ", stackId" + stackId, aStackBounds.width(),
+                            wTaskBounds.width());
+                    assertEquals("Task and stack bounds must match height taskId=" + taskId
+                                    + ", stackId" + stackId, aStackBounds.height(),
+                            wTaskBounds.height());
+                    assertEquals("Task and stack bounds must match y taskId=" + taskId
+                                    + ", stackId" + stackId, aStackBounds.top,
+                            wTaskBounds.top);
+                } else {
+                    assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
+                            + ", stackId=" + stackId, aTaskBounds, wTaskBounds);
+
+                    if (compareTaskAndStackBounds
+                            && aStack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                        int aTaskMinWidth = aTask.getMinWidth();
+                        int aTaskMinHeight = aTask.getMinHeight();
+
+                        if (aTaskMinWidth == -1 || aTaskMinHeight == -1) {
+                            // Minimal dimension(s) not set for task - it should be using defaults.
+                            int defaultMinimalSize =
+                                    aStack.getWindowingMode() == WINDOWING_MODE_PINNED
+                                    ? defaultMinimalPinnedTaskSize(aStack.mDisplayId)
+                                    : defaultMinimalTaskSize(aStack.mDisplayId);
+
+                            if (aTaskMinWidth == -1) {
+                                aTaskMinWidth = defaultMinimalSize;
+                            }
+                            if (aTaskMinHeight == -1) {
+                                aTaskMinHeight = defaultMinimalSize;
+                            }
+                        }
+
+                        if (aStackBounds.width() >= aTaskMinWidth
+                                && aStackBounds.height() >= aTaskMinHeight
+                                || aStack.getWindowingMode() == WINDOWING_MODE_PINNED) {
+                            // Bounds are not smaller then minimal possible, so stack and task
+                            // bounds must be equal.
+                            assertEquals("Task bounds must be equal to stack bounds taskId="
+                                    + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
+                        } else if (aStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                                && homeStackIsResizable && mWmState.isDockedStackMinimized()) {
+                            // Portrait if the display height is larger than the width
+                            if (isScreenPortrait(aStack.mDisplayId)) {
+                                assertEquals("Task width must be equal to stack width taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        aStackBounds.width(), wTaskBounds.width());
+                                assertThat("Task height must be greater than stack height "
+                                                + "taskId=" + taskId + ", stackId=" + stackId,
+                                        aStackBounds.height(), lessThan(wTaskBounds.height()));
+                                assertEquals("Task and stack x position must be equal taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        wTaskBounds.left, wStackBounds.left);
+                            } else {
+                                assertThat("Task width must be greater than stack width taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        aStackBounds.width(), lessThan(wTaskBounds.width()));
+                                assertEquals("Task height must be equal to stack height taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        aStackBounds.height(), wTaskBounds.height());
+                                assertEquals("Task and stack y position must be equal taskId="
+                                                + taskId + ", stackId=" + stackId, wTaskBounds.top,
+                                        wStackBounds.top);
+                            }
+                        } else {
+                            // Minimal dimensions affect task size, so bounds of task and stack must
+                            // be different - will compare dimensions instead.
+                            int targetWidth = (int) Math.max(aTaskMinWidth,
+                                    aStackBounds.width());
+                            assertEquals("Task width must be set according to minimal width"
+                                            + " taskId=" + taskId + ", stackId=" + stackId,
+                                    targetWidth, (int) wTaskBounds.width());
+                            int targetHeight = (int) Math.max(aTaskMinHeight,
+                                    aStackBounds.height());
+                            assertEquals("Task height must be set according to minimal height"
+                                            + " taskId=" + taskId + ", stackId=" + stackId,
+                                    targetHeight, (int) wTaskBounds.height());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void assertActivityDisplayed(final ComponentName activityName) throws Exception {
+        assertWindowDisplayed(getWindowName(activityName));
+    }
+
+    public void assertWindowDisplayed(final String windowName) throws Exception {
+        waitForValidState(WaitForValidActivityState.forWindow(windowName));
+        assertTrue(windowName + "is visible", getWmState().isWindowVisible(windowName));
+    }
+
+    boolean isScreenPortrait() {
+        final int displayId = mAmState.getStandardStackByWindowingMode(
+            WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).mDisplayId;
+        return isScreenPortrait(displayId);
+    }
+
+    boolean isScreenPortrait(int displayId) {
+        final Rect displayRect = mWmState.getDisplay(displayId).getDisplayRect();
+        return displayRect.height() > displayRect.width();
+    }
+
+    static int dpToPx(float dp, int densityDpi) {
+        return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
+    }
+
+    private int defaultMinimalTaskSize(int displayId) {
+        return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
+    }
+
+    private int defaultMinimalPinnedTaskSize(int displayId) {
+        return dpToPx(DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
+    }
+}
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
new file mode 100644
index 0000000..9542dbb
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityLauncher.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.server.am.Components.TEST_ACTIVITY;
+
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+/** Utility class which contains common code for launching activities. */
+public class ActivityLauncher {
+    private static final String TAG = ActivityLauncher.class.getSimpleName();
+
+    /** Key for boolean extra, indicates whether it should launch an activity. */
+    public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
+    /**
+     * Key for boolean extra, indicates whether it the activity should be launched to side in
+     * split-screen.
+     */
+    public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side";
+    /**
+     * Key for boolean extra, indicates if launch intent should include random data to be different
+     * from other launch intents.
+     */
+    public static final String KEY_RANDOM_DATA = "random_data";
+    /**
+     * Key for boolean extra, indicates if launch intent should have
+     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
+     */
+    public static final String KEY_NEW_TASK = "new_task";
+    /**
+     * Key for boolean extra, indicates if launch intent should have
+     * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
+     */
+    public static final String KEY_MULTIPLE_TASK = "multiple_task";
+    /**
+     * Key for boolean extra, indicates if launch intent should have
+     * {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}.
+     */
+    public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
+    /**
+     * Key for string extra with string representation of target component.
+     */
+    public static final String KEY_TARGET_COMPONENT = "target_component";
+    /**
+     * Key for int extra with target display id where the activity should be launched. Adding this
+     * automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and
+     * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent.
+     */
+    public static final String KEY_DISPLAY_ID = "display_id";
+    /**
+     * Key for boolean extra, indicates if launch should be done from application context of the one
+     * passed in {@link #launchActivityFromExtras(Context, Bundle)}.
+     */
+    public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
+    /**
+     * Key for boolean extra, indicates if instrumentation context will be used for launch. This
+     * means that {@link PendingIntent} should be used instead of a regular one, because application
+     * switch will not be allowed otherwise.
+     */
+    public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
+    /**
+     * Key for boolean extra, indicates if any exceptions thrown during launch other then
+     * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
+     * it's always written to logs.
+     */
+    public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
+
+
+    /** Perform an activity launch configured by provided extras. */
+    public static void launchActivityFromExtras(final Context context, Bundle extras) {
+        if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
+            return;
+        }
+
+        Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
+
+        final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
+        final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent)
+                ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent));
+
+        if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) {
+            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
+            if (extras.getBoolean(KEY_RANDOM_DATA)) {
+                final Uri data = new Uri.Builder()
+                        .path(String.valueOf(System.currentTimeMillis()))
+                        .build();
+                newIntent.setData(data);
+            }
+        }
+        if (extras.getBoolean(KEY_MULTIPLE_TASK)) {
+            newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+        if (extras.getBoolean(KEY_NEW_TASK)) {
+            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        }
+
+        if (extras.getBoolean(KEY_REORDER_TO_FRONT)) {
+            newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
+        }
+
+        ActivityOptions options = null;
+        final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
+        if (displayId != -1) {
+            options = ActivityOptions.makeBasic();
+            options.setLaunchDisplayId(displayId);
+            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+        final Bundle optionsBundle = options != null ? options.toBundle() : null;
+
+        final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ?
+                context.getApplicationContext() : context;
+
+        try {
+            if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
+                // Using PendingIntent for Instrumentation launches, because otherwise we won't
+                // be allowed to switch the current activity with ours with different uid.
+                // android.permission.STOP_APP_SWITCHES is needed to do this directly.
+                final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
+                        newIntent, 0, optionsBundle);
+                pendingIntent.send();
+            } else {
+                launchContext.startActivity(newIntent, optionsBundle);
+            }
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException launching activity");
+        } catch (PendingIntent.CanceledException e) {
+            if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
+                Log.e(TAG, "Exception launching activity with pending intent");
+            } else {
+                throw new RuntimeException(e);
+            }
+            Log.e(TAG, "SecurityException launching activity");
+        } catch (Exception e) {
+            if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
+                Log.e(TAG, "Exception launching activity");
+            } else {
+                throw e;
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000..a0abf28
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.ActivityManager.StackId.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;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.ProtoExtractors.extract;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.am.proto.nano.ActivityDisplayProto;
+import com.android.server.am.proto.nano.ActivityManagerServiceDumpActivitiesProto;
+import com.android.server.am.proto.nano.ActivityRecordProto;
+import com.android.server.am.proto.nano.ActivityStackProto;
+import com.android.server.am.proto.nano.ActivityStackSupervisorProto;
+import com.android.server.am.proto.nano.KeyguardControllerProto;
+import com.android.server.am.proto.nano.TaskRecordProto;
+import com.android.server.wm.proto.nano.ConfigurationContainerProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityManagerState {
+
+    public static final int DUMP_MODE_ACTIVITIES = 0;
+
+    public static final String STATE_RESUMED = "RESUMED";
+    public static final String STATE_PAUSED = "PAUSED";
+    public static final String STATE_STOPPED = "STOPPED";
+    public static final String STATE_DESTROYED = "DESTROYED";
+
+    private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity --proto activities";
+
+    // Displays in z-order with the top most at the front of the list, starting with primary.
+    private final List<ActivityDisplay> mDisplays = new ArrayList<>();
+    // 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 boolean mIsHomeRecentsComponent = false;
+    private String mResumedActivityRecord = null;
+    private final List<String> mResumedActivities = new ArrayList<>();
+
+    void computeState() {
+        computeState(DUMP_MODE_ACTIVITIES);
+    }
+
+    void computeState(int dumpMode) {
+        // It is possible the system is in the middle of transition to the right state when we get
+        // the dump. We try a few times to get the information we need before giving up.
+        int retriesLeft = 3;
+        boolean retry = false;
+        byte[] dump = null;
+
+        log("==============================");
+        log("     ActivityManagerState     ");
+        log("==============================");
+
+        do {
+            if (retry) {
+                log("***Incomplete AM state. Retrying...");
+                // Wait half a second between retries for activity manager to finish transitioning.
+                SystemClock.sleep(500);
+            }
+
+            String dumpsysCmd = "";
+            switch (dumpMode) {
+                case DUMP_MODE_ACTIVITIES:
+                    dumpsysCmd = DUMPSYS_ACTIVITY_ACTIVITIES;
+                    break;
+            }
+
+            dump = executeShellCommand(dumpsysCmd);
+            try {
+                parseSysDumpProto(dump);
+            } catch (InvalidProtocolBufferNanoException ex) {
+                throw new RuntimeException("Failed to parse dumpsys:\n"
+                        + new String(dump, StandardCharsets.UTF_8), ex);
+            }
+
+            retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
+                    || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
+        } while (retry && retriesLeft-- > 0);
+
+        if (mStacks.isEmpty()) {
+            logE("No stacks found...");
+        }
+        if (mFocusedStackId == -1) {
+            logE("No focused stack found...");
+        }
+        if (mResumedActivityRecord == null) {
+            logE("No focused activity found...");
+        }
+        if (mResumedActivities.isEmpty()) {
+            logE("No resumed activities found...");
+        }
+    }
+
+    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);
+        }
+    }
+
+    private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
+        reset();
+
+        ActivityStackSupervisorProto state = ActivityManagerServiceDumpActivitiesProto.parseFrom(sysDump)
+                .activityStackSupervisor;
+        for (int i = 0; i < state.displays.length; i++) {
+            ActivityDisplayProto activityDisplay = state.displays[i];
+            mDisplays.add(new ActivityDisplay(activityDisplay, this));
+        }
+        mKeyguardControllerState = new KeyguardControllerState(state.keyguardController);
+        mFocusedStackId = state.focusedStackId;
+        if (state.resumedActivity != null) {
+            mResumedActivityRecord = state.resumedActivity.title;
+        }
+        mIsHomeRecentsComponent = state.isHomeRecentsComponent;
+    }
+
+    private void reset() {
+        mDisplays.clear();
+        mStacks.clear();
+        mFocusedStackId = -1;
+        mResumedActivityRecord = null;
+        mResumedActivities.clear();
+        mKeyguardControllerState = null;
+    }
+
+    /**
+     * @return Whether the home activity is the recents component.
+     */
+    boolean isHomeRecentsComponent() {
+        return mIsHomeRecentsComponent;
+    }
+
+    ActivityDisplay getDisplay(int displayId) {
+        for (ActivityDisplay display : mDisplays) {
+            if (display.mId == displayId) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    int getFrontStackId(int displayId) {
+        return getDisplay(displayId).mStacks.get(0).mStackId;
+    }
+
+    int getFrontStackActivityType(int displayId) {
+        return getDisplay(displayId).mStacks.get(0).getActivityType();
+    }
+
+    int getFrontStackWindowingMode(int displayId) {
+        return getDisplay(displayId).mStacks.get(0).getWindowingMode();
+    }
+
+    int getFocusedStackId() {
+        return mFocusedStackId;
+    }
+
+    int getFocusedStackActivityType() {
+        final ActivityStack stack = getStackById(mFocusedStackId);
+        return stack != null ? stack.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    int getFocusedStackWindowingMode() {
+        final ActivityStack stack = getStackById(mFocusedStackId);
+        return stack != null ? stack.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+    }
+
+    String getFocusedActivity() {
+        return mResumedActivityRecord;
+    }
+
+    String getResumedActivity() {
+        return mResumedActivities.get(0);
+    }
+
+    int getResumedActivitiesCount() {
+        return mResumedActivities.size();
+    }
+
+    public KeyguardControllerState getKeyguardControllerState() {
+        return mKeyguardControllerState;
+    }
+
+    boolean containsStack(int stackId) {
+        return getStackById(stackId) != null;
+    }
+
+    boolean containsStack(int windowingMode, int activityType) {
+        for (ActivityStack stack : mStacks) {
+            if (activityType != ACTIVITY_TYPE_UNDEFINED
+                    && activityType != stack.getActivityType()) {
+                continue;
+            }
+            if (windowingMode != WINDOWING_MODE_UNDEFINED
+                    && windowingMode != stack.getWindowingMode()) {
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    ActivityStack getStackById(int stackId) {
+        for (ActivityStack stack : mStacks) {
+            if (stackId == stack.mStackId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    ActivityStack getStackByActivityType(int activityType) {
+        for (ActivityStack stack : mStacks) {
+            if (activityType == stack.getActivityType()) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    ActivityStack getStandardStackByWindowingMode(int windowingMode) {
+        for (ActivityStack stack : mStacks) {
+            if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+                continue;
+            }
+            if (stack.getWindowingMode() == windowingMode) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    int getStandardTaskCountByWindowingMode(int windowingMode) {
+        int count = 0;
+        for (ActivityStack stack : mStacks) {
+            if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+                continue;
+            }
+            if (stack.getWindowingMode() == windowingMode) {
+                count += stack.mTasks.size();
+            }
+        }
+        return count;
+    }
+
+    /** Get the stack position on its display. */
+    int getStackIndexByActivityType(int activityType) {
+        for (ActivityDisplay display : mDisplays) {
+            for (int i = 0; i < display.mStacks.size(); i++) {
+                if (activityType == display.mStacks.get(i).getActivityType()) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /** Get the stack position on its display. */
+    int getStackIndexByActivity(ComponentName activityName) {
+        final String fullName = getActivityName(activityName);
+
+        for (ActivityDisplay display : mDisplays) {
+            for (int i = display.mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = display.mStacks.get(i);
+                for (ActivityTask task : stack.mTasks) {
+                    for (Activity activity : task.mActivities) {
+                        if (activity.name.equals(fullName)) {
+                            return i;
+                        }
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+
+    List<ActivityDisplay> getDisplays() {
+        return new ArrayList<>(mDisplays);
+    }
+
+    List<ActivityStack> getStacks() {
+        return new ArrayList<>(mStacks);
+    }
+
+    int getStackCount() {
+        return mStacks.size();
+    }
+
+    boolean containsActivity(ComponentName activityName) {
+        final String fullName = getActivityName(activityName);
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (activity.name.equals(fullName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
+        final String fullName = getActivityName(activityName);
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (activity.name.equals(fullName)
+                            && activity.getWindowingMode() == windowingMode) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean isActivityVisible(ComponentName activityName) {
+        final String fullName = getActivityName(activityName);
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (activity.name.equals(fullName)) {
+                        return activity.visible;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean containsStartedActivities() {
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (!activity.state.equals(STATE_STOPPED)
+                            && !activity.state.equals(STATE_DESTROYED)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean hasActivityState(ComponentName activityName, String activityState) {
+        final String fullName = getActivityName(activityName);
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (activity.name.equals(fullName)) {
+                        return activity.state.equals(activityState);
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    int getActivityProcId(ComponentName activityName) {
+        final String fullName = getActivityName(activityName);
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                for (Activity activity : task.mActivities) {
+                    if (activity.name.equals(fullName)) {
+                        return activity.procId;
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+
+    boolean isHomeActivityVisible() {
+        final Activity homeActivity = getHomeActivity();
+        return homeActivity != null && homeActivity.visible;
+    }
+
+    boolean isRecentsActivityVisible() {
+        final Activity recentsActivity = getRecentsActivity();
+        return recentsActivity != null && recentsActivity.visible;
+    }
+
+    ComponentName getHomeActivityName() {
+        Activity activity = getHomeActivity();
+        if (activity == null) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(activity.name);
+    }
+
+    ActivityTask getHomeTask() {
+        final ActivityStack homeStack = getStackByActivityType(ACTIVITY_TYPE_HOME);
+        if (homeStack != null && !homeStack.mTasks.isEmpty()) {
+            return homeStack.mTasks.get(0);
+        }
+        return null;
+    }
+
+    private ActivityTask getRecentsTask() {
+        final ActivityStack recentsStack = getStackByActivityType(ACTIVITY_TYPE_RECENTS);
+        if (recentsStack != null && !recentsStack.mTasks.isEmpty()) {
+            return recentsStack.mTasks.get(0);
+        }
+        return null;
+    }
+
+    private Activity getHomeActivity() {
+        final ActivityTask homeTask = getHomeTask();
+        return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
+    }
+
+    private Activity getRecentsActivity() {
+        final ActivityTask recentsTask = getRecentsTask();
+        return recentsTask != null ? recentsTask.mActivities.get(recentsTask.mActivities.size() - 1)
+                : null;
+    }
+
+    int getStackIdByActivity(ComponentName activityName) {
+        final ActivityTask task = getTaskByActivity(activityName);
+        return  (task == null) ? INVALID_STACK_ID : task.mStackId;
+    }
+
+    ActivityTask getTaskByActivity(ComponentName activityName) {
+        return getTaskByActivityInternal(getActivityName(activityName), WINDOWING_MODE_UNDEFINED);
+    }
+
+    ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
+        return getTaskByActivityInternal(getActivityName(activityName), windowingMode);
+    }
+
+    private ActivityTask getTaskByActivityInternal(String fullName, int windowingMode) {
+        for (ActivityStack stack : mStacks) {
+            if (windowingMode == WINDOWING_MODE_UNDEFINED
+                    || windowingMode == stack.getWindowingMode()) {
+                for (ActivityTask task : stack.mTasks) {
+                    for (Activity activity : task.mActivities) {
+                        if (activity.name.equals(fullName)) {
+                            return task;
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    static class ActivityDisplay extends ActivityContainer {
+
+        int mId;
+        ArrayList<ActivityStack> mStacks = new ArrayList<>();
+
+        ActivityDisplay(ActivityDisplayProto proto, ActivityManagerState amState) {
+            super(proto.configurationContainer);
+            mId = proto.id;
+            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);
+                }
+            }
+        }
+    }
+
+    static class ActivityStack extends ActivityContainer {
+
+        int mDisplayId;
+        int mStackId;
+        String mResumedActivity;
+        ArrayList<ActivityTask> mTasks = new ArrayList<>();
+
+        ActivityStack(ActivityStackProto proto) {
+            super(proto.configurationContainer);
+            mStackId = proto.id;
+            mDisplayId = proto.displayId;
+            mBounds = extract(proto.bounds);
+            mFullscreen = proto.fullscreen;
+            for (int i = 0; i < proto.tasks.length; i++) {
+                mTasks.add(new ActivityTask(proto.tasks[i]));
+            }
+            if (proto.resumedActivity != null) {
+                mResumedActivity = proto.resumedActivity.title;
+            }
+        }
+
+        /**
+         * @return the bottom task in the stack.
+         */
+        ActivityTask getBottomTask() {
+            if (!mTasks.isEmpty()) {
+                // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
+                //       so the indices are inverted
+                return mTasks.get(mTasks.size() - 1);
+            }
+            return null;
+        }
+
+        /**
+         * @return the top task in the stack.
+         */
+        ActivityTask getTopTask() {
+            if (!mTasks.isEmpty()) {
+                // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
+                //       so the indices are inverted
+                return mTasks.get(0);
+            }
+            return null;
+        }
+
+        List<ActivityTask> getTasks() {
+            return new ArrayList<>(mTasks);
+        }
+
+        ActivityTask getTask(int taskId) {
+            for (ActivityTask task : mTasks) {
+                if (taskId == task.mTaskId) {
+                    return task;
+                }
+            }
+            return null;
+        }
+    }
+
+    static class ActivityTask extends ActivityContainer {
+
+        int mTaskId;
+        int mStackId;
+        Rect mLastNonFullscreenBounds;
+        String mRealActivity;
+        String mOrigActivity;
+        ArrayList<Activity> mActivities = new ArrayList<>();
+        int mTaskType = -1;
+        private int mResizeMode;
+
+        ActivityTask(TaskRecordProto proto) {
+            super(proto.configurationContainer);
+            mTaskId = proto.id;
+            mStackId = proto.stackId;
+            mLastNonFullscreenBounds = extract(proto.lastNonFullscreenBounds);
+            mRealActivity = proto.realActivity;
+            mOrigActivity = proto.origActivity;
+            mTaskType = proto.activityType;
+            mResizeMode = proto.resizeMode;
+            mFullscreen = proto.fullscreen;
+            mBounds = extract(proto.bounds);
+            mMinWidth = proto.minWidth;
+            mMinHeight = proto.minHeight;
+            for (int i = 0;  i < proto.activities.length; i++) {
+                mActivities.add(new Activity(proto.activities[i]));
+            }
+        }
+
+        public int getResizeMode() {
+            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;
+        }
+    }
+
+    static class Activity extends ActivityContainer {
+
+        String name;
+        String state;
+        boolean visible;
+        boolean frontOfTask;
+        int procId = -1;
+
+        Activity(ActivityRecordProto proto) {
+            super(proto.configurationContainer);
+            name = proto.identifier.title;
+            state = proto.state;
+            visible = proto.visible;
+            frontOfTask = proto.frontOfTask;
+            if (proto.procId != 0) {
+                procId = proto.procId;
+            }
+        }
+    }
+
+    static abstract class ActivityContainer extends WindowManagerState.ConfigurationContainer {
+        protected boolean mFullscreen;
+        protected Rect mBounds;
+        protected int mMinWidth = -1;
+        protected int mMinHeight = -1;
+
+        ActivityContainer(ConfigurationContainerProto proto) {
+            super(proto);
+        }
+
+        Rect getBounds() {
+            return mBounds;
+        }
+
+        boolean isFullscreen() {
+            return mFullscreen;
+        }
+
+        int getMinWidth() {
+            return mMinWidth;
+        }
+
+        int getMinHeight() {
+            return mMinHeight;
+        }
+    }
+
+    static class KeyguardControllerState {
+
+        boolean keyguardShowing = false;
+        boolean keyguardOccluded = false;
+
+        KeyguardControllerState(KeyguardControllerProto proto) {
+            if (proto != null) {
+                keyguardShowing = proto.keyguardShowing;
+                keyguardOccluded = proto.keyguardOccluded;
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000..0803efc
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -0,0 +1,1443 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+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.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+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;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
+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.server.am.ActivityLauncher.KEY_DISPLAY_ID;
+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_TASK;
+import static android.server.am.ActivityLauncher.KEY_NEW_TASK;
+import static android.server.am.ActivityLauncher.KEY_RANDOM_DATA;
+import static android.server.am.ActivityLauncher.KEY_REORDER_TO_FRONT;
+import static android.server.am.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
+import static android.server.am.ActivityLauncher.KEY_TARGET_COMPONENT;
+import static android.server.am.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
+import static android.server.am.ActivityLauncher.KEY_USE_INSTRUMENTATION;
+import static android.server.am.ActivityLauncher.launchActivityFromExtras;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.ComponentNameUtils.getLogTag;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logAlways;
+import static android.server.am.StateLogger.logE;
+import static android.server.am.UiDeviceUtils.pressAppSwitchButton;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.UiDeviceUtils.pressEnterButton;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.UiDeviceUtils.pressSleepButton;
+import static android.server.am.UiDeviceUtils.pressUnlockButton;
+import static android.server.am.UiDeviceUtils.pressWakeupButton;
+import static android.server.am.UiDeviceUtils.waitForDeviceIdle;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.lang.Integer.toHexString;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.view.Display;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+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;
+
+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";
+
+    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";
+
+    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";
+
+    /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+    static final String FINISH_ACTIVITY_BROADCAST
+            = "am broadcast -a trigger_broadcast --ez finish true";
+
+    /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+    static final String MOVE_TASK_TO_BACK_BROADCAST
+            = "am broadcast -a trigger_broadcast --ez moveToBack true";
+
+    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";
+
+    private static final int INVALID_DISPLAY_ID = Display.INVALID_DISPLAY;
+
+    private static final int UI_MODE_TYPE_MASK = 0x0f;
+    private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
+
+    private static Boolean sHasHomeScreen = null;
+
+    protected static final int INVALID_DEVICE_ROTATION = -1;
+
+    protected Context mContext;
+    protected ActivityManager mAm;
+
+    /**
+     * @return the am command to start the given activity with the following extra key/value pairs.
+     *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
+     */
+    // TODO: Make this more generic, for instance accepting flags or extras of other types.
+    protected static String getAmStartCmd(final ComponentName activityName,
+            final String... keyValuePairs) {
+        return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
+    }
+
+    private static String getAmStartCmdInternal(final String activityName,
+            final String... keyValuePairs) {
+        return appendKeyValuePairs(
+                new StringBuilder("am start -n ").append(activityName),
+                keyValuePairs);
+    }
+
+    private static String appendKeyValuePairs(
+            final StringBuilder cmd, final String... keyValuePairs) {
+        if (keyValuePairs.length % 2 != 0) {
+            throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
+        }
+        for (int i = 0; i < keyValuePairs.length; i += 2) {
+            final String key = keyValuePairs[i];
+            final String value = keyValuePairs[i + 1];
+            cmd.append(" --es ")
+                    .append(key)
+                    .append(" ")
+                    .append(value);
+        }
+        return cmd.toString();
+    }
+
+    protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
+            final String... keyValuePair) {
+        return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair);
+    }
+
+    private static String getAmStartCmdInternal(final String activityName, final int displayId,
+            final String... keyValuePairs) {
+        return appendKeyValuePairs(
+                new StringBuilder("am start -n ")
+                        .append(activityName)
+                        .append(" -f 0x")
+                        .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
+                        .append(" --display ")
+                        .append(displayId),
+                keyValuePairs);
+    }
+
+    protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
+        return "am start -n " + getActivityName(activityName) + " -f 0x18000000";
+    }
+
+    protected static String getAmStartCmdOverHome(final ComponentName activityName) {
+        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 static String getOrientationBroadcast(int orientation) {
+        return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
+    }
+
+    protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mAm = mContext.getSystemService(ActivityManager.class);
+        executeShellCommand("pm grant " + mContext.getPackageName()
+                + " android.permission.MANAGE_ACTIVITY_STACKS");
+        executeShellCommand("pm grant " + mContext.getPackageName()
+                + " android.permission.ACTIVITY_EMBEDDING");
+
+        pressWakeupButton();
+        pressUnlockButton();
+        pressHomeButton();
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // 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();
+    }
+
+    protected void removeStacksInWindowingModes(int... windowingModes) {
+        mAm.removeStacksInWindowingModes(windowingModes);
+        waitForIdle();
+    }
+
+    public static String executeShellCommand(String command) {
+        log("Shell command: " + command);
+        try {
+            return SystemUtil
+                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+        } catch (IOException e) {
+            //bubble it up
+            logE("Error running shell command: " + command);
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected Bitmap takeScreenshot() {
+        return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
+    }
+
+    protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
+        executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
+        mAmWmState.waitForValidState(new WaitForValidActivityState(activityName));
+    }
+
+    protected void launchActivityNoWait(final ComponentName targetActivityName,
+            final String... keyValuePairs) {
+        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+    }
+
+    protected void launchActivityInNewTask(final ComponentName activityName) {
+        executeShellCommand(getAmStartCmdInNewTask(activityName));
+        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 " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
+                + " " + 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();
+    }
+
+    /** Returns the set of stack ids. */
+    private HashSet<Integer> getStackIds() {
+        mAmWmState.computeState(true);
+        final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
+        final HashSet<Integer> stackIds = new HashSet<>();
+        for (ActivityManagerState.ActivityStack s : stacks) {
+            stackIds.add(s.mStackId);
+        }
+        return stackIds;
+    }
+
+    protected void launchHomeActivity() {
+        executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
+        mAmWmState.waitForHomeActivityVisible();
+    }
+
+    protected void launchActivity(ComponentName activityName, int windowingMode,
+            final String... keyValuePairs) {
+        executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
+                + " --windowingMode " + windowingMode);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
+    protected void launchActivityOnDisplay(ComponentName targetActivityName, int displayId,
+            String... keyValuePairs) {
+        executeShellCommand(getAmStartCmd(targetActivityName, displayId, keyValuePairs));
+
+        mAmWmState.waitForValidState(new WaitForValidActivityState(targetActivityName));
+    }
+
+    /**
+     * Launches {@param  activityName} into split-screen primary windowing mode and also makes
+     * the recents activity visible to the side of it.
+     */
+    protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) {
+        launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+    }
+
+    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 */);
+
+        mAmWmState.waitForValidState(
+                new WaitForValidActivityState.Builder(activityName)
+                        .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                        .setActivityType(ACTIVITY_TYPE_STANDARD)
+                        .build());
+        mAmWmState.waitForRecentsActivityVisible();
+    }
+
+    /**
+     * Launches {@param primaryActivity} into split-screen primary windowing mode
+     * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
+     */
+    protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
+            LaunchActivityBuilder secondaryActivity) {
+        // Launch split-screen primary.
+        primaryActivity
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(
+                primaryActivity.mTargetActivity).mTaskId;
+        mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+                true /* onTop */, false /* animate */, null /* initialBounds */,
+                true /* showRecents */);
+        mAmWmState.waitForRecentsActivityVisible();
+
+        // Launch split-screen secondary
+        // Recents become focused, so we can just launch new task in focused stack
+        secondaryActivity
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .setNewTask(true)
+                .setMultipleTask(true)
+                .execute();
+    }
+
+    protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
+        final int taskId = getActivityTaskId(activityName);
+        mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
+    protected void moveActivityToStack(ComponentName activityName, int stackId) {
+        final int taskId = getActivityTaskId(activityName);
+        final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
+        executeShellCommand(cmd);
+
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setStackId(stackId)
+                .build());
+    }
+
+    protected void resizeActivityTask(
+            ComponentName activityName, int left, int top, int right, int bottom) {
+        final int taskId = getActivityTaskId(activityName);
+        final String cmd = "am task resize "
+                + taskId + " " + left + " " + top + " " + right + " " + bottom;
+        executeShellCommand(cmd);
+    }
+
+    protected void resizeDockedStack(
+            int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
+        executeShellCommand(AM_RESIZE_DOCKED_STACK
+                + "0 0 " + stackWidth + " " + stackHeight
+                + " 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));
+    }
+
+    protected void pressAppSwitchButtonAndWaitForRecents() {
+        pressAppSwitchButton();
+        mAmWmState.waitForRecentsActivityVisible();
+        mAmWmState.waitForAppTransitionIdle();
+    }
+
+    // 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);
+        }
+    }
+
+    @Deprecated
+    protected int getActivityTaskId(final ComponentName activityName) {
+        final String windowName = getWindowName(activityName);
+        final String output = executeShellCommand(AM_STACK_LIST);
+        final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)");
+        for (final String line : output.split("\\n")) {
+            final Matcher matcher = activityPattern.matcher(line);
+            if (matcher.matches()) {
+                for (String word : line.split("\\s+")) {
+                    if (word.startsWith(TASK_ID_PREFIX)) {
+                        final String withColon = word.split("=")[1];
+                        return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+                    }
+                }
+            }
+        }
+        return -1;
+    }
+
+    protected boolean supportsVrMode() {
+        return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
+    }
+
+    protected boolean supportsPip() {
+        return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
+                || PRETEND_DEVICE_SUPPORTS_PIP;
+    }
+
+    protected boolean supportsFreeform() {
+        return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+                || PRETEND_DEVICE_SUPPORTS_FREEFORM;
+    }
+
+    protected boolean isHandheld() {
+        return !hasDeviceFeature(FEATURE_LEANBACK)
+                && !hasDeviceFeature(FEATURE_WATCH)
+                && !hasDeviceFeature(FEATURE_EMBEDDED);
+    }
+
+    protected boolean isTablet() {
+        // Larger than approx 7" tablets
+        return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+    }
+
+    // TODO: Switch to using a feature flag, when available.
+    protected boolean isUiModeLockedToVrHeadset() {
+        final String output = runCommandAndPrintOutput("dumpsys uimode");
+
+        Integer curUiMode = null;
+        Boolean uiModeLocked = null;
+        for (String line : output.split("\\n")) {
+            line = line.trim();
+            Matcher matcher = sCurrentUiModePattern.matcher(line);
+            if (matcher.find()) {
+                curUiMode = Integer.parseInt(matcher.group(1), 16);
+            }
+            matcher = sUiModeLockedPattern.matcher(line);
+            if (matcher.find()) {
+                uiModeLocked = matcher.group(1).equals("true");
+            }
+        }
+
+        boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
+                && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
+
+        if (uiModeLockedToVrHeadset) {
+            log("UI mode is locked to VR headset");
+        }
+
+        return uiModeLockedToVrHeadset;
+    }
+
+    protected boolean supportsSplitScreenMultiWindow() {
+        return ActivityManager.supportsSplitScreenMultiWindow(mContext);
+    }
+
+    protected boolean hasHomeScreen() {
+        if (sHasHomeScreen == null) {
+            sHasHomeScreen = !executeShellCommand(AM_NO_HOME_SCREEN).startsWith("true");
+        }
+        return sHasHomeScreen;
+    }
+
+    /**
+     * Rotation support is indicated by explicitly having both landscape and portrait
+     * features or not listing either at all.
+     */
+    protected boolean supportsRotation() {
+        final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
+        final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
+        return (supportsLandscape && supportsPortrait)
+                || (!supportsLandscape && !supportsPortrait);
+    }
+
+    protected boolean hasDeviceFeature(final String requiredFeature) {
+        return InstrumentationRegistry.getContext()
+                .getPackageManager()
+                .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;
+    }
+
+    /**
+     * Test @Rule class that disables screen doze settings before each test method running and
+     * restoring to initial values after test method finished.
+     */
+    protected static class DisableScreenDozeRule implements TestRule {
+
+        /** Copied from android.provider.Settings.Secure since these keys are hiden. */
+        private static final String[] DOZE_SETTINGS = {
+                "doze_enabled",
+                "doze_always_on",
+                "doze_pulse_on_pick_up",
+                "doze_pulse_on_long_press",
+                "doze_pulse_on_double_tap"
+        };
+
+        private String get(String key) {
+            return executeShellCommand("settings get secure " + key).trim();
+        }
+
+        private void put(String key, String value) {
+            executeShellCommand("settings put secure " + key + " " + value);
+        }
+
+        @Override
+        public Statement apply(final Statement base, final Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    final Map<String, String> initialValues = new HashMap<>();
+                    Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k)));
+                    try {
+                        Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0"));
+                        base.evaluate();
+                    } finally {
+                        Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k)));
+                    }
+                }
+            };
+        }
+    }
+
+    protected class LockScreenSession implements AutoCloseable {
+        private static final boolean DEBUG = false;
+
+        private final boolean mIsLockDisabled;
+        private boolean mLockCredentialSet;
+
+        public LockScreenSession() {
+            mIsLockDisabled = isLockDisabled();
+            mLockCredentialSet = false;
+            // Enable lock screen (swipe) by default.
+            setLockDisabled(false);
+        }
+
+        public LockScreenSession setLockCredential() {
+            mLockCredentialSet = true;
+            runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
+            return this;
+        }
+
+        public LockScreenSession enterAndConfirmLockCredential() {
+            waitForDeviceIdle(3000);
+
+            runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
+            pressEnterButton();
+            return this;
+        }
+
+        private void removeLockCredential() {
+            runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
+            mLockCredentialSet = false;
+        }
+
+        LockScreenSession disableLockScreen() {
+            setLockDisabled(true);
+            return this;
+        }
+
+        LockScreenSession sleepDevice() {
+            pressSleepButton();
+            for (int retry = 1; isDisplayOn() && retry <= 5; retry++) {
+                logAlways("***Waiting for display to turn off... retry=" + retry);
+                SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
+            }
+            return this;
+        }
+
+        LockScreenSession wakeUpDevice() {
+            pressWakeupButton();
+            return this;
+        }
+
+        LockScreenSession unlockDevice() {
+            pressUnlockButton();
+            return this;
+        }
+
+        public LockScreenSession gotoKeyguard() {
+            if (DEBUG && isLockDisabled()) {
+                logE("LockScreenSession.gotoKeygurad() is called without lock enabled.");
+            }
+            sleepDevice();
+            wakeUpDevice();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            return this;
+        }
+
+        @Override
+        public void close() throws Exception {
+            setLockDisabled(mIsLockDisabled);
+            if (mLockCredentialSet) {
+                removeLockCredential();
+            }
+
+            // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
+            // the stale credential.
+            pressBackButton();
+            sleepDevice();
+            wakeUpDevice();
+            unlockDevice();
+        }
+
+        /**
+         * Returns whether the lock screen is disabled.
+         *
+         * @return true if the lock screen is disabled, false otherwise.
+         */
+        private boolean isLockDisabled() {
+            final String isLockDisabled = runCommandAndPrintOutput(
+                    "locksettings get-disabled").trim();
+            return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled);
+        }
+
+        /**
+         * Disable the lock screen.
+         *
+         * @param lockDisabled true if should disable, false otherwise.
+         */
+        protected void setLockDisabled(boolean lockDisabled) {
+            runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
+        }
+    }
+
+    /** Helper class to save, set & wait, and restore rotation related preferences. */
+    protected class RotationSession extends SettingsSession<Integer> {
+        private final SettingsSession<Integer> mUserRotation;
+
+        public RotationSession() throws Exception {
+            // Save accelerometer_rotation preference.
+            super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+                    Settings.System::getInt, Settings.System::putInt);
+            mUserRotation = new SettingsSession<>(
+                    Settings.System.getUriFor(Settings.System.USER_ROTATION),
+                    Settings.System::getInt, Settings.System::putInt);
+            // Disable accelerometer_rotation.
+            super.set(0);
+        }
+
+        @Override
+        public void set(@NonNull Integer value) throws Exception {
+            mUserRotation.set(value);
+            // Wait for settling rotation.
+            mAmWmState.waitForRotation(value);
+        }
+
+        @Override
+        public void close() throws Exception {
+            mUserRotation.close();
+            // Restore accelerometer_rotation preference.
+            super.close();
+        }
+    }
+
+    protected int getDeviceRotation(int displayId) {
+        final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
+        Pattern pattern = Pattern.compile(
+                "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
+                        + "(rotation)(\\s+)(\\d+)");
+        Matcher matcher = pattern.matcher(displays);
+        if (matcher.find()) {
+            final String match = matcher.group(7);
+            return Integer.parseInt(match);
+        }
+
+        return INVALID_DEVICE_ROTATION;
+    }
+
+    protected static String runCommandAndPrintOutput(String command) {
+        final String output = executeShellCommand(command);
+        log(output);
+        return output;
+    }
+
+    protected static class LogSeparator {
+        private final String mUniqueString;
+
+        private LogSeparator() {
+            mUniqueString = UUID.randomUUID().toString();
+        }
+
+        @Override
+        public String toString() {
+            return mUniqueString;
+        }
+    }
+
+    /**
+     * Tries to clear logcat and inserts log separator in case clearing didn't succeed, so we can
+     * always find the starting point from where to evaluate following logs.
+     * @return Unique log separator.
+     */
+    protected LogSeparator clearLogcat() {
+        executeShellCommand("logcat -c");
+        final LogSeparator logSeparator = new LogSeparator();
+        executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
+        return logSeparator;
+    }
+
+    protected static String[] getDeviceLogsForComponents(
+            LogSeparator logSeparator, String... logTags) {
+        String filters = LOG_SEPARATOR + ":I ";
+        for (String component : logTags) {
+            filters += component + ":I ";
+        }
+        final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S")
+                .split("\\n");
+        if (logSeparator == null) {
+            return result;
+        }
+
+        // Make sure that we only check logs after the separator.
+        int i = 0;
+        boolean lookingForSeparator = true;
+        while (i < result.length && lookingForSeparator) {
+            if (result[i].contains(logSeparator.toString())) {
+                lookingForSeparator = false;
+            }
+            i++;
+        }
+        final String[] filteredResult = new String[result.length - i];
+        for (int curPos = 0; i < result.length; curPos++, i++) {
+            filteredResult[curPos] = result[i];
+        }
+        return filteredResult;
+    }
+
+    /**
+     * Base helper class for retrying validator success.
+     */
+    private abstract static class RetryValidator {
+
+        private static final int RETRY_LIMIT = 5;
+        private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1);
+
+        /**
+         * @return Error string if validation is failed, null if everything is fine.
+         **/
+        @Nullable
+        protected abstract String validate();
+
+        /**
+         * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with
+         * {@link #RETRY_INTERVAL} interval.
+         *
+         * @param waitingMessage logging message while waiting validation.
+         */
+        void assertValidator(String waitingMessage) {
+            String resultString = null;
+            for (int retry = 1; retry <= RETRY_LIMIT; retry++) {
+                resultString = validate();
+                if (resultString == null) {
+                    return;
+                }
+                logAlways(waitingMessage + ": " + resultString);
+                SystemClock.sleep(RETRY_INTERVAL);
+            }
+            fail(resultString);
+        }
+    }
+
+    private static class ActivityLifecycleCountsValidator extends RetryValidator {
+        private final ComponentName mActivityName;
+        private final LogSeparator mLogSeparator;
+        private final int mCreateCount;
+        private final int mStartCount;
+        private final int mResumeCount;
+        private final int mPauseCount;
+        private final int mStopCount;
+        private final int mDestroyCount;
+
+        ActivityLifecycleCountsValidator(ComponentName activityName, LogSeparator logSeparator,
+                int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
+                int destroyCount) {
+            mActivityName = activityName;
+            mLogSeparator = logSeparator;
+            mCreateCount = createCount;
+            mStartCount = startCount;
+            mResumeCount = resumeCount;
+            mPauseCount = pauseCount;
+            mStopCount = stopCount;
+            mDestroyCount = destroyCount;
+        }
+
+        @Override
+        @Nullable
+        protected String validate() {
+            final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
+                    mActivityName, mLogSeparator);
+            if (lifecycleCounts.mCreateCount == mCreateCount
+                    && lifecycleCounts.mStartCount == mStartCount
+                    && lifecycleCounts.mResumeCount == mResumeCount
+                    && lifecycleCounts.mPauseCount == mPauseCount
+                    && lifecycleCounts.mStopCount == mStopCount
+                    && lifecycleCounts.mDestroyCount == mDestroyCount) {
+                return null;
+            }
+            final String expected = IntStream.of(mCreateCount, mStopCount, mResumeCount,
+                    mPauseCount, mStopCount, mDestroyCount)
+                    .mapToObj(Integer::toString)
+                    .collect(Collectors.joining("/"));
+            return getActivityName(mActivityName) + " lifecycle count mismatched:"
+                    + " expected=" + expected
+                    + " actual=" + lifecycleCounts.counters();
+        }
+    }
+
+    void assertActivityLifecycle(ComponentName activityName, boolean relaunched,
+            LogSeparator logSeparator) {
+        new RetryValidator() {
+
+            @Nullable
+            @Override
+            protected String validate() {
+                final ActivityLifecycleCounts lifecycleCounts =
+                        new ActivityLifecycleCounts(activityName, logSeparator);
+                final String logTag = getLogTag(activityName);
+                if (relaunched) {
+                    if (lifecycleCounts.mDestroyCount < 1) {
+                        return logTag + " must have been destroyed. mDestroyCount="
+                                + lifecycleCounts.mDestroyCount;
+                    }
+                    if (lifecycleCounts.mCreateCount < 1) {
+                        return logTag + " must have been (re)created. mCreateCount="
+                                + lifecycleCounts.mCreateCount;
+                    }
+                    return null;
+                }
+                if (lifecycleCounts.mDestroyCount > 0) {
+                    return logTag + " must *NOT* have been destroyed. mDestroyCount="
+                            + lifecycleCounts.mDestroyCount;
+                }
+                if (lifecycleCounts.mCreateCount > 0) {
+                    return logTag + " must *NOT* have been (re)created. mCreateCount="
+                            + lifecycleCounts.mCreateCount;
+                }
+                if (lifecycleCounts.mConfigurationChangedCount < 1) {
+                    return logTag + " must have received configuration changed. "
+                            + "mConfigurationChangedCount="
+                            + lifecycleCounts.mConfigurationChangedCount;
+                }
+                return null;
+            }
+        }.assertValidator("***Waiting for valid lifecycle state");
+    }
+
+    protected void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
+            int numConfigChange, LogSeparator logSeparator) {
+        new RetryValidator() {
+
+            @Nullable
+            @Override
+            protected String validate() {
+                final ActivityLifecycleCounts lifecycleCounts =
+                        new ActivityLifecycleCounts(activityName, logSeparator);
+                final String logTag = getLogTag(activityName);
+                if (lifecycleCounts.mDestroyCount != numRelaunch) {
+                    return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
+                            + " time(s), expecting " + numRelaunch;
+                } else if (lifecycleCounts.mCreateCount != numRelaunch) {
+                    return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
+                            + " time(s), expecting " + numRelaunch;
+                } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
+                    return logTag + " has received "
+                            + lifecycleCounts.mConfigurationChangedCount
+                            + " onConfigurationChanged() calls, expecting " + numConfigChange;
+                }
+                return null;
+            }
+        }.assertValidator("***Waiting for relaunch or config changed");
+    }
+
+    protected void assertActivityDestroyed(ComponentName activityName, LogSeparator logSeparator) {
+        new RetryValidator() {
+
+            @Nullable
+            @Override
+            protected String validate() {
+                final ActivityLifecycleCounts lifecycleCounts =
+                        new ActivityLifecycleCounts(activityName, logSeparator);
+                final String logTag = getLogTag(activityName);
+                if (lifecycleCounts.mDestroyCount != 1) {
+                    return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
+                            + " time(s), expecting single destruction.";
+                }
+                if (lifecycleCounts.mCreateCount != 0) {
+                    return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
+                            + " time(s), not expecting any.";
+                }
+                if (lifecycleCounts.mConfigurationChangedCount != 0) {
+                    return logTag + " has received " + lifecycleCounts.mConfigurationChangedCount
+                            + " onConfigurationChanged() calls, not expecting any.";
+                }
+                return null;
+            }
+        }.assertValidator("***Waiting for activity destroyed");
+    }
+
+    void assertSingleLaunch(ComponentName activityName, LogSeparator logSeparator) {
+        new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
+                1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
+                0 /* destroyCount */)
+                .assertValidator("***Waiting for activity create, start, and resume");
+    }
+
+    void assertSingleLaunchAndStop(ComponentName activityName, LogSeparator logSeparator) {
+        new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
+                1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
+                0 /* destroyCount */)
+                .assertValidator("***Waiting for activity create, start, resume, pause, and stop");
+    }
+
+    void assertSingleStartAndStop(ComponentName activityName, LogSeparator logSeparator) {
+        new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
+                1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
+                0 /* destroyCount */)
+                .assertValidator("***Waiting for activity start, resume, pause, and stop");
+    }
+
+    void assertSingleStart(ComponentName activityName, LogSeparator logSeparator) {
+        new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
+                1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
+                0 /* destroyCount */)
+                .assertValidator("***Waiting for activity start and resume");
+    }
+
+    // TODO: Now that our test are device side, we can convert these to a more direct communication
+    // channel vs. depending on logs.
+    private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
+    private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
+    private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
+    private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
+    private static final Pattern sConfigurationChangedPattern =
+            Pattern.compile("(.+): onConfigurationChanged");
+    private static final Pattern sMovedToDisplayPattern =
+            Pattern.compile("(.+): onMovedToDisplay");
+    private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
+    private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
+    private static final Pattern sMultiWindowModeChangedPattern =
+            Pattern.compile("(.+): onMultiWindowModeChanged");
+    private static final Pattern sPictureInPictureModeChangedPattern =
+            Pattern.compile("(.+): onPictureInPictureModeChanged");
+    private static final Pattern sUserLeaveHintPattern = Pattern.compile("(.+): onUserLeaveHint");
+    private static final Pattern sNewConfigPattern = Pattern.compile(
+            "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
+            + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
+            + " orientation=(\\d+)");
+    private static final Pattern sDisplayStatePattern =
+            Pattern.compile("Display Power: state=(.+)");
+    private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
+    private static final Pattern sUiModeLockedPattern =
+            Pattern.compile("mUiModeLocked=(true|false)");
+
+    static class ReportedSizes {
+        int widthDp;
+        int heightDp;
+        int displayWidth;
+        int displayHeight;
+        int metricsWidth;
+        int metricsHeight;
+        int smallestWidthDp;
+        int densityDpi;
+        int orientation;
+
+        @Override
+        public String toString() {
+            return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
+                    + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
+                    + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
+                    + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
+                    + " orientation=" + orientation + "}";
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if ( this == obj ) return true;
+            if ( !(obj instanceof ReportedSizes) ) return false;
+            ReportedSizes that = (ReportedSizes) obj;
+            return widthDp == that.widthDp
+                    && heightDp == that.heightDp
+                    && displayWidth == that.displayWidth
+                    && displayHeight == that.displayHeight
+                    && metricsWidth == that.metricsWidth
+                    && metricsHeight == that.metricsHeight
+                    && smallestWidthDp == that.smallestWidthDp
+                    && densityDpi == that.densityDpi
+                    && orientation == that.orientation;
+        }
+    }
+
+    @Nullable
+    ReportedSizes getLastReportedSizesForActivity(
+            ComponentName activityName, LogSeparator logSeparator) {
+        final String logTag = getLogTag(activityName);
+        for (int retry = 1; retry <= 5; retry++ ) {
+            final ReportedSizes result = readLastReportedSizes(logSeparator, logTag);
+            if (result != null) {
+                return result;
+            }
+            logAlways("***Waiting for sizes to be reported... retry=" + retry);
+            SystemClock.sleep(1000);
+        }
+        logE("***Waiting for activity size failed: activityName=" + logTag);
+        return null;
+    }
+
+    private ReportedSizes readLastReportedSizes(LogSeparator logSeparator, String logTag) {
+        final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
+        for (int i = lines.length - 1; i >= 0; i--) {
+            final String line = lines[i].trim();
+            final Matcher matcher = sNewConfigPattern.matcher(line);
+            if (matcher.matches()) {
+                ReportedSizes details = new ReportedSizes();
+                details.widthDp = Integer.parseInt(matcher.group(2));
+                details.heightDp = Integer.parseInt(matcher.group(3));
+                details.displayWidth = Integer.parseInt(matcher.group(4));
+                details.displayHeight = Integer.parseInt(matcher.group(5));
+                details.metricsWidth = Integer.parseInt(matcher.group(6));
+                details.metricsHeight = Integer.parseInt(matcher.group(7));
+                details.smallestWidthDp = Integer.parseInt(matcher.group(8));
+                details.densityDpi = Integer.parseInt(matcher.group(9));
+                details.orientation = Integer.parseInt(matcher.group(10));
+                return details;
+            }
+        }
+        return null;
+    }
+
+    /** Waits for at least one onMultiWindowModeChanged event. */
+    ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName,
+            LogSeparator logSeparator) {
+        int retry = 1;
+        ActivityLifecycleCounts result;
+        do {
+            result = new ActivityLifecycleCounts(activityName, logSeparator);
+            if (result.mMultiWindowModeChangedCount >= 1) {
+                return result;
+            }
+            logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
+            SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
+        } while (retry++ <= 5);
+        return result;
+    }
+
+    // TODO: Now that our test are device side, we can convert these to a more direct communication
+    // channel vs. depending on logs.
+    static class ActivityLifecycleCounts {
+        int mCreateCount;
+        int mStartCount;
+        int mResumeCount;
+        int mConfigurationChangedCount;
+        int mLastConfigurationChangedLineIndex;
+        int mMovedToDisplayCount;
+        int mMultiWindowModeChangedCount;
+        int mLastMultiWindowModeChangedLineIndex;
+        int mPictureInPictureModeChangedCount;
+        int mLastPictureInPictureModeChangedLineIndex;
+        int mUserLeaveHintCount;
+        int mPauseCount;
+        int mStopCount;
+        int mLastStopLineIndex;
+        int mDestroyCount;
+
+        ActivityLifecycleCounts(ComponentName componentName, LogSeparator logSeparator) {
+            int lineIndex = 0;
+            waitForIdle();
+            for (String line : getDeviceLogsForComponents(logSeparator, getLogTag(componentName))) {
+                line = line.trim();
+                lineIndex++;
+
+                Matcher matcher = sCreatePattern.matcher(line);
+                if (matcher.matches()) {
+                    mCreateCount++;
+                    continue;
+                }
+
+                matcher = sStartPattern.matcher(line);
+                if (matcher.matches()) {
+                    mStartCount++;
+                    continue;
+                }
+
+                matcher = sResumePattern.matcher(line);
+                if (matcher.matches()) {
+                    mResumeCount++;
+                    continue;
+                }
+
+                matcher = sConfigurationChangedPattern.matcher(line);
+                if (matcher.matches()) {
+                    mConfigurationChangedCount++;
+                    mLastConfigurationChangedLineIndex = lineIndex;
+                    continue;
+                }
+
+                matcher = sMovedToDisplayPattern.matcher(line);
+                if (matcher.matches()) {
+                    mMovedToDisplayCount++;
+                    continue;
+                }
+
+                matcher = sMultiWindowModeChangedPattern.matcher(line);
+                if (matcher.matches()) {
+                    mMultiWindowModeChangedCount++;
+                    mLastMultiWindowModeChangedLineIndex = lineIndex;
+                    continue;
+                }
+
+                matcher = sPictureInPictureModeChangedPattern.matcher(line);
+                if (matcher.matches()) {
+                    mPictureInPictureModeChangedCount++;
+                    mLastPictureInPictureModeChangedLineIndex = lineIndex;
+                    continue;
+                }
+
+                matcher = sUserLeaveHintPattern.matcher(line);
+                if (matcher.matches()) {
+                    mUserLeaveHintCount++;
+                    continue;
+                }
+
+                matcher = sPausePattern.matcher(line);
+                if (matcher.matches()) {
+                    mPauseCount++;
+                    continue;
+                }
+
+                matcher = sStopPattern.matcher(line);
+                if (matcher.matches()) {
+                    mStopCount++;
+                    mLastStopLineIndex = lineIndex;
+                    continue;
+                }
+
+                matcher = sDestroyPattern.matcher(line);
+                if (matcher.matches()) {
+                    mDestroyCount++;
+                    continue;
+                }
+            }
+        }
+
+        String counters() {
+            return IntStream.of(mCreateCount, mStartCount, mResumeCount, mPauseCount, mStopCount,
+                    mDestroyCount)
+                    .mapToObj(Integer::toString)
+                    .collect(Collectors.joining("/"));
+        }
+    }
+
+    protected void stopTestPackage(final ComponentName activityName) {
+        executeShellCommand("am force-stop " + activityName.getPackageName());
+    }
+
+    protected LaunchActivityBuilder getLaunchActivityBuilder() {
+        return new LaunchActivityBuilder(mAmWmState);
+    }
+
+    protected static class LaunchActivityBuilder {
+        private final ActivityAndWindowManagersState mAmWmState;
+
+        // The activity to be launched
+        private ComponentName mTargetActivity = TEST_ACTIVITY;
+        private boolean mUseApplicationContext;
+        private boolean mToSide;
+        private boolean mRandomData;
+        private boolean mNewTask;
+        private boolean mMultipleTask;
+        private int mDisplayId = INVALID_DISPLAY_ID;
+        // A proxy activity that launches other activities including mTargetActivityName
+        private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY;
+        private boolean mReorderToFront;
+        private boolean mWaitForLaunched;
+        private boolean mSuppressExceptions;
+        // 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 enum LauncherType {
+            INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
+        }
+        private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
+
+        public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) {
+            mAmWmState = amWmState;
+            mWaitForLaunched = true;
+        }
+
+        public LaunchActivityBuilder setToSide(boolean toSide) {
+            mToSide = toSide;
+            return this;
+        }
+
+        public LaunchActivityBuilder setRandomData(boolean randomData) {
+            mRandomData = randomData;
+            return this;
+        }
+
+        public LaunchActivityBuilder setNewTask(boolean newTask) {
+            mNewTask = newTask;
+            return this;
+        }
+
+        public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
+            mMultipleTask = multipleTask;
+            return this;
+        }
+
+        public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
+            mReorderToFront = reorderToFront;
+            return this;
+        }
+
+        public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) {
+            mUseApplicationContext = useApplicationContext;
+            return this;
+        }
+
+        public ComponentName getTargetActivity() {
+            return mTargetActivity;
+        }
+
+        public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) {
+            mTargetActivity = targetActivity;
+            return this;
+        }
+
+        public LaunchActivityBuilder setDisplayId(int id) {
+            mDisplayId = id;
+            return this;
+        }
+
+        public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) {
+            mLaunchingActivity = launchingActivity;
+            mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
+            return this;
+        }
+
+        public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
+            mWaitForLaunched = shouldWait;
+            return this;
+        }
+
+        /** Use broadcast receiver as a launchpad for activities. */
+        public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
+                final String broadcastAction) {
+            mBroadcastReceiver = broadcastReceiver;
+            mBroadcastReceiverAction = broadcastAction;
+            mLauncherType = LauncherType.BROADCAST_RECEIVER;
+            return this;
+        }
+
+        /** Use {@link android.app.Instrumentation} as a launchpad for activities. */
+        public LaunchActivityBuilder setUseInstrumentation() {
+            mLauncherType = LauncherType.INSTRUMENTATION;
+            // Calling startActivity() from outside of an Activity context requires the
+            // FLAG_ACTIVITY_NEW_TASK flag.
+            setNewTask(true);
+            return this;
+        }
+
+        public LaunchActivityBuilder setSuppressExceptions(boolean suppress) {
+            mSuppressExceptions = suppress;
+            return this;
+        }
+
+        public void execute() {
+            switch (mLauncherType) {
+                case INSTRUMENTATION:
+                    launchUsingInstrumentation();
+                    break;
+                case LAUNCHING_ACTIVITY:
+                case BROADCAST_RECEIVER:
+                    launchUsingShellCommand();
+            }
+
+            if (mWaitForLaunched) {
+                mAmWmState.waitForValidState(mTargetActivity);
+            }
+        }
+
+        /** Launch an activity using instrumentation. */
+        private void launchUsingInstrumentation() {
+            final Bundle b = new Bundle();
+            b.putBoolean(KEY_USE_INSTRUMENTATION, true);
+            b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
+            b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
+            b.putBoolean(KEY_RANDOM_DATA, mRandomData);
+            b.putBoolean(KEY_NEW_TASK, mNewTask);
+            b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
+            b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
+            b.putInt(KEY_DISPLAY_ID, mDisplayId);
+            b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext);
+            b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity));
+            b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
+            final Context context = InstrumentationRegistry.getContext();
+            launchActivityFromExtras(context, b);
+        }
+
+        /** Build and execute a shell command to launch an activity. */
+        private void launchUsingShellCommand() {
+            StringBuilder commandBuilder = new StringBuilder();
+            if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) {
+                // Use broadcast receiver to launch the target.
+                commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction)
+                        .append(" -p ").append(mBroadcastReceiver.getPackageName())
+                        // Include stopped packages
+                        .append(" -f 0x00000020");
+            } else {
+                // Use launching activity to launch the target.
+                commandBuilder.append(getAmStartCmd(mLaunchingActivity))
+                        .append(" -f 0x20000020");
+            }
+
+            // Add a flag to ensure we actually mean to launch an activity.
+            commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true");
+
+            if (mToSide) {
+                commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true");
+            }
+            if (mRandomData) {
+                commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true");
+            }
+            if (mNewTask) {
+                commandBuilder.append(" --ez " + KEY_NEW_TASK + " true");
+            }
+            if (mMultipleTask) {
+                commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true");
+            }
+            if (mReorderToFront) {
+                commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
+            }
+            if (mDisplayId != INVALID_DISPLAY_ID) {
+                commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
+            }
+
+            if (mUseApplicationContext) {
+                commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true");
+            }
+
+            if (mTargetActivity != null) {
+                // {@link ActivityLauncher} parses this extra string by
+                // {@link ComponentName#unflattenFromString(String)}.
+                commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ")
+                        .append(getActivityName(mTargetActivity));
+            }
+
+            if (mSuppressExceptions) {
+                commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true");
+            }
+            executeShellCommand(commandBuilder.toString());
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ComponentNameUtils.java b/tests/framework/base/activitymanager/util/src/android/server/am/ComponentNameUtils.java
new file mode 100644
index 0000000..cb69f7d
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ComponentNameUtils.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.server.am;
+
+import android.content.ComponentName;
+
+public class ComponentNameUtils {
+
+    /**
+     * Get component's activity name.
+     *
+     * @return the activity name of {@code componentName}, such as "contextPackage/.SimpleClassName"
+     *     or "contextPackage/FullyClassName".
+     */
+    public static String getActivityName(ComponentName componentName) {
+        return componentName.flattenToShortString();
+    }
+
+    /**
+     * Get component's window mane.
+     *
+     * @return the window name of {@code componentName}, such that "contextPackage/FullyClassName".
+     */
+    public static String getWindowName(ComponentName componentName) {
+        return componentName.flattenToString();
+    }
+
+    /**
+     * Get component's simple class name suitable for logging tag.
+     *
+     * @return the simple class name of {@code componentName} that has no '.'.
+     */
+    public static String getLogTag(ComponentName componentName) {
+        final String className = componentName.getClassName();
+        final int pos = className.lastIndexOf('.');
+        return pos >= 0 ? className.substring(pos + 1) : className;
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java b/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java
new file mode 100644
index 0000000..060fc60
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.WindowConfiguration;
+import android.app.nano.WindowConfigurationProto;
+import android.content.nano.ConfigurationProto;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.nano.RectProto;
+
+/**
+ * Utility class for extracting some common framework object from nano proto objects.
+ * Normally the extractors will be in the framework object class, but we don't want the framework to
+ * depend on nano proto due to size cost.
+ * TODO: This class should probably be in frameworks/base/lib project so it can be used
+ * outside of CTS.
+ */
+public class ProtoExtractors {
+    public static Configuration extract(ConfigurationProto proto) {
+        final Configuration config = new Configuration();
+        if (proto == null) {
+            return config;
+        }
+        config.windowConfiguration.setTo(extract(proto.windowConfiguration));
+        config.densityDpi = proto.densityDpi;
+        config.orientation = proto.orientation;
+        config.screenHeightDp = proto.screenHeightDp;
+        config.screenWidthDp = proto.screenWidthDp;
+        config.smallestScreenWidthDp = proto.smallestScreenWidthDp;
+        config.screenLayout = proto.screenLayout;
+        config.uiMode = proto.uiMode;
+        return config;
+    }
+
+    public static WindowConfiguration extract(WindowConfigurationProto proto) {
+        final WindowConfiguration config = new WindowConfiguration();
+        if (proto == null) {
+            return config;
+        }
+        config.setAppBounds(extract(proto.appBounds));
+        config.setWindowingMode(proto.windowingMode);
+        config.setActivityType(proto.activityType);
+        return config;
+    }
+
+    public static Rect extract(RectProto proto) {
+        if (proto == null) {
+            return null;
+        }
+        return new Rect(proto.left, proto.top, proto.right, proto.bottom);
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java b/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java
new file mode 100644
index 0000000..cf848bf
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util.Log;
+
+/**
+ * Util class to perform simple state logging.
+ */
+public class StateLogger {
+
+    private static final boolean DEBUG = false;
+    private static String TAG = "AMWM";
+
+    /**
+     * Simple info-level logging gated by {@link #DEBUG} flag
+     */
+    public static void log(String logText) {
+        if (DEBUG) {
+            Log.i(TAG, logText);
+        }
+    }
+
+    public static void logAlways(String logText) {
+        Log.i(TAG, logText);
+    }
+
+    public static void logE(String logText) {
+        Log.e(TAG, logText);
+    }
+
+    public static void logE(String logText, Throwable e) {
+        Log.e(TAG, logText, e);
+    }
+}
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
new file mode 100644
index 0000000..0283d85
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/UiDeviceUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.StateLogger.logE;
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.view.KeyEvent.KEYCODE_APP_SWITCH;
+import static android.view.KeyEvent.KEYCODE_MENU;
+import static android.view.KeyEvent.KEYCODE_SLEEP;
+import static android.view.KeyEvent.KEYCODE_WAKEUP;
+import static android.view.KeyEvent.KEYCODE_WINDOW;
+
+import android.app.KeyguardManager;
+import android.graphics.Point;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Helper class to interact with {@link UiDevice}.
+ *
+ * All references to {@link UiDevice} and {@link KeyEvent} should be here for easy debugging.
+ */
+public class UiDeviceUtils {
+
+    private static final String TAG = "UiDeviceUtils";
+    private static final boolean DEBUG = false;
+
+    static void waitForDeviceIdle(long timeout) {
+        if (DEBUG) Log.d(TAG, "waitForDeviceIdle: timeout=" + timeout);
+        getDevice().waitForIdle(timeout);
+    }
+
+    public static void wakeUpDevice() throws RemoteException {
+        if (DEBUG) Log.d(TAG, "wakeUpDevice");
+        getDevice().wakeUp();
+    }
+
+    public static void dragPointer(Point from, Point to, int steps) {
+        if (DEBUG) Log.d(TAG, "dragPointer: from=" + from + " to=" + to + " steps=" + steps);
+        getDevice().drag(from.x, from.y, to.x, to.y, steps);
+    }
+
+    static void pressEnterButton() {
+        if (DEBUG) Log.d(TAG, "pressEnterButton");
+        getDevice().pressEnter();
+    }
+
+    static void pressHomeButton() {
+        if (DEBUG) Log.d(TAG, "pressHomeButton");
+        getDevice().pressHome();
+    }
+
+    public static void pressBackButton() {
+        if (DEBUG) Log.d(TAG, "pressBackButton");
+        getDevice().pressBack();
+    }
+
+    public static void pressMenuButton() {
+        if (DEBUG) Log.d(TAG, "pressMenuButton");
+        getDevice().pressMenu();
+    }
+
+    static void pressSleepButton() {
+        if (DEBUG) Log.d(TAG, "pressSleepButton");
+        final PowerManager pm = getContext().getSystemService(PowerManager.class);
+        retryPressKeyCode(KEYCODE_SLEEP, () -> pm != null && !pm.isInteractive(),
+                "***Waiting for device sleep...");
+    }
+
+    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() {
+        if (DEBUG) Log.d(TAG, "pressUnlockButton");
+        final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
+        retryPressKeyCode(KEYCODE_MENU, () -> kgm != null && !kgm.isKeyguardLocked(),
+                "***Waiting for device unlock...");
+    }
+
+    static void pressWindowButton() {
+        if (DEBUG) Log.d(TAG, "pressWindowButton");
+        pressKeyCode(KEYCODE_WINDOW);
+    }
+
+    static void pressAppSwitchButton() {
+        if (DEBUG) Log.d(TAG, "pressAppSwitchButton");
+        pressKeyCode(KEYCODE_APP_SWITCH);
+    }
+
+    private static void retryPressKeyCode(int keyCode, BooleanSupplier waitFor, String msg) {
+        int retry = 1;
+        do {
+            pressKeyCode(keyCode);
+            if (waitFor.getAsBoolean()) {
+                return;
+            }
+            Log.d(TAG, msg + " retry=" + retry);
+            SystemClock.sleep(50);
+        } while (retry++ < 5);
+        if (!waitFor.getAsBoolean()) {
+            logE(msg + " FAILED");
+        }
+    }
+
+    private static void pressKeyCode(int keyCode) {
+        getDevice().pressKeyCode(keyCode);
+    }
+
+    private static UiDevice getDevice() {
+        return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+}
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
new file mode 100644
index 0000000..c82e484
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.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;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.ComponentNameUtils.getWindowName;
+
+import android.content.ComponentName;
+import android.support.annotation.Nullable;
+
+public class WaitForValidActivityState {
+    @Nullable
+    public final ComponentName activityName;
+    @Nullable
+    public final String windowName;
+    public final int stackId;
+    public final int windowingMode;
+    public final int activityType;
+
+    public static WaitForValidActivityState forWindow(final String windowName) {
+        return new Builder().setWindowName(windowName).build();
+    }
+
+    public WaitForValidActivityState(final ComponentName activityName) {
+        this.activityName = activityName;
+        this.windowName = getWindowName(activityName);
+        this.stackId = INVALID_STACK_ID;
+        this.windowingMode = WINDOWING_MODE_UNDEFINED;
+        this.activityType = ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    private WaitForValidActivityState(final Builder builder) {
+        this.activityName = builder.mActivityName;
+        this.windowName = builder.mWindowName;
+        this.stackId = builder.mStackId;
+        this.windowingMode = builder.mWindowingMode;
+        this.activityType = builder.mActivityType;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("wait:");
+        if (activityName != null) {
+            sb.append(" activity=").append(getActivityName(activityName));
+        }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+            sb.append(" type=").append(activityTypeName(activityType));
+        }
+        if (windowName != null) {
+            sb.append(" window=").append(windowName);
+        }
+        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+            sb.append(" mode=").append(windowingModeName(windowingMode));
+        }
+        if (stackId != INVALID_STACK_ID) {
+            sb.append(" stack=").append(stackId);
+        }
+        return sb.toString();
+    }
+
+    private static String windowingModeName(int windowingMode) {
+        switch (windowingMode) {
+            case WINDOWING_MODE_UNDEFINED: return "UNDEFINED";
+            case WINDOWING_MODE_FULLSCREEN: return "FULLSCREEN";
+            case WINDOWING_MODE_PINNED: return "PINNED";
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "SPLIT_SCREEN_PRIMARY";
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "SPLIT_SCREEN_SECONDARY";
+            case WINDOWING_MODE_FREEFORM: return "FREEFORM";
+            default:
+                throw new IllegalArgumentException("Unknown WINDOWING_MODE_: " + windowingMode);
+        }
+    }
+
+    private static String activityTypeName(int activityType) {
+        switch (activityType) {
+            case ACTIVITY_TYPE_UNDEFINED: return "UNDEFINED";
+            case ACTIVITY_TYPE_STANDARD: return "STANDARD";
+            case ACTIVITY_TYPE_HOME: return "HOME";
+            case ACTIVITY_TYPE_RECENTS: return "RECENTS";
+            case ACTIVITY_TYPE_ASSISTANT: return "ASSISTANT";
+            default:
+                throw new IllegalArgumentException("Unknown ACTIVITY_TYPE_: " + activityType);
+        }
+    }
+
+    public static class Builder {
+        @Nullable
+        private ComponentName mActivityName = null;
+        @Nullable
+        private String mWindowName = null;
+        private int mStackId = INVALID_STACK_ID;
+        private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+        private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
+
+        private Builder() {}
+
+        public Builder(final ComponentName activityName) {
+            mActivityName = activityName;
+            mWindowName = getWindowName(activityName);
+        }
+
+        private Builder setWindowName(String windowName) {
+            mWindowName = windowName;
+            return this;
+        }
+
+        public Builder setStackId(int stackId) {
+            mStackId = stackId;
+            return this;
+        }
+
+        public Builder setWindowingMode(int windowingMode) {
+            mWindowingMode = windowingMode;
+            return this;
+        }
+
+        public Builder setActivityType(int activityType) {
+            mActivityType = activityType;
+            return this;
+        }
+
+        public WaitForValidActivityState build() {
+            return new WaitForValidActivityState(this);
+        }
+    }
+}
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
new file mode 100644
index 0000000..e53b9f8
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ProtoExtractors.extract;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+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.nano.DisplayInfoProto;
+
+import com.android.server.wm.proto.nano.AppTransitionProto;
+import com.android.server.wm.proto.nano.AppWindowTokenProto;
+import com.android.server.wm.proto.nano.ConfigurationContainerProto;
+import com.android.server.wm.proto.nano.DisplayFramesProto;
+import com.android.server.wm.proto.nano.DisplayProto;
+import com.android.server.wm.proto.nano.IdentifierProto;
+import com.android.server.wm.proto.nano.PinnedStackControllerProto;
+import com.android.server.wm.proto.nano.StackProto;
+import com.android.server.wm.proto.nano.TaskProto;
+import com.android.server.wm.proto.nano.WindowContainerProto;
+import com.android.server.wm.proto.nano.WindowManagerServiceDumpProto;
+import com.android.server.wm.proto.nano.WindowStateAnimatorProto;
+import com.android.server.wm.proto.nano.WindowStateProto;
+import com.android.server.wm.proto.nano.WindowSurfaceControllerProto;
+import com.android.server.wm.proto.nano.WindowTokenProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class WindowManagerState {
+    public static final String TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN";
+    public static final String TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE";
+    public static final String TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN";
+    public static final String TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE";
+
+    public static final String TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN";
+    public static final String TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE";
+    public static final String TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN";
+    public static final String TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE";
+
+    public static final String TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY";
+    public static final String TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
+            "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
+    public static final String TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE";
+    public static final String TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE";
+
+    public static final String APP_STATE_IDLE = "APP_STATE_IDLE";
+
+    private static final String DUMPSYS_WINDOW = "dumpsys window -a --proto";
+
+    private static final String STARTING_WINDOW_PREFIX = "Starting ";
+    private static final String DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: ";
+
+    // 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.
+    private final List<WindowStack> mStacks = new ArrayList();
+    // Stacks on all attached displays, in z-order with the top most at the front of the list.
+    private final Map<Integer, List<WindowStack>> mDisplayStacks
+            = new HashMap<>();
+    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();
+    private final LinkedList<String> mSysDump = new LinkedList();
+    private int mRotation;
+    private int mLastOrientation;
+    private boolean mDisplayFrozen;
+    private boolean mIsDockedStackMinimized;
+
+    public void computeState() {
+        // It is possible the system is in the middle of transition to the right state when we get
+        // the dump. We try a few times to get the information we need before giving up.
+        int retriesLeft = 3;
+        boolean retry = false;
+        byte[] dump = null;
+
+        log("==============================");
+        log("      WindowManagerState      ");
+        log("==============================");
+        do {
+            if (retry) {
+                log("***Incomplete WM state. Retrying...");
+                // Wait half a second between retries for window manager to finish transitioning...
+                SystemClock.sleep(500);
+            }
+
+            dump = executeShellCommand(DUMPSYS_WINDOW);
+            try {
+                parseSysDumpProto(dump);
+            } catch (InvalidProtocolBufferNanoException ex) {
+                throw new RuntimeException("Failed to parse dumpsys:\n"
+                        + new String(dump, StandardCharsets.UTF_8), ex);
+            }
+
+            retry = mWindowStates.isEmpty() || mFocusedApp == null;
+        } while (retry && retriesLeft-- > 0);
+
+        if (mWindowStates.isEmpty()) {
+            logE("No Windows found...");
+        }
+        if (mFocusedWindow == null) {
+            logE("No Focused Window...");
+        }
+        if (mFocusedApp == null) {
+            logE("No Focused App...");
+        }
+    }
+
+    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);
+        }
+    }
+
+
+    private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
+        reset();
+        WindowManagerServiceDumpProto state = WindowManagerServiceDumpProto.parseFrom(sysDump);
+        List<WindowState> allWindows = new ArrayList<>();
+        Map<String, WindowState> windowMap = new HashMap<>();
+        if (state.focusedWindow != null) {
+            mFocusedWindow = state.focusedWindow.title;
+        }
+        mFocusedApp = state.focusedApp;
+        for (int i = 0; i < state.rootWindowContainer.displays.length; i++) {
+            DisplayProto displayProto = state.rootWindowContainer.displays[i];
+            final Display display = new Display(displayProto);
+            mDisplays.add(display);
+            allWindows.addAll(display.getWindows());
+            List<WindowStack> stacks = new ArrayList<>();
+            for (int j = 0; j < displayProto.stacks.length; j++) {
+                StackProto stackProto = displayProto.stacks[j];
+                final WindowStack stack = new WindowStack(stackProto);
+                mStacks.add(stack);
+                stacks.add(stack);
+                allWindows.addAll(stack.getWindows());
+            }
+            mDisplayStacks.put(display.mDisplayId, stacks);
+
+            // use properties from the default display only
+            if (display.getDisplayId() == DEFAULT_DISPLAY) {
+                if (displayProto.dockedStackDividerController != null) {
+                    mIsDockedStackMinimized =
+                            displayProto.dockedStackDividerController.minimizedDock;
+                }
+                PinnedStackControllerProto pinnedStackProto = displayProto.pinnedStackController;
+                if (pinnedStackProto != null) {
+                    mDefaultPinnedStackBounds = extract(pinnedStackProto.defaultBounds);
+                    mPinnedStackMovementBounds = extract(pinnedStackProto.movementBounds);
+                }
+            }
+        }
+        for (WindowState w : allWindows) {
+            windowMap.put(w.getToken(), w);
+        }
+        for (int i = 0; i < state.rootWindowContainer.windows.length; i++) {
+            IdentifierProto identifierProto = state.rootWindowContainer.windows[i];
+            String hash_code = Integer.toHexString(identifierProto.hashCode);
+            mWindowStates.add(windowMap.get(hash_code));
+        }
+        if (state.inputMethodWindow != null) {
+            mInputMethodWindowAppToken = Integer.toHexString(state.inputMethodWindow.hashCode);
+        }
+        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) {
+        switch (appState) {
+            case AppTransitionProto.APP_STATE_IDLE:
+                return "APP_STATE_IDLE";
+            case AppTransitionProto.APP_STATE_READY:
+                return "APP_STATE_READY";
+            case AppTransitionProto.APP_STATE_RUNNING:
+                return "APP_STATE_RUNNING";
+            case AppTransitionProto.APP_STATE_TIMEOUT:
+                return "APP_STATE_TIMEOUT";
+            default:
+                fail("Invalid AppTransitionState");
+                return null;
+        }
+    }
+
+    static String appTransitionToString(int transition) {
+        switch (transition) {
+            case AppTransitionProto.TRANSIT_UNSET: {
+                return "TRANSIT_UNSET";
+            }
+            case AppTransitionProto.TRANSIT_NONE: {
+                return "TRANSIT_NONE";
+            }
+            case AppTransitionProto.TRANSIT_ACTIVITY_OPEN: {
+                return TRANSIT_ACTIVITY_OPEN;
+            }
+            case AppTransitionProto.TRANSIT_ACTIVITY_CLOSE: {
+                return TRANSIT_ACTIVITY_CLOSE;
+            }
+            case AppTransitionProto.TRANSIT_TASK_OPEN: {
+                return TRANSIT_TASK_OPEN;
+            }
+            case AppTransitionProto.TRANSIT_TASK_CLOSE: {
+                return TRANSIT_TASK_CLOSE;
+            }
+            case AppTransitionProto.TRANSIT_TASK_TO_FRONT: {
+                return "TRANSIT_TASK_TO_FRONT";
+            }
+            case AppTransitionProto.TRANSIT_TASK_TO_BACK: {
+                return "TRANSIT_TASK_TO_BACK";
+            }
+            case AppTransitionProto.TRANSIT_WALLPAPER_CLOSE: {
+                return TRANSIT_WALLPAPER_CLOSE;
+            }
+            case AppTransitionProto.TRANSIT_WALLPAPER_OPEN: {
+                return TRANSIT_WALLPAPER_OPEN;
+            }
+            case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_OPEN: {
+                return TRANSIT_WALLPAPER_INTRA_OPEN;
+            }
+            case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_CLOSE: {
+                return TRANSIT_WALLPAPER_INTRA_CLOSE;
+            }
+            case AppTransitionProto.TRANSIT_TASK_OPEN_BEHIND: {
+                return "TRANSIT_TASK_OPEN_BEHIND";
+            }
+            case AppTransitionProto.TRANSIT_ACTIVITY_RELAUNCH: {
+                return "TRANSIT_ACTIVITY_RELAUNCH";
+            }
+            case AppTransitionProto.TRANSIT_DOCK_TASK_FROM_RECENTS: {
+                return "TRANSIT_DOCK_TASK_FROM_RECENTS";
+            }
+            case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY: {
+                return TRANSIT_KEYGUARD_GOING_AWAY;
+            }
+            case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
+                return TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+            }
+            case AppTransitionProto.TRANSIT_KEYGUARD_OCCLUDE: {
+                return TRANSIT_KEYGUARD_OCCLUDE;
+            }
+            case AppTransitionProto.TRANSIT_KEYGUARD_UNOCCLUDE: {
+                return TRANSIT_KEYGUARD_UNOCCLUDE;
+            }
+            default: {
+                fail("Invalid lastUsedAppTransition");
+                return null;
+            }
+        }
+    }
+
+    void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
+        tokenList.clear();
+
+        for (WindowState ws : mWindowStates) {
+            if (windowName.equals(ws.getName())) {
+                tokenList.add(ws.getToken());
+            }
+        }
+    }
+
+    public void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
+        windowList.clear();
+        for (WindowState ws : mWindowStates) {
+            if (ws.isShown() && windowName.equals(ws.getName())) {
+                windowList.add(ws);
+            }
+        }
+    }
+
+    public boolean containsExitingWindow() {
+        for (WindowState ws : mWindowStates) {
+            if (ws.isExitingWindow()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public WindowState getWindowByPackageName(String packageName, int windowType) {
+        for (WindowState ws : mWindowStates) {
+            final String name = ws.getName();
+            if (name == null || !name.contains(packageName)) {
+                continue;
+            }
+            if (windowType != ws.getType()) {
+                continue;
+            }
+            return ws;
+        }
+
+        return null;
+    }
+
+    public void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
+            List<WindowState> outWindowList) {
+        outWindowList.clear();
+        for (WindowState ws : mWindowStates) {
+            final String name = ws.getName();
+            if (name == null || !name.contains(packageName)) {
+                continue;
+            }
+            if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
+                continue;
+            }
+            outWindowList.add(ws);
+        }
+    }
+
+    WindowState getWindowStateForAppToken(String appToken) {
+        for (WindowState ws : mWindowStates) {
+            if (ws.getToken().equals(appToken)) {
+                return ws;
+            }
+        }
+        return null;
+    }
+
+    Display getDisplay(int displayId) {
+        for (Display display : mDisplays) {
+            if (displayId == display.getDisplayId()) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    List<Display> getDisplays() {
+        return mDisplays;
+    }
+
+    String getFrontWindow() {
+        if (mWindowStates == null || mWindowStates.isEmpty()) {
+            return null;
+        }
+        return mWindowStates.get(0).getName();
+    }
+
+    public String getFocusedWindow() {
+        return mFocusedWindow;
+    }
+
+    public String getFocusedApp() {
+        return mFocusedApp;
+    }
+
+    String getLastTransition() {
+        return mLastTransition;
+    }
+
+    String getAppTransitionState() {
+        return mAppTransitionState;
+    }
+
+    int getFrontStackId(int displayId) {
+        return mDisplayStacks.get(displayId).get(0).mStackId;
+    }
+
+    int getFrontStackActivityType(int displayId) {
+        return mDisplayStacks.get(displayId).get(0).getActivityType();
+    }
+
+    public int getRotation() {
+        return mRotation;
+    }
+
+    int getLastOrientation() {
+        return mLastOrientation;
+    }
+
+    boolean containsStack(int stackId) {
+        for (WindowStack stack : mStacks) {
+            if (stackId == stack.mStackId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean containsStack(int windowingMode, int activityType) {
+        for (WindowStack stack : mStacks) {
+            if (activityType != ACTIVITY_TYPE_UNDEFINED
+                    && activityType != stack.getActivityType()) {
+                continue;
+            }
+            if (windowingMode != WINDOWING_MODE_UNDEFINED
+                    && windowingMode != stack.getWindowingMode()) {
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if there exists a window record with matching windowName.
+     */
+    boolean containsWindow(String windowName) {
+        for (WindowState window : mWindowStates) {
+            if (window.getName().equals(windowName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if at least one window which matches provided window name is visible.
+     */
+    boolean isWindowVisible(String windowName) {
+        for (WindowState window : mWindowStates) {
+            if (window.getName().equals(windowName)) {
+                if (window.isShown()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean allWindowsVisible(String windowName) {
+        boolean allVisible = false;
+        for (WindowState window : mWindowStates) {
+            if (window.getName().equals(windowName)) {
+                if (!window.isShown()) {
+                    log("[VISIBLE] not visible" + windowName);
+                    return false;
+                }
+                log("[VISIBLE] visible" + windowName);
+                allVisible = true;
+            }
+        }
+        return allVisible;
+    }
+
+    WindowStack getStack(int stackId) {
+        for (WindowStack stack : mStacks) {
+            if (stackId == stack.mStackId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    WindowStack getStandardStackByWindowingMode(int windowingMode) {
+        for (WindowStack stack : mStacks) {
+            if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+                continue;
+            }
+            if (stack.getWindowingMode() == windowingMode) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    /** Get the stack position on its display. */
+    int getStackIndexByActivityType(int activityType) {
+        for (Integer displayId : mDisplayStacks.keySet()) {
+            List<WindowStack> stacks = mDisplayStacks.get(displayId);
+            for (int i = 0; i < stacks.size(); i++) {
+                if (activityType == stacks.get(i).getActivityType()) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    WindowState getInputMethodWindowState() {
+        return getWindowStateForAppToken(mInputMethodWindowAppToken);
+    }
+
+    Rect getStableBounds() {
+        return getDisplay(DEFAULT_DISPLAY).mStableBounds;
+    }
+
+    Rect getDefaultPinnedStackBounds() {
+        return new Rect(mDefaultPinnedStackBounds);
+    }
+
+    Rect getPinnedStackMomentBounds() {
+        return new Rect(mPinnedStackMovementBounds);
+    }
+
+    WindowState findFirstWindowWithType(int type) {
+        for (WindowState window : mWindowStates) {
+            if (window.getType() == type) {
+                return window;
+            }
+        }
+        return null;
+    }
+
+    public boolean isDisplayFrozen() {
+        return mDisplayFrozen;
+    }
+
+    public boolean isDockedStackMinimized() {
+        return mIsDockedStackMinimized;
+    }
+
+    public int getZOrder(WindowState w) {
+        return mWindowStates.size() - mWindowStates.indexOf(w);
+    }
+
+    private void reset() {
+        mSysDump.clear();
+        mStacks.clear();
+        mDisplays.clear();
+        mWindowStates.clear();
+        mDisplayStacks.clear();
+        mFocusedWindow = null;
+        mFocusedApp = null;
+        mLastTransition = null;
+        mInputMethodWindowAppToken = null;
+        mIsDockedStackMinimized = false;
+        mDefaultPinnedStackBounds.setEmpty();
+        mPinnedStackMovementBounds.setEmpty();
+        mRotation = 0;
+        mLastOrientation = 0;
+        mDisplayFrozen = false;
+    }
+
+    static class WindowStack extends WindowContainer {
+
+        int mStackId;
+        ArrayList<WindowTask> mTasks = new ArrayList<>();
+        boolean mWindowAnimationBackgroundSurfaceShowing;
+
+        WindowStack(StackProto proto) {
+            super(proto.windowContainer);
+            mStackId = proto.id;
+            mFullscreen = proto.fillsParent;
+            mBounds = extract(proto.bounds);
+            for (int i = 0; i < proto.tasks.length; i++) {
+                TaskProto taskProto = proto.tasks[i];
+                WindowTask task = new WindowTask(taskProto);
+                mTasks.add(task);
+                mSubWindows.addAll(task.getWindows());
+            }
+            mWindowAnimationBackgroundSurfaceShowing = proto.animationBackgroundSurfaceIsDimming;
+        }
+
+        WindowTask getTask(int taskId) {
+            for (WindowTask task : mTasks) {
+                if (taskId == task.mTaskId) {
+                    return task;
+                }
+            }
+            return null;
+        }
+
+        boolean isWindowAnimationBackgroundSurfaceShowing() {
+            return mWindowAnimationBackgroundSurfaceShowing;
+        }
+    }
+
+    static class WindowTask extends WindowContainer {
+
+        int mTaskId;
+        Rect mTempInsetBounds;
+        List<String> mAppTokens = new ArrayList<>();
+
+        WindowTask(TaskProto proto) {
+            super(proto.windowContainer);
+            mTaskId = proto.id;
+            mFullscreen = proto.fillsParent;
+            mBounds = extract(proto.bounds);
+            for (int i = 0; i < proto.appWindowTokens.length; i++) {
+                AppWindowTokenProto appWindowTokenProto = proto.appWindowTokens[i];
+                mAppTokens.add(appWindowTokenProto.name);
+                WindowTokenProto windowTokenProto = appWindowTokenProto.windowToken;
+                for (int j = 0; j < windowTokenProto.windows.length; j++) {
+                    WindowStateProto windowProto = windowTokenProto.windows[j];
+                    WindowState window = new WindowState(windowProto);
+                    mSubWindows.add(window);
+                    mSubWindows.addAll(window.getWindows());
+                }
+            }
+            mTempInsetBounds = extract(proto.tempInsetBounds);
+        }
+    }
+
+    static class ConfigurationContainer {
+        final Configuration mOverrideConfiguration = new Configuration();
+        final Configuration mFullConfiguration = new Configuration();
+        final Configuration mMergedOverrideConfiguration = new Configuration();
+
+        ConfigurationContainer(ConfigurationContainerProto proto) {
+            if (proto == null) {
+                return;
+            }
+            mOverrideConfiguration.setTo(extract(proto.overrideConfiguration));
+            mFullConfiguration.setTo(extract(proto.fullConfiguration));
+            mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration));
+        }
+
+        int getWindowingMode() {
+            if (mFullConfiguration == null) {
+                return WINDOWING_MODE_UNDEFINED;
+            }
+            return mFullConfiguration.windowConfiguration.getWindowingMode();
+        }
+
+        int getActivityType() {
+            if (mFullConfiguration == null) {
+                return ACTIVITY_TYPE_UNDEFINED;
+            }
+            return mFullConfiguration.windowConfiguration.getActivityType();
+        }
+    }
+
+    static abstract class WindowContainer extends ConfigurationContainer {
+
+        protected boolean mFullscreen;
+        protected Rect mBounds;
+        protected int mOrientation;
+        protected List<WindowState> mSubWindows = new ArrayList<>();
+
+        WindowContainer(WindowContainerProto proto) {
+            super(proto.configurationContainer);
+            mOrientation = proto.orientation;
+        }
+
+        Rect getBounds() {
+            return mBounds;
+        }
+
+        boolean isFullscreen() {
+            return mFullscreen;
+        }
+
+        List<WindowState> getWindows() {
+            return mSubWindows;
+        }
+    }
+
+    static class Display extends WindowContainer {
+
+        private final int mDisplayId;
+        private Rect mDisplayRect = new Rect();
+        private Rect mAppRect = new Rect();
+        private int mDpi;
+        private Rect mStableBounds;
+        private String mName;
+
+        public Display(DisplayProto proto) {
+            super(proto.windowContainer);
+            mDisplayId = proto.id;
+            for (int i = 0; i < proto.aboveAppWindows.length; i++) {
+                addWindowsFromTokenProto(proto.aboveAppWindows[i]);
+            }
+            for (int i = 0; i < proto.belowAppWindows.length; i++) {
+                addWindowsFromTokenProto(proto.belowAppWindows[i]);
+            }
+            for (int i = 0; i < proto.imeWindows.length; i++) {
+                addWindowsFromTokenProto(proto.imeWindows[i]);
+            }
+            mDpi = proto.dpi;
+            DisplayInfoProto infoProto = proto.displayInfo;
+            if (infoProto != null) {
+                mDisplayRect.set(0, 0, infoProto.logicalWidth, infoProto.logicalHeight);
+                mAppRect.set(0, 0, infoProto.appWidth, infoProto.appHeight);
+                mName = infoProto.name;
+            }
+            final DisplayFramesProto displayFramesProto = proto.displayFrames;
+            if (displayFramesProto != null) {
+                mStableBounds = extract(displayFramesProto.stableBounds);
+            }
+        }
+
+        private void addWindowsFromTokenProto(WindowTokenProto proto) {
+            for (int j = 0; j < proto.windows.length; j++) {
+                WindowStateProto windowProto = proto.windows[j];
+                WindowState childWindow = new WindowState(windowProto);
+                mSubWindows.add(childWindow);
+                mSubWindows.addAll(childWindow.getWindows());
+            }
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        int getDpi() {
+            return mDpi;
+        }
+
+        Rect getDisplayRect() {
+            return mDisplayRect;
+        }
+
+        Rect getAppRect() {
+            return mAppRect;
+        }
+
+        String getName() {
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "Display #" + mDisplayId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
+                    + " mAppRect=" + mAppRect;
+        }
+    }
+
+    public static class WindowState extends WindowContainer {
+
+        private static final int WINDOW_TYPE_NORMAL = 0;
+        private static final int WINDOW_TYPE_STARTING = 1;
+        private static final int WINDOW_TYPE_EXITING = 2;
+        private static final int WINDOW_TYPE_DEBUGGER = 3;
+
+        private String mName;
+        private final String mAppToken;
+        private final int mWindowType;
+        private int mType = 0;
+        private int mDisplayId;
+        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 mSurfaceInsets = new Rect();
+        private Rect mContentInsets = new Rect();
+        private Rect mGivenContentInsets = new Rect();
+        private Rect mCrop = new Rect();
+
+        WindowState(WindowStateProto proto) {
+            super(proto.windowContainer);
+            IdentifierProto identifierProto = proto.identifier;
+            mName = identifierProto.title;
+            mAppToken = Integer.toHexString(identifierProto.hashCode);
+            mDisplayId = proto.displayId;
+            mStackId = proto.stackId;
+            if (proto.attributes != null) {
+                mType = proto.attributes.type;
+            }
+            WindowStateAnimatorProto animatorProto = proto.animator;
+            if (animatorProto != null) {
+                if (animatorProto.surface != null) {
+                    WindowSurfaceControllerProto surfaceProto = animatorProto.surface;
+                    mShown = surfaceProto.shown;
+                    mLayer = surfaceProto.layer;
+                }
+                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);
+            mSurfaceInsets = extract(proto.surfaceInsets);
+            if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
+                mWindowType = WINDOW_TYPE_STARTING;
+                // Existing code depends on the prefix being removed
+                mName = mName.substring(STARTING_WINDOW_PREFIX.length());
+            } else if (proto.animatingExit) {
+                mWindowType = WINDOW_TYPE_EXITING;
+            } else if (mName.startsWith(DEBUGGER_WINDOW_PREFIX)) {
+                mWindowType = WINDOW_TYPE_STARTING;
+                mName = mName.substring(DEBUGGER_WINDOW_PREFIX.length());
+            } else {
+                mWindowType = 0;
+            }
+            for (int i = 0; i < proto.childWindows.length; i++) {
+                WindowStateProto childProto = proto.childWindows[i];
+                WindowState childWindow = new WindowState(childProto);
+                mSubWindows.add(childWindow);
+                mSubWindows.addAll(childWindow.getWindows());
+            }
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        String getToken() {
+            return mAppToken;
+        }
+
+        boolean isStartingWindow() {
+            return mWindowType == WINDOW_TYPE_STARTING;
+        }
+
+        boolean isExitingWindow() {
+            return mWindowType == WINDOW_TYPE_EXITING;
+        }
+
+        boolean isDebuggerWindow() {
+            return mWindowType == WINDOW_TYPE_DEBUGGER;
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        int getStackId() {
+            return mStackId;
+        }
+
+        Rect getContainingFrame() {
+            return mContainingFrame;
+        }
+
+        public Rect getFrame() {
+            return mFrame;
+        }
+
+        Rect getSurfaceInsets() {
+            return mSurfaceInsets;
+        }
+
+        Rect getContentInsets() {
+            return mContentInsets;
+        }
+
+        Rect getGivenContentInsets() {
+            return mGivenContentInsets;
+        }
+
+        public Rect getContentFrame() {
+            return mContentFrame;
+        }
+
+        Rect getParentFrame() {
+            return mParentFrame;
+        }
+
+        Rect getCrop() {
+            return mCrop;
+        }
+
+        public boolean isShown() {
+            return mShown;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        private String getWindowTypeSuffix(int windowType) {
+            switch (windowType) {
+                case WINDOW_TYPE_STARTING:
+                    return " STARTING";
+                case WINDOW_TYPE_EXITING:
+                    return " EXITING";
+                case WINDOW_TYPE_DEBUGGER:
+                    return " DEBUGGER";
+                default:
+                    break;
+            }
+            return "";
+        }
+
+        @Override
+        public String toString() {
+            return "WindowState: {" + mAppToken + " " + mName
+                    + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
+                    + " cf=" + mContainingFrame + " pf=" + mParentFrame;
+        }
+    }
+}
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
new file mode 100644
index 0000000..9877209
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
@@ -0,0 +1,164 @@
+package android.server.am.settings;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings.SettingNotFoundException;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class to save, set, and restore global system-level preferences.
+ * <p>
+ * To use this class, testing APK must be self-instrumented and have
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}.
+ * <p>
+ * A test that changes system-level preferences can be written easily and reliably.
+ * <pre>
+ * static class PrefSession extends SettingsSession<String> {
+ *     PrefSession() {
+ *         super(android.provider.Settings.Secure.getUriFor(
+ *                       android.provider.Settings.Secure.PREFERENCE_KEY),
+ *               android.provider.Settings.Secure::getString,
+ *               android.provider.Settings.Secure::putString);
+ *     }
+ * }
+ *
+ * @Test
+ * public void doTest() throws Exception {
+ *     try (final PrefSession prefSession = new PrefSession()) {
+ *         prefSession.set("value 1");
+ *         doTest1();
+ *         prefSession.set("value 2");
+ *         doTest2();
+ *     }
+ * }
+ * </pre>
+ */
+public class SettingsSession<T> implements AutoCloseable {
+    private static final String TAG = SettingsSession.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    @FunctionalInterface
+    public interface SettingsGetter<T> {
+        T get(ContentResolver cr, String key) throws SettingNotFoundException;
+    }
+
+    @FunctionalInterface
+    public interface SettingsSetter<T> {
+        void set(ContentResolver cr, String key, T value);
+    }
+
+    /**
+     * To debug to detect nested sessions for the same key. Enabled when {@link #DEBUG} is true.
+     * Note that nested sessions can be merged into one session.
+     */
+    private static final SessionCounters sSessionCounters = new SessionCounters();
+
+    private final Uri mUri;
+    private final SettingsGetter<T> mGetter;
+    private final SettingsSetter<T> mSetter;
+    private final boolean mHasInitialValue;
+    private final T mInitialValue;
+
+    public SettingsSession(final Uri uri, final SettingsGetter<T> getter,
+            final SettingsSetter<T> setter) {
+        mUri = uri;
+        mGetter = getter;
+        mSetter = setter;
+        T initialValue;
+        boolean hasInitialValue;
+        try {
+            initialValue = get(uri, getter);
+            hasInitialValue = true;
+        } catch (SettingNotFoundException e) {
+            initialValue = null;
+            hasInitialValue = false;
+        }
+        mInitialValue = initialValue;
+        mHasInitialValue = hasInitialValue;
+        if (DEBUG) {
+            Log.i(TAG, "start: uri=" + uri
+                    + (mHasInitialValue ? " value=" + mInitialValue : " undefined"));
+            sSessionCounters.open(uri);
+        }
+    }
+
+    public void set(final @NonNull T value) throws Exception {
+        put(mUri, mSetter, value);
+        if (DEBUG) {
+            Log.i(TAG, "  set: uri=" + mUri + " value=" + value);
+        }
+    }
+
+    public T get() throws SettingNotFoundException {
+        return get(mUri, mGetter);
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (mHasInitialValue) {
+            put(mUri, mSetter, mInitialValue);
+            if (DEBUG) {
+                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);
+            }
+        }
+        if (DEBUG) {
+            sSessionCounters.close(mUri);
+        }
+    }
+
+    private static <T> void put(final Uri uri, final SettingsSetter<T> setter, T value)
+            throws SettingNotFoundException {
+        setter.set(getContentResolver(), uri.getLastPathSegment(), value);
+    }
+
+    private static <T> T get(final Uri uri, final SettingsGetter<T> getter)
+            throws SettingNotFoundException {
+        return getter.get(getContentResolver(), uri.getLastPathSegment());
+    }
+
+    private static void delete(final Uri uri) throws IllegalArgumentException {
+        getContentResolver().delete(uri, null, null);
+    }
+
+    private static ContentResolver getContentResolver() {
+        return InstrumentationRegistry.getTargetContext().getContentResolver();
+    }
+
+    private static class SessionCounters {
+        private final Map<Uri, Integer> mOpenSessions = new HashMap<>();
+
+        void open(final Uri uri) {
+            final Integer count = mOpenSessions.get(uri);
+            if (count == null) {
+                mOpenSessions.put(uri, 1);
+                return;
+            }
+            mOpenSessions.put(uri, count + 1);
+            Log.w(TAG, "Open nested session for " + uri, new Throwable());
+        }
+
+        void close(final Uri uri) {
+            final int count = mOpenSessions.get(uri);
+            if (count == 1) {
+                mOpenSessions.remove(uri);
+                return;
+            }
+            mOpenSessions.put(uri, count - 1);
+            Log.w(TAG, "Close nested session for " + uri, new Throwable());
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/Android.mk b/tests/framework/base/windowmanager/Android.mk
new file mode 100644
index 0000000..af34c5d
--- /dev/null
+++ b/tests/framework/base/windowmanager/Android.mk
@@ -0,0 +1,44 @@
+#
+# 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 optional
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, alertwindowservice/src) \
+    $(call all-named-files-under,Components.java, alertwindowapp) \
+    $(call all-named-files-under,Components.java, alertwindowappsdk25)
+
+LOCAL_PACKAGE_NAME := CtsWindowManagerDeviceTestCases
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    android-support-test \
+    platform-test-annotations \
+    cts-amwm-util
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
new file mode 100644
index 0000000..6e57292
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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.server.cts.wm">
+
+    <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" />
+
+    <application android:label="CtsWindowManagerDeviceTestCases">
+        <uses-library android:name="android.test.runner"/>
+
+        <service
+            android:name="android.server.wm.TestLogService"
+            android:enabled="true"
+            android:exported="true">
+        </service>
+
+        <activity android:name="android.server.wm.AlertWindowsAppOpsTestsActivity"/>
+        <activity android:name="android.server.wm.DialogFrameTestActivity" />
+
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.server.cts.wm"
+                     android:label="CTS tests of WindowManager">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
new file mode 100644
index 0000000..624d4e6
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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 WindowManager 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="CtsWindowManagerDeviceTestCases.apk"/>
+        <option name="test-file-name" value="CtsDragAndDropSourceApp.apk"/>
+        <option name="test-file-name" value="CtsDragAndDropTargetApp.apk"/>
+        <option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk"/>
+        <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk"/>
+        <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk"/>
+        <option name="test-file-name" value="CtsAlertWindowService.apk"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.cts.wm"/>
+        <option name="runtime-hint" value="8m"/>
+    </test>
+
+</configuration>
diff --git a/tests/framework/base/windowmanager/alertwindowapp/Android.mk b/tests/framework/base/windowmanager/alertwindowapp/Android.mk
new file mode 100644
index 0000000..d15d4ab
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/Android.mk
@@ -0,0 +1,35 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../alertwindowappsdk25/src) \
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml
new file mode 100755
index 0000000..446e2fa
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="android.server.wm.alertwindowapp">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application android:label="CtsAlertWindow">
+        <activity android:name=".AlertWindowTestActivity"
+                  android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java
new file mode 100644
index 0000000..2185dd6
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowapp;
+
+import android.os.Bundle;
+import android.server.wm.alertwindowappsdk25.AlertWindowTestBaseActivity;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
+    private static final int[] ALERT_WINDOW_TYPES = {
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY,
+            TYPE_APPLICATION_OVERLAY
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        createAllAlertWindows(getPackageName());
+    }
+
+    @Override
+    protected int[] getAlertWindowTypes() {
+        return ALERT_WINDOW_TYPES;
+    }
+}
diff --git a/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/Components.java b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/Components.java
new file mode 100644
index 0000000..d41863b
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/Components.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.server.wm.alertwindowapp;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName ALERT_WINDOW_TEST_ACTIVITY =
+            component(Components.class, "AlertWindowTestActivity");
+}
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
new file mode 100644
index 0000000..2786352
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
@@ -0,0 +1,34 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := 25
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestAppSdk25
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml
new file mode 100755
index 0000000..0ac1c91
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.server.wm.alertwindowappsdk25">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application android:label="CtsAlertWindowSdk25">
+        <activity android:name=".AlertWindowTestActivitySdk25"
+                  android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
new file mode 100644
index 0000000..47057f1
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowappsdk25;
+
+import android.os.Bundle;
+
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
+    private static final int[] ALERT_WINDOW_TYPES = {
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        createAllAlertWindows(getPackageName());
+    }
+
+    @Override
+    protected int[] getAlertWindowTypes() {
+        return ALERT_WINDOW_TYPES;
+    }
+}
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java
new file mode 100644
index 0000000..021b886
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowappsdk25;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public abstract class AlertWindowTestBaseActivity extends Activity {
+
+    protected void createAllAlertWindows(String windowName) {
+        final int[] alertWindowTypes = getAlertWindowTypes();
+        for (int type : alertWindowTypes) {
+            try {
+                createAlertWindow(type, windowName);
+            } catch (Exception e) {
+                Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
+            }
+        }
+    }
+
+    protected void createAlertWindow(int type) {
+        createAlertWindow(type, getPackageName());
+    }
+
+    protected void createAlertWindow(int type, String windowName) {
+        if (!isSystemAlertWindowType(type)) {
+            throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
+        }
+
+        final Point size = new Point();
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.getDefaultDisplay().getSize(size);
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+        params.width = size.x / 3;
+        params.height = size.y / 3;
+        params.gravity = TOP | LEFT;
+        params.setTitle(windowName);
+
+        final TextView view = new TextView(this);
+        view.setText(windowName + "   type=" + type);
+        view.setBackgroundColor(Color.RED);
+        wm.addView(view, params);
+    }
+
+    private boolean isSystemAlertWindowType(int type) {
+        final int[] alertWindowTypes = getAlertWindowTypes();
+        for (int current : alertWindowTypes) {
+            if (current == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected abstract int[] getAlertWindowTypes();
+}
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/Components.java b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/Components.java
new file mode 100644
index 0000000..a70566a
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/Components.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.server.wm.alertwindowappsdk25;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName SDK25_ALERT_WINDOW_TEST_ACTIVITY =
+            component(Components.class, "AlertWindowTestActivitySdk25");
+}
diff --git a/tests/framework/base/windowmanager/alertwindowservice/Android.mk b/tests/framework/base/windowmanager/alertwindowservice/Android.mk
new file mode 100644
index 0000000..c6dcbe5
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/Android.mk
@@ -0,0 +1,37 @@
+# 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 := \
+    cts-am-app-base \
+    compatibility-device-util \
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAlertWindowService
+
+# 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/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml
new file mode 100644
index 0000000..76c30bd
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.server.wm.alertwindowservice">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <service android:name=".AlertWindowService"
+                 android:exported="true"/>
+    </application>
+</manifest>
diff --git a/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java
new file mode 100644
index 0000000..f284fff
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowservice;
+
+import static android.graphics.Color.BLUE;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.util.LinkedList;
+
+/** Service for creating and managing alert windows. */
+public final class AlertWindowService extends Service {
+
+    private static final String TAG = "AlertWindowService";
+    private static final boolean DEBUG = false;
+
+    public static final String EXTRA_MESSENGER = "messenger";
+
+    public static final int MSG_ADD_ALERT_WINDOW = 1;
+    public static final int MSG_REMOVE_ALERT_WINDOW = 2;
+    public static final int MSG_REMOVE_ALL_ALERT_WINDOWS = 3;
+
+    public static final int MSG_ON_ALERT_WINDOW_ADDED = 4;
+    public static final int MSG_ON_ALERT_WINDOW_REMOVED = 5;
+
+    private LinkedList<View> mAlertWindows = new LinkedList<>();
+
+    private Messenger mOutgoingMessenger = null;
+    private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
+
+    private class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ADD_ALERT_WINDOW:
+                    addAlertWindow();
+                    break;
+                case MSG_REMOVE_ALERT_WINDOW:
+                    removeAlertWindow();
+                    break;
+                case MSG_REMOVE_ALL_ALERT_WINDOWS:
+                    removeAllAlertWindows();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    private void addAlertWindow() {
+        final Point size = new Point();
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.getDefaultDisplay().getSize(size);
+
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+        params.width = size.x / 3;
+        params.height = size.y / 3;
+        params.gravity = TOP | LEFT;
+
+        final TextView view = new TextView(this);
+        view.setText("AlertWindowService" + mAlertWindows.size());
+        view.setBackgroundColor(BLUE);
+        wm.addView(view, params);
+        mAlertWindows.add(view);
+
+        if (DEBUG) Log.e(TAG, "addAlertWindow " + mAlertWindows.size());
+        if (mOutgoingMessenger != null) {
+            try {
+                mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_ADDED));
+            } catch (RemoteException e) {
+
+            }
+        }
+    }
+
+    private void removeAlertWindow() {
+        if (mAlertWindows.size() == 0) {
+            return;
+        }
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.removeView(mAlertWindows.pop());
+
+        if (DEBUG) Log.e(TAG, "removeAlertWindow " + mAlertWindows.size());
+        if (mOutgoingMessenger != null) {
+            try {
+                mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_REMOVED));
+            } catch (RemoteException e) {
+
+            }
+        }
+    }
+
+    private void removeAllAlertWindows() {
+        while (mAlertWindows.size() > 0) {
+            removeAlertWindow();
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (DEBUG) Log.e(TAG, "onBind");
+        mOutgoingMessenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+        return mIncomingMessenger.getBinder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (DEBUG) Log.e(TAG, "onUnbind");
+        removeAllAlertWindows();
+        return super.onUnbind(intent);
+    }
+}
diff --git a/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/Components.java b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/Components.java
new file mode 100644
index 0000000..a6e74a6
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/Components.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.server.wm.alertwindowservice;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName ALERT_WINDOW_SERVICE =
+            component(Components.class, "AlertWindowService");
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/Android.mk b/tests/framework/base/windowmanager/dndsourceapp/Android.mk
new file mode 100644
index 0000000..7cfd03f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
new file mode 100644
index 0000000..4c8f0bb
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.server.wm.dndsourceapp">
+    <application android:label="CtsDnDSource">
+        <activity android:name="android.server.wm.dndsourceapp.DragSource">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <provider android:name="android.server.wm.dndsourceapp.DragSourceContentProvider"
+                  android:authorities="android.server.wm.dndsource.contentprovider"
+                  android:grantUriPermissions="true"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/res/layout/source_activity.xml b/tests/framework/base/windowmanager/dndsourceapp/res/layout/source_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/res/layout/source_activity.xml
rename to tests/framework/base/windowmanager/dndsourceapp/res/layout/source_activity.xml
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java
new file mode 100644
index 0000000..9c7ed7b
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dndsourceapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.FileUriExposedException;
+import android.os.PersistableBundle;
+import android.server.wm.TestLogClient;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import java.io.File;
+
+public class DragSource extends Activity{
+    private static final String RESULT_KEY_START_DRAG = "START_DRAG";
+    private static final String RESULT_KEY_DETAILS = "DETAILS";
+    private static final String RESULT_OK = "OK";
+    private static final String RESULT_EXCEPTION = "Exception";
+
+    private static final String URI_PREFIX =
+            "content://" + DragSourceContentProvider.AUTHORITY + "/data";
+
+    private static final String MAGIC_VALUE = "42";
+    private static final long TIMEOUT_CANCEL = 150;
+
+    private TextView mTextView;
+    private TestLogClient mLogClient;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+
+        View view = getLayoutInflater().inflate(R.layout.source_activity, null);
+        setContentView(view);
+
+        final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
+
+        setUpDragSource("disallow_global", plainUri, 0);
+        setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
+
+        setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
+        setUpDragSource("grant_read", plainUri,
+                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+        setUpDragSource("grant_write", plainUri,
+                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_WRITE);
+        setUpDragSource("grant_read_persistable", plainUri,
+                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+                        View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION);
+
+        final Uri prefixUri = Uri.parse(URI_PREFIX);
+
+        setUpDragSource("grant_read_prefix", prefixUri,
+                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+                        View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
+        setUpDragSource("grant_read_noprefix", prefixUri,
+                View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+
+        final Uri fileUri = Uri.fromFile(new File("/sdcard/sample.jpg"));
+
+        setUpDragSource("file_local", fileUri, 0);
+        setUpDragSource("file_global", fileUri, View.DRAG_FLAG_GLOBAL);
+    }
+
+    private void setUpDragSource(String mode, final Uri uri, final int flags) {
+        if (!mode.equals(getIntent().getStringExtra("mode"))) {
+            return;
+        }
+        mTextView = (TextView) findViewById(R.id.drag_source);
+        mTextView.setText(mode);
+        mTextView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() != MotionEvent.ACTION_DOWN) {
+                    return false;
+                }
+                try {
+                    final ClipDescription clipDescription = new ClipDescription("", new String[] {
+                            ClipDescription.MIMETYPE_TEXT_URILIST });
+                    PersistableBundle extras = new PersistableBundle(1);
+                    extras.putString("extraKey", "extraValue");
+                    clipDescription.setExtras(extras);
+                    final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri));
+                    v.startDragAndDrop(
+                            clipData,
+                            new View.DragShadowBuilder(v),
+                            null,
+                            flags);
+                    logResult(RESULT_KEY_START_DRAG, RESULT_OK);
+                } catch (FileUriExposedException e) {
+                    logResult(RESULT_KEY_DETAILS, e.getMessage());
+                    logResult(RESULT_KEY_START_DRAG, RESULT_EXCEPTION);
+                }
+                if (mode.equals("cancel_soon")) {
+                    new Handler().postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            v.cancelDragAndDrop();
+                        }
+                    }, TIMEOUT_CANCEL);
+                }
+                return true;
+            }
+        });
+    }
+
+    private void logResult(String key, String value) {
+        mLogClient.record(key, value);
+        mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+    }
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java
new file mode 100644
index 0000000..1ec1e58
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndsourceapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class DragSourceContentProvider extends ContentProvider {
+
+    public static final String AUTHORITY = "android.server.wm.dndsource.contentprovider";
+
+    private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    private static final int URI_DATA = 1;
+
+    static {
+        sMatcher.addURI(AUTHORITY, "data/#", URI_DATA);
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                        String sortOrder) {
+        switch (sMatcher.match(uri)) {
+            case URI_DATA:
+                return new DragSourceCursor(uri.getLastPathSegment());
+        }
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java
new file mode 100644
index 0000000..468842f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dndsourceapp;
+
+import android.database.AbstractCursor;
+
+public class DragSourceCursor extends AbstractCursor {
+    private static final String COLUMN_KEY = "key";
+
+    private final String mValue;
+
+    public DragSourceCursor(String value) {
+        mValue = value;
+    }
+
+    @Override
+    public int getCount() {
+        return 1;
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return new String[] {COLUMN_KEY};
+    }
+
+    @Override
+    public String getString(int column) {
+        if (getPosition() != 0) {
+            throw new IllegalArgumentException("Incorrect position: " + getPosition());
+        }
+        if (column != 0) {
+            throw new IllegalArgumentException("Incorrect column: " + column);
+        }
+        return mValue;
+    }
+
+    @Override
+    public short getShort(int column) {
+        return 0;
+    }
+
+    @Override
+    public int getInt(int column) {
+        return 0;
+    }
+
+    @Override
+    public long getLong(int column) {
+        return 0;
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return 0;
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return 0;
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return false;
+    }
+}
diff --git a/tests/framework/base/windowmanager/dndtargetapp/Android.mk b/tests/framework/base/windowmanager/dndtargetapp/Android.mk
new file mode 100644
index 0000000..d291df4
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
new file mode 100644
index 0000000..33c7a0f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.server.wm.dndtargetapp">
+    <application android:label="CtsDnDTarget">
+        <activity android:name="android.server.wm.dndtargetapp.DropTarget">
+            <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/services/activityandwindowmanager/windowmanager/dndtargetapp/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/res/layout/target_activity.xml
rename to tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
diff --git a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
new file mode 100644
index 0000000..0500ef4
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dndtargetapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.server.wm.TestLogClient;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class DropTarget extends Activity {
+    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+    private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
+    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+    private static final String RESULT_KEY_DROP_RESULT = "DROP";
+    private static final String RESULT_KEY_DETAILS = "DETAILS";
+    private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
+    private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
+    private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
+    private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
+    private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
+
+    public static final String RESULT_OK = "OK";
+    public static final String RESULT_EXCEPTION = "Exception";
+    public static final String RESULT_MISSING = "MISSING";
+    public static final String RESULT_LEAKING = "LEAKING";
+
+    protected static final String MAGIC_VALUE = "42";
+
+    private TextView mTextView;
+    private TestLogClient mLogClient;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+
+        View view = getLayoutInflater().inflate(R.layout.target_activity, null);
+        setContentView(view);
+
+        setUpDropTarget("request_none", new OnDragUriReadListener(false));
+        setUpDropTarget("request_read", new OnDragUriReadListener());
+        setUpDropTarget("request_write", new OnDragUriWriteListener());
+        setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
+        setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
+    }
+
+    private void setUpDropTarget(String mode, OnDragUriListener listener) {
+        if (!mode.equals(getIntent().getStringExtra("mode"))) {
+            return;
+        }
+        mTextView = (TextView)findViewById(R.id.drag_target);
+        mTextView.setText(mode);
+        mTextView.setOnDragListener(listener);
+    }
+
+    private String checkExtraValue(DragEvent event) {
+        PersistableBundle extras = event.getClipDescription().getExtras();
+        if (extras == null) {
+            return "Null";
+        }
+
+        final String value = extras.getString("extraKey");
+        if ("extraValue".equals(value)) {
+            return RESULT_OK;
+        }
+        return value;
+    }
+
+    private void logResult(String key, String value) {
+        mLogClient.record(key, value);
+        mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+    }
+
+    private abstract class OnDragUriListener implements View.OnDragListener {
+        private final boolean requestPermissions;
+
+        public OnDragUriListener(boolean requestPermissions) {
+            this.requestPermissions = requestPermissions;
+        }
+
+        @Override
+        public boolean onDrag(View v, DragEvent event) {
+            checkDragEvent(event);
+
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+                    logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENTERED:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_EXITED:
+                    return true;
+
+                case DragEvent.ACTION_DROP:
+                    // Try accessing the Uri without the permissions grant.
+                    accessContent(event, RESULT_KEY_ACCESS_BEFORE, false);
+
+                    // Try accessing the Uri with the permission grant (if required);
+                    accessContent(event, RESULT_KEY_DROP_RESULT, requestPermissions);
+
+                    // Try accessing the Uri after the permissions have been released.
+                    accessContent(event, RESULT_KEY_ACCESS_AFTER, false);
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENDED:
+                    logResult(RESULT_KEY_DRAG_ENDED, RESULT_OK);
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+
+        private void accessContent(DragEvent event, String resultKey, boolean requestPermissions) {
+            String result;
+            try {
+                result = processDrop(event, requestPermissions);
+            } catch (SecurityException e) {
+                result = RESULT_EXCEPTION;
+                if (resultKey.equals(RESULT_KEY_DROP_RESULT)) {
+                    logResult(RESULT_KEY_DETAILS, e.getMessage());
+                }
+            }
+            logResult(resultKey, result);
+        }
+
+        private String processDrop(DragEvent event, boolean requestPermissions) {
+            final ClipData clipData = event.getClipData();
+            if (clipData == null) {
+                return "Null ClipData";
+            }
+            if (clipData.getItemCount() == 0) {
+                return "Empty ClipData";
+            }
+            ClipData.Item item = clipData.getItemAt(0);
+            if (item == null) {
+                return "Null ClipData.Item";
+            }
+            Uri uri = item.getUri();
+            if (uri == null) {
+                return "Null Uri";
+            }
+
+            DragAndDropPermissions permissions = null;
+            if (requestPermissions) {
+                permissions = requestDragAndDropPermissions(event);
+                if (permissions == null) {
+                    return "Null DragAndDropPermissions";
+                }
+            }
+
+            try {
+                return processUri(uri);
+            } finally {
+                if (permissions != null) {
+                    permissions.release();
+                }
+            }
+        }
+
+        abstract protected String processUri(Uri uri);
+    }
+
+    private void checkDragEvent(DragEvent event) {
+        final int action = event.getAction();
+
+        // ClipData should be available for ACTION_DROP only.
+        final ClipData clipData = event.getClipData();
+        if (action == DragEvent.ACTION_DROP) {
+            if (clipData == null) {
+                logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
+            }
+        } else {
+            if (clipData != null) {
+                logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_LEAKING + action);
+            }
+        }
+
+        // ClipDescription should be always available except for ACTION_DRAG_ENDED.
+        final ClipDescription clipDescription = event.getClipDescription();
+        if (action != DragEvent.ACTION_DRAG_ENDED) {
+            if (clipDescription == null) {
+                logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING + action);
+            }
+        } else {
+            if (clipDescription != null) {
+                logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_LEAKING);
+            }
+        }
+
+        // Local state should be always null for cross-app drags.
+        final Object localState = event.getLocalState();
+        if (localState != null) {
+            logResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_LEAKING + action);
+        }
+    }
+
+    private class OnDragUriReadListener extends OnDragUriListener {
+        OnDragUriReadListener(boolean requestPermissions) {
+            super(requestPermissions);
+        }
+
+        OnDragUriReadListener() {
+            super(true);
+        }
+
+        protected String processUri(Uri uri) {
+            return checkQueryResult(uri, MAGIC_VALUE);
+        }
+
+        protected String checkQueryResult(Uri uri, String expectedValue) {
+            Cursor cursor = null;
+            try {
+                cursor = getContentResolver().query(uri, null, null, null, null);
+                if (cursor == null) {
+                    return "Null Cursor";
+                }
+                cursor.moveToPosition(0);
+                String value = cursor.getString(0);
+                if (!expectedValue.equals(value)) {
+                    return "Wrong value: " + value;
+                }
+                return RESULT_OK;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    private class OnDragUriWriteListener extends OnDragUriListener {
+        OnDragUriWriteListener() {
+            super(true);
+        }
+
+        protected String processUri(Uri uri) {
+            ContentValues values = new ContentValues();
+            values.put("key", 100);
+            getContentResolver().update(uri, values, null, null);
+            return RESULT_OK;
+        }
+    }
+
+    private class OnDragUriReadPrefixListener extends OnDragUriReadListener {
+        @Override
+        protected String processUri(Uri uri) {
+            final String result1 = queryPrefixed(uri, "1");
+            if (!result1.equals(RESULT_OK)) {
+                return result1;
+            }
+            final String result2 = queryPrefixed(uri, "2");
+            if (!result2.equals(RESULT_OK)) {
+                return result2;
+            }
+            return queryPrefixed(uri, "3");
+        }
+
+        private String queryPrefixed(Uri uri, String selector) {
+            final Uri prefixedUri = Uri.parse(uri.toString() + "/" + selector);
+            return checkQueryResult(prefixedUri, selector);
+        }
+    }
+
+    private class OnDragUriTakePersistableListener extends OnDragUriListener {
+        OnDragUriTakePersistableListener() {
+            super(true);
+        }
+
+        @Override
+        protected String processUri(Uri uri) {
+            getContentResolver().takePersistableUriPermission(
+                    uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+            getContentResolver().releasePersistableUriPermission(
+                    uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+            return RESULT_OK;
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk
new file mode 100644
index 0000000..59f4ab1
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := 23
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetAppSdk23
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
new file mode 100644
index 0000000..d10a548
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.server.wm.dndtargetappsdk23">
+    <application android:label="CtsDnDTarget">
+        <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget">
+            <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/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
rename to tests/framework/base/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java b/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java
new file mode 100644
index 0000000..93a8659
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.dndtargetappsdk23;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.server.wm.TestLogClient;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * This application is compiled against SDK 23 and used to verify that apps targeting SDK 23 and
+ * below do not receive global drags.
+ */
+public class DropTarget extends Activity {
+    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+    private static final String RESULT_KEY_DROP_RESULT = "DROP";
+
+    public static final String RESULT_OK = "OK";
+
+    private TextView mTextView;
+    private TestLogClient mLogClient;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        View view = getLayoutInflater().inflate(R.layout.target_activity, null);
+        setContentView(view);
+
+        mTextView = (TextView) findViewById(R.id.drag_target);
+        mTextView.setOnDragListener(new OnDragListener());
+
+        mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+    }
+
+    private void logResult(String key, String value) {
+        mLogClient.record(key, value);
+        mTextView.setText(key + "=" + value);
+    }
+
+    private class OnDragListener implements View.OnDragListener {
+        @Override
+        public boolean onDrag(View v, DragEvent event) {
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENTERED:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_EXITED:
+                    return true;
+
+                case DragEvent.ACTION_DROP:
+                    logResult(RESULT_KEY_DROP_RESULT, RESULT_OK);
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENDED:
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
new file mode 100644
index 0000000..166037f
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.AppOpsManager;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.AppOpsUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+
+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.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Test whether system alert window properly interacts with app ops.
+ *
+ * Build/Install/Run: atest CtsWindowManagerDeviceTestCases:AlertWindowsAppOpsTests
+ */
+@Presubmit
+@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
+@RunWith(AndroidJUnit4.class)
+public class AlertWindowsAppOpsTests {
+    private static final long APP_OP_CHANGE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
+
+    @Rule
+    public final ActivityTestRule<AlertWindowsAppOpsTestsActivity> mActivityRule =
+            new ActivityTestRule<>(AlertWindowsAppOpsTestsActivity.class);
+
+    @BeforeClass
+    public static void grantSystemAlertWindowAccess() throws IOException {
+        AppOpsUtils.setOpMode(getContext().getPackageName(),
+                OPSTR_SYSTEM_ALERT_WINDOW, MODE_ALLOWED);
+    }
+
+    @AfterClass
+    public static void revokeSystemAlertWindowAccess() throws IOException {
+        AppOpsUtils.setOpMode(getContext().getPackageName(),
+                OPSTR_SYSTEM_ALERT_WINDOW, MODE_ERRORED);
+    }
+
+    @Test
+    public void testSystemAlertWindowAppOpsInitiallyAllowed() {
+        final String packageName = getContext().getPackageName();
+        final int uid = Process.myUid();
+
+        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        final AppOpsManager.OnOpActiveChangedListener listener = mock(
+                AppOpsManager.OnOpActiveChangedListener.class);
+
+        // Launch our activity.
+        final AlertWindowsAppOpsTestsActivity activity = mActivityRule.getActivity();
+
+        // Start watching for app op
+        appOpsManager.startWatchingActive(new int[] {OP_SYSTEM_ALERT_WINDOW}, listener);
+
+        // Assert the app op is not started
+        assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+
+
+        // Show a system alert window.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                activity::showSystemAlertWindow);
+
+        // The app op should start
+        verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                eq(uid), eq(packageName), eq(true));
+
+        // The app op should be reported as started
+        assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW,
+                uid, packageName));
+
+
+        // Start with a clean slate
+        reset(listener);
+
+        // Hide a system alert window.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                activity::hideSystemAlertWindow);
+
+        // The app op should finish
+        verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                eq(uid), eq(packageName), eq(false));
+
+        // The app op should be reported as finished
+        assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+
+
+        // Start with a clean slate
+        reset(listener);
+
+        // Stop watching for app op
+        appOpsManager.stopWatchingActive(listener);
+
+        // Show a system alert window
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                activity::showSystemAlertWindow);
+
+        // No other callbacks expected
+        verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS).times(0))
+                .onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                        anyInt(), anyString(), anyBoolean());
+
+        // The app op should be reported as started
+        assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java
new file mode 100644
index 0000000..4a9decc
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.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.server.wm;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.WindowManager;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+public class AlertWindowsAppOpsTestsActivity extends Activity {
+    private View mContent;
+
+    public void showSystemAlertWindow() {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                TYPE_APPLICATION_OVERLAY);
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.height = WindowManager.LayoutParams.MATCH_PARENT;
+        mContent = new View(this);
+        getWindowManager().addView(mContent, params);
+    }
+
+    public void hideSystemAlertWindow() {
+        getWindowManager().removeView(mContent);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
new file mode 100644
index 0000000..2da6bf0
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm;
+
+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.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.server.wm.alertwindowappsdk25.Components.SDK25_ALERT_WINDOW_TEST_ACTIVITY;
+import static android.server.wm.alertwindowservice.Components.ALERT_WINDOW_SERVICE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.alertwindowservice.AlertWindowService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.ToIntFunction;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:AlertWindowsImportanceTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class AlertWindowsImportanceTests {
+
+    private static final String TAG = "AlertWindowsTests";
+
+    private static final boolean DEBUG = false;
+    private static final long WAIT_TIME_MS = 2 * 1000;
+
+    private Messenger mService;
+    private String mServicePackageName;
+
+    private ActivityManager mAm;
+    private ActivityManager mAm25; // ActivityManager created for an SDK 25 app context.
+
+    private final Messenger mMessenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));
+    private final Object mAddedLock = new Object();
+    private final Object mRemoveLock = new Object();
+
+    @Before
+    public void setUp() throws Exception {
+        if (DEBUG) Log.e(TAG, "setUp");
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        mAm = context.getSystemService(ActivityManager.class);
+        mAm25 = context.createPackageContext(SDK25_ALERT_WINDOW_TEST_ACTIVITY.getPackageName(), 0)
+                .getSystemService(ActivityManager.class);
+
+        final Intent intent = new Intent()
+                .setComponent(ALERT_WINDOW_SERVICE)
+                .putExtra(AlertWindowService.EXTRA_MESSENGER, mMessenger);
+        // Needs to be both BIND_NOT_FOREGROUND and BIND_ALLOW_OOM_MANAGEMENT to avoid the binding
+        // to this instrumentation test from increasing its importance.
+        context.bindService(intent, mConnection,
+                BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_ALLOW_OOM_MANAGEMENT);
+        synchronized (mConnection) {
+            // Wait for alert window service to be connection before processing.
+            mConnection.wait(WAIT_TIME_MS);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (DEBUG) Log.e(TAG, "tearDown");
+        if (mService != null) {
+            mService.send(Message.obtain(null, AlertWindowService.MSG_REMOVE_ALL_ALERT_WINDOWS));
+        }
+        final Context context = InstrumentationRegistry.getTargetContext();
+        context.unbindService(mConnection);
+        mAm = null;
+        mAm25 = null;
+    }
+
+    @Test
+    public void testAlertWindowOomAdj() throws Exception {
+        // Alert windows are always hidden when running in VR.
+        if (isRunningInVR()) {
+            return;
+        }
+        setAlertWindowPermission(true /* allow */);
+
+        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+        // TODO AM.getUidImportance() sometimes return a different value from what
+        // getPackageImportance() returns... b/37950472
+        // assertUidImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+        addAlertWindow();
+        // Process importance should be increased to visible when the service has an alert window.
+        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+        addAlertWindow();
+        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+        setAlertWindowPermission(false /* allow */);
+        // Process importance should no longer be visible since its alert windows are not allowed to
+        // be visible.
+        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+        setAlertWindowPermission(true /* allow */);
+        // They can show again so importance should be visible again.
+        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+        removeAlertWindow();
+        assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+        removeAlertWindow();
+        // Process importance should no longer be visible when the service no longer as alert
+        // windows.
+        assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+    }
+
+    private void addAlertWindow() throws Exception {
+        mService.send(Message.obtain(null, AlertWindowService.MSG_ADD_ALERT_WINDOW));
+        synchronized (mAddedLock) {
+            // Wait for window addition confirmation before proceeding.
+            mAddedLock.wait(WAIT_TIME_MS);
+        }
+    }
+
+    private void removeAlertWindow() throws Exception {
+        mService.send(Message.obtain(null, AlertWindowService.MSG_REMOVE_ALERT_WINDOW));
+        synchronized (mRemoveLock) {
+            // Wait for window removal confirmation before proceeding.
+            mRemoveLock.wait(WAIT_TIME_MS);
+        }
+    }
+
+    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);
+    }
+
+    private void assertImportance(ToIntFunction<ActivityManager> apiCaller,
+            int expectedForO, int expectedForPreO) throws Exception {
+        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);
+
+        // Check the result for pre-O apps.
+        assertEquals(expectedForPreO, apiCaller.applyAsInt(mAm25));
+    }
+
+    /**
+     * Make sure {@link ActivityManager#getPackageImportance} returns the expected value.
+     */
+    private void assertPackageImportance(int expectedForO, int expectedForPreO) throws Exception {
+        assertImportance(am -> am.getPackageImportance(mServicePackageName),
+                expectedForO, expectedForPreO);
+    }
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) Log.e(TAG, "onServiceConnected");
+            mService = new Messenger(service);
+            mServicePackageName = name.getPackageName();
+            synchronized (mConnection) {
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG) Log.e(TAG, "onServiceDisconnected");
+            mService = null;
+            mServicePackageName = null;
+        }
+    };
+
+    private class IncomingHandler extends Handler {
+
+        IncomingHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AlertWindowService.MSG_ON_ALERT_WINDOW_ADDED:
+                    synchronized (mAddedLock) {
+                        if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_ADDED");
+                        mAddedLock.notifyAll();
+                    }
+                    break;
+                case AlertWindowService.MSG_ON_ALERT_WINDOW_REMOVED:
+                    synchronized (mRemoveLock) {
+                        if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_REMOVED");
+                        mRemoveLock.notifyAll();
+                    }
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    private boolean isRunningInVR() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
+             == Configuration.UI_MODE_TYPE_VR_HEADSET) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
new file mode 100644
index 0000000..94c625f
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm;
+
+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.server.wm.alertwindowapp.Components.ALERT_WINDOW_TEST_ACTIVITY;
+import static android.server.wm.alertwindowappsdk25.Components.SDK25_ALERT_WINDOW_TEST_ACTIVITY;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:AlertWindowsTests
+ */
+@Presubmit
+public class AlertWindowsTests extends ActivityManagerTestBase {
+
+    // From WindowManager.java
+    private static final int TYPE_BASE_APPLICATION      = 1;
+    private static final int FIRST_SYSTEM_WINDOW        = 2000;
+
+    private static final int TYPE_PHONE                 = FIRST_SYSTEM_WINDOW + 2;
+    private static final int TYPE_SYSTEM_ALERT          = FIRST_SYSTEM_WINDOW + 3;
+    private static final int TYPE_SYSTEM_OVERLAY        = FIRST_SYSTEM_WINDOW + 6;
+    private static final int TYPE_PRIORITY_PHONE        = FIRST_SYSTEM_WINDOW + 7;
+    private static final int TYPE_SYSTEM_ERROR          = FIRST_SYSTEM_WINDOW + 10;
+    private static final int TYPE_APPLICATION_OVERLAY   = FIRST_SYSTEM_WINDOW + 38;
+
+    private static final int TYPE_STATUS_BAR            = FIRST_SYSTEM_WINDOW;
+    private static final int TYPE_INPUT_METHOD          = FIRST_SYSTEM_WINDOW + 11;
+    private static final int TYPE_NAVIGATION_BAR        = FIRST_SYSTEM_WINDOW + 19;
+
+    private final List<Integer> mAlertWindowTypes = Arrays.asList(
+            TYPE_PHONE,
+            TYPE_PRIORITY_PHONE,
+            TYPE_SYSTEM_ALERT,
+            TYPE_SYSTEM_ERROR,
+            TYPE_SYSTEM_OVERLAY,
+            TYPE_APPLICATION_OVERLAY);
+    private final List<Integer> mSystemWindowTypes = Arrays.asList(
+            TYPE_STATUS_BAR,
+            TYPE_INPUT_METHOD,
+            TYPE_NAVIGATION_BAR);
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        resetPermissionState(ALERT_WINDOW_TEST_ACTIVITY);
+        resetPermissionState(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        resetPermissionState(ALERT_WINDOW_TEST_ACTIVITY);
+        resetPermissionState(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
+        stopTestPackage(ALERT_WINDOW_TEST_ACTIVITY);
+        stopTestPackage(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testAlertWindowAllowed() throws Exception {
+        runAlertWindowTest(ALERT_WINDOW_TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowDisallowed() throws Exception {
+        runAlertWindowTest(ALERT_WINDOW_TEST_ACTIVITY, false /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowAllowedSdk25() throws Exception {
+        runAlertWindowTest(SDK25_ALERT_WINDOW_TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+                false /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowDisallowedSdk25() throws Exception {
+        runAlertWindowTest(SDK25_ALERT_WINDOW_TEST_ACTIVITY, false /* hasAlertWindowPermission */,
+                false /* atLeastO */);
+    }
+
+    private void runAlertWindowTest(final ComponentName activityName,
+            final boolean hasAlertWindowPermission, final boolean atLeastO) throws Exception {
+        setAlertWindowPermission(activityName, hasAlertWindowPermission);
+
+        executeShellCommand(getAmStartCmd(activityName));
+        mAmWmState.computeState(new WaitForValidActivityState(activityName));
+        mAmWmState.assertVisibility(activityName, true);
+
+        assertAlertWindows(activityName, hasAlertWindowPermission, atLeastO);
+    }
+
+    private boolean allWindowsHidden(ArrayList<WindowManagerState.WindowState> windows) {
+        for (WindowManagerState.WindowState ws : windows) {
+            if (ws.isShown()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void assertAlertWindows(final ComponentName activityName,
+            final boolean hasAlertWindowPermission, final boolean atLeastO) throws Exception {
+        final String packageName = activityName.getPackageName();
+        final WindowManagerState wMState = mAmWmState.getWmState();
+
+        final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList<>();
+        wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
+
+        if (!hasAlertWindowPermission) {
+            // When running in VR Mode, an App Op restriction is
+            // 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());
+                assertTrue("All alert windows should be hidden",
+                        allWindowsHidden(alertWindows));
+            } else {
+                assertTrue("Should be empty alertWindows=" + alertWindows,
+                        alertWindows.isEmpty());
+                assertTrue(AppOpsUtils.rejectedOperationLogged(packageName,
+                        OPSTR_SYSTEM_ALERT_WINDOW));
+                return;
+            }
+        }
+
+        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);
+            }
+        }
+
+        final WindowManagerState.WindowState mainAppWindow =
+                wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
+
+        assertNotNull(mainAppWindow);
+
+        final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
+        final WindowManagerState.WindowState highestAlertWindow =
+                alertWindows.get(alertWindows.size() - 1);
+
+        // Assert that the alert windows have higher z-order than the main app window
+        final WindowManagerState wmState = mAmWmState.getWmState();
+        assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
+                + mainAppWindow,
+                wmState.getZOrder(lowestAlertWindow) > 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));
+        }
+
+        // Assert that alert windows are below key system windows.
+        final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList<>();
+        wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
+        if (!systemWindows.isEmpty()) {
+            final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
+            assertTrue("highestAlertWindow=" + highestAlertWindow
+                            + " greater than lowestSystemWindow=" + lowestSystemWindow,
+                    wmState.getZOrder(highestAlertWindow) < wmState.getZOrder(lowestSystemWindow));
+        }
+        assertTrue(AppOpsUtils.allowedOperationLogged(packageName, OPSTR_SYSTEM_ALERT_WINDOW));
+    }
+
+    // Resets the permission states for a package to the system defaults.
+    // Also clears the app operation logs for this package, required to test that displaying
+    // the alert window gets logged.
+    private void resetPermissionState(ComponentName activityName) throws Exception {
+        AppOpsUtils.reset(activityName.getPackageName());
+    }
+
+    private void setAlertWindowPermission(final ComponentName activityName, final boolean allow)
+            throws Exception {
+        int mode = allow ? MODE_ALLOWED : MODE_ERRORED;
+        AppOpsUtils.setOpMode(activityName.getPackageName(), OPSTR_SYSTEM_ALERT_WINDOW, mode);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
new file mode 100644
index 0000000..2ab39e7
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.server.am.ActivityManagerTestBase.executeShellCommand;
+import static android.server.am.StateLogger.log;
+import static android.server.am.UiDeviceUtils.dragPointer;
+import static android.server.am.UiDeviceUtils.pressMenuButton;
+import static android.server.am.UiDeviceUtils.wakeUpDevice;
+
+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.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.CrossAppDragAndDropTests
+ */
+@Presubmit
+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:
+    // taskId=<TASK_ID>: <componentName> bounds=[LEFT,TOP][RIGHT,BOTTOM]
+    private static final String TASK_REGEX_PATTERN_STRING =
+            "taskId=[0-9]+: %s bounds=\\[[0-9]+,[0-9]+\\]\\[[0-9]+,[0-9]+\\]";
+
+    private static final int SWIPE_STEPS = 100;
+
+    private static final String SOURCE_PACKAGE_NAME = "android.server.wm.dndsourceapp";
+    private static final String TARGET_PACKAGE_NAME = "android.server.wm.dndtargetapp";
+    private static final String TARGET_23_PACKAGE_NAME = "android.server.wm.dndtargetappsdk23";
+
+
+    private static final String SOURCE_ACTIVITY_NAME = "DragSource";
+    private static final String TARGET_ACTIVITY_NAME = "DropTarget";
+
+    private static final String FILE_GLOBAL = "file_global";
+    private static final String FILE_LOCAL = "file_local";
+    private static final String DISALLOW_GLOBAL = "disallow_global";
+    private static final String CANCEL_SOON = "cancel_soon";
+    private static final String GRANT_NONE = "grant_none";
+    private static final String GRANT_READ = "grant_read";
+    private static final String GRANT_WRITE = "grant_write";
+    private static final String GRANT_READ_PREFIX = "grant_read_prefix";
+    private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
+    private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
+
+    private static final String REQUEST_NONE = "request_none";
+    private static final String REQUEST_READ = "request_read";
+    private static final String REQUEST_READ_NESTED = "request_read_nested";
+    private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
+    private static final String REQUEST_WRITE = "request_write";
+
+    private static final String SOURCE_LOG_TAG = "DragSource";
+    private static final String TARGET_LOG_TAG = "DropTarget";
+
+    private static final String RESULT_KEY_START_DRAG = "START_DRAG";
+    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+    private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
+    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+    private static final String RESULT_KEY_DROP_RESULT = "DROP";
+    private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
+    private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
+    private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
+    private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
+    private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
+
+    private static final String RESULT_MISSING = "Missing";
+    private static final String RESULT_OK = "OK";
+    private static final String RESULT_EXCEPTION = "Exception";
+    private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
+
+    protected Context mContext;
+    protected ActivityManager mAm;
+
+    private Map<String, String> mSourceResults;
+    private Map<String, String> mTargetResults;
+
+    private String mSourcePackageName;
+    private String mTargetPackageName;
+
+    private String mSessionId;
+    private String mSourceLogTag;
+    private String mTargetLogTag;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(supportsDragAndDrop());
+
+        // Use uptime in seconds as unique test invocation id.
+        mSessionId = Long.toString(SystemClock.uptimeMillis() / 1000);
+        mSourceLogTag = SOURCE_LOG_TAG + mSessionId;
+        mTargetLogTag = TARGET_LOG_TAG + mSessionId;
+
+        mContext = InstrumentationRegistry.getContext();
+        mAm = mContext.getSystemService(ActivityManager.class);
+
+        mSourcePackageName = SOURCE_PACKAGE_NAME;
+        mTargetPackageName = TARGET_PACKAGE_NAME;
+        cleanupState();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        assumeTrue(supportsDragAndDrop());
+
+        executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
+        executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
+    }
+
+    private void clearLogs() {
+        executeShellCommand("logcat -c");
+    }
+
+    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;
+    }
+
+    /**
+     * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
+     * 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();
+
+        // Remove special stacks.
+        mAm.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);
+        waitForResume(packageName, activityName);
+    }
+
+    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);
+        waitForResume(packageName, activityName);
+    }
+
+    /**
+     * @param displaySize size of the display
+     * @param leftSide {@code true} to launch the app taking up the left half of the display,
+     *         {@code false} to launch the app taking up the right half of the display.
+     */
+    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));
+    }
+
+    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");
+    }
+
+    private void injectInput(Point from, Point to, int steps) throws Exception {
+        dragPointer(from, to, steps);
+    }
+
+    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;
+            }
+            if (pattern.matcher(truncatedLine).find()) {
+                return truncatedLine;
+            }
+        }
+        return "";
+    }
+
+    private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
+        final String taskInfo = findTaskInfo(name);
+        final String[] sections = taskInfo.split("\\[");
+        if (sections.length > 2) {
+            try {
+                parsePoint(sections[1], from);
+                parsePoint(sections[2], to);
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    private int getActivityTaskId(String name) {
+        final String taskInfo = findTaskInfo(name);
+        for (String word : taskInfo.split("\\s+")) {
+            if (word.startsWith(TASK_ID_PREFIX)) {
+                final String withColon = word.split("=")[1];
+                return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+            }
+        }
+        return -1;
+    }
+
+    private Point getDisplaySize() throws Exception {
+        final String output = executeShellCommand("wm size");
+        final String[] sizes = output.split(" ")[2].split("x");
+        return new Point(Integer.valueOf(sizes[0].trim()), Integer.valueOf(sizes[1].trim()));
+    }
+
+    private Point getWindowCenter(String name) throws Exception {
+        Point p1 = new Point();
+        Point p2 = new Point();
+        if (getWindowBounds(name, p1, p2)) {
+            return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+        }
+        return null;
+    }
+
+    private void parsePoint(String string, Point point) {
+        final String[] parts = string.split("[,|\\]]");
+        point.x = Integer.parseInt(parts[0]);
+        point.y = Integer.parseInt(parts[1]);
+    }
+
+    private void unlockDevice() {
+        // Wake up the device, if necessary.
+        try {
+            wakeUpDevice();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        // Unlock the screen.
+        pressMenuButton();
+    }
+
+    private void assertDropResult(String sourceMode, String targetMode, String expectedDropResult)
+            throws Exception {
+        assertDragAndDropResults(sourceMode, targetMode, RESULT_OK, expectedDropResult, RESULT_OK);
+    }
+
+    private void assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)
+            throws Exception {
+        assertDragAndDropResults(
+                sourceMode, REQUEST_NONE, expectedStartDragResult, RESULT_MISSING, RESULT_MISSING);
+    }
+
+    private void assertDragAndDropResults(String sourceMode, String targetMode,
+            String expectedStartDragResult, String expectedDropResult,
+            String expectedListenerResults) throws Exception {
+        Log.e(TAG, "session: " + mSessionId + ", source: " + sourceMode
+                + ", target: " + targetMode);
+
+        if (supportsSplitScreenMultiWindow()) {
+            launchDockedActivity(
+                    mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag);
+            launchFullscreenActivity(
+                    mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag);
+        } else if (supportsFreeformMultiWindow()) {
+            // Fallback to try to launch two freeform windows side by side.
+            Point displaySize = getDisplaySize();
+            launchFreeformActivity(
+                    mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag,
+                    displaySize, true /* leftSide */);
+            launchFreeformActivity(
+                    mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag,
+                    displaySize, false /* leftSide */);
+        } else {
+            return;
+        }
+
+        Point p1 = getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME));
+        assertNotNull(p1);
+        Point p2 = getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME));
+        assertNotNull(p2);
+
+        TestLogService.registerClient(mSourceLogTag, RESULT_KEY_START_DRAG);
+        TestLogService.registerClient(mTargetLogTag, RESULT_KEY_DRAG_ENDED);
+
+        injectInput(p1, p2, SWIPE_STEPS);
+
+        mSourceResults = TestLogService.getResultsForClient(mSourceLogTag, 1000);
+        assertSourceResult(RESULT_KEY_START_DRAG, expectedStartDragResult);
+
+        mTargetResults = TestLogService.getResultsForClient(mTargetLogTag, 1000);
+        assertTargetResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
+        if (!RESULT_MISSING.equals(expectedDropResult)) {
+            assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
+            assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
+        }
+        assertListenerResults(expectedListenerResults);
+    }
+
+    private void assertListenerResults(String expectedResult) throws Exception {
+        assertTargetResult(RESULT_KEY_DRAG_STARTED, expectedResult);
+        assertTargetResult(RESULT_KEY_DRAG_ENDED, expectedResult);
+        assertTargetResult(RESULT_KEY_EXTRAS, expectedResult);
+
+        assertTargetResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
+        assertTargetResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING);
+        assertTargetResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_MISSING);
+    }
+
+    private void assertSourceResult(String resultKey, String expectedResult) throws Exception {
+        assertResult(mSourceResults, resultKey, expectedResult);
+    }
+
+    private void assertTargetResult(String resultKey, String expectedResult) throws Exception {
+        assertResult(mTargetResults, resultKey, expectedResult);
+    }
+
+    private void assertResult(Map<String, String> results, String resultKey, String expectedResult)
+            throws Exception {
+        if (RESULT_MISSING.equals(expectedResult)) {
+            if (results.containsKey(resultKey)) {
+                fail("Unexpected " + resultKey + "=" + results.get(resultKey));
+            }
+        } else {
+            assertTrue("Missing " + resultKey, results.containsKey(resultKey));
+            assertEquals(resultKey + " result mismatch,", expectedResult,
+                    results.get(resultKey));
+        }
+    }
+
+    private static boolean supportsDragAndDrop() {
+        return ActivityManager.supportsMultiWindow(InstrumentationRegistry.getContext());
+    }
+
+    private static boolean supportsSplitScreenMultiWindow() {
+        return ActivityManager.supportsSplitScreenMultiWindow(InstrumentationRegistry.getContext());
+    }
+
+    private static boolean supportsFreeformMultiWindow() {
+        return InstrumentationRegistry.getContext()
+                .getPackageManager()
+                .hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+    }
+
+    @Test
+    public void testCancelSoon() throws Exception {
+        assertDropResult(CANCEL_SOON, REQUEST_NONE, RESULT_MISSING);
+    }
+
+    @Test
+    public void testDisallowGlobal() throws Exception {
+        assertNoGlobalDragEvents(DISALLOW_GLOBAL, RESULT_OK);
+    }
+
+    @Test
+    public void testDisallowGlobalBelowSdk24() throws Exception {
+        mTargetPackageName = TARGET_23_PACKAGE_NAME;
+        assertNoGlobalDragEvents(GRANT_NONE, RESULT_OK);
+    }
+
+    @Test
+    public void testFileUriLocal() throws Exception {
+        assertNoGlobalDragEvents(FILE_LOCAL, RESULT_OK);
+    }
+
+    @Test
+    public void testFileUriGlobal() throws Exception {
+        assertNoGlobalDragEvents(FILE_GLOBAL, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantNoneRequestNone() throws Exception {
+        assertDropResult(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantNoneRequestRead() throws Exception {
+        assertDropResult(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
+    }
+
+    @Test
+    public void testGrantNoneRequestWrite() throws Exception {
+        assertDropResult(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
+    }
+
+    @Test
+    public void testGrantReadRequestNone() throws Exception {
+        assertDropResult(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantReadRequestRead() throws Exception {
+        assertDropResult(GRANT_READ, REQUEST_READ, RESULT_OK);
+    }
+
+    @Test
+    public void testGrantReadRequestWrite() throws Exception {
+        assertDropResult(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantReadNoPrefixRequestReadNested() throws Exception {
+        assertDropResult(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantReadPrefixRequestReadNested() throws Exception {
+        assertDropResult(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
+    }
+
+    @Test
+    public void testGrantPersistableRequestTakePersistable() throws Exception {
+        assertDropResult(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
+    }
+
+    @Test
+    public void testGrantReadRequestTakePersistable() throws Exception {
+        assertDropResult(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantWriteRequestNone() throws Exception {
+        assertDropResult(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantWriteRequestRead() throws Exception {
+        assertDropResult(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testGrantWriteRequestWrite() throws Exception {
+        assertDropResult(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
new file mode 100644
index 0000000..822d103
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.view.Gravity;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import java.util.function.Consumer;
+
+// TODO: Make this an inner class of {@link DialogFrameTest}.
+public class DialogFrameTestActivity extends Activity {
+
+    static final String DIALOG_WINDOW_NAME = "TestDialog";
+
+    // TODO: Passing layout parameters for {@link Dialog} in extra of {@link Intent} instead of
+    // using test case name.
+    // Extra key for test case name.
+    static final String EXTRA_TEST_CASE = "test-case";
+    // Value constants for {@link #EXTRA_TEST_CASE}.
+    static final String TEST_EXPLICIT_POSITION_MATCH_PARENT = "ExplicitPositionMatchParent";
+    static final String TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS =
+            "ExplicitPositionMatchParentNoLimits";
+    static final String TEST_EXPLICIT_SIZE = "ExplicitSize";
+    static final String TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY =
+            "ExplicitSizeBottomRightGravity";
+    static final String TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY = "ExplicitSizeTopLeftGravity";
+    static final String TEST_MATCH_PARENT = "MatchParent";
+    static final String TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN = "MatchParentLayoutInOverscan";
+    static final String TEST_NO_FOCUS = "NoFocus";
+    static final String TEST_OVER_SIZED_DIMENSIONS = "OversizedDimensions";
+    static final String TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS = "OversizedDimensionsNoLimits";
+    static final String TEST_WITH_MARGINS = "WithMargins";
+
+    private AlertDialog mDialog;
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mDialog.dismiss();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        setupTest(getIntent());
+    }
+
+    private void setupTest(Intent intent) {
+        final String testCase = intent.getStringExtra(EXTRA_TEST_CASE);
+        switch (testCase) {
+            case TEST_MATCH_PARENT:
+                testMatchParent();
+                break;
+            case TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN:
+                testMatchParentLayoutInOverscan();
+                break;
+            case TEST_EXPLICIT_SIZE:
+                testExplicitSize();
+                break;
+            case TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY:
+                testExplicitSizeTopLeftGravity();
+                break;
+            case TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY:
+                testExplicitSizeBottomRightGravity();
+                break;
+            case TEST_OVER_SIZED_DIMENSIONS:
+                testOversizedDimensions();
+                break;
+            case TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS:
+                testOversizedDimensionsNoLimits();
+                break;
+            case TEST_EXPLICIT_POSITION_MATCH_PARENT:
+                testExplicitPositionMatchParent();
+                break;
+            case TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS:
+                testExplicitPositionMatchParentNoLimits();
+                break;
+            case TEST_NO_FOCUS:
+                testNoFocus();
+                break;
+            case TEST_WITH_MARGINS:
+                testWithMargins();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void doLayoutParamTest(Consumer<LayoutParams> setUp) {
+        mDialog = new AlertDialog.Builder(this).create();
+
+        mDialog.setMessage("Testing is fun!");
+        mDialog.setTitle(DIALOG_WINDOW_NAME);
+        mDialog.create();
+
+        Window w = mDialog.getWindow();
+        final LayoutParams params = w.getAttributes();
+        setUp.accept(params);
+        w.setAttributes(params);
+
+        mDialog.show();
+    }
+
+    private void testMatchParent() {
+        doLayoutParamTest(params -> {
+            params.width = MATCH_PARENT;
+            params.height = MATCH_PARENT;
+        });
+    }
+
+    private void testMatchParentLayoutInOverscan() {
+        doLayoutParamTest(params -> {
+            params.width = MATCH_PARENT;
+            params.height = MATCH_PARENT;
+            params.flags |= FLAG_LAYOUT_IN_SCREEN;
+            params.flags |= FLAG_LAYOUT_IN_OVERSCAN;
+        });
+    }
+
+    private void testExplicitSize() {
+        doLayoutParamTest(params -> {
+            params.width = 200;
+            params.height = 200;
+        });
+    }
+
+    private void testExplicitSizeTopLeftGravity() {
+        doLayoutParamTest(params -> {
+            params.width = 200;
+            params.height = 200;
+            params.gravity = Gravity.TOP | Gravity.LEFT;
+        });
+    }
+
+    private void testExplicitSizeBottomRightGravity() {
+        doLayoutParamTest(params -> {
+            params.width = 200;
+            params.height = 200;
+            params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+        });
+    }
+
+    private void testOversizedDimensions() {
+        doLayoutParamTest(params -> {
+            params.width = 100000;
+            params.height = 100000;
+        });
+    }
+
+    private void testOversizedDimensionsNoLimits() {
+        doLayoutParamTest(params -> {
+            params.width = 5000;
+            params.height = 5000;
+            params.flags |= FLAG_LAYOUT_NO_LIMITS;
+            params.gravity = Gravity.LEFT | Gravity.TOP;
+        });
+    }
+
+    private void testExplicitPositionMatchParent() {
+        doLayoutParamTest(params -> {
+            params.width = MATCH_PARENT;
+            params.height = MATCH_PARENT;
+            params.x = 100;
+            params.y = 100;
+        });
+    }
+
+    private void testExplicitPositionMatchParentNoLimits() {
+        doLayoutParamTest(params -> {
+            params.width = MATCH_PARENT;
+            params.height = MATCH_PARENT;
+            params.gravity = Gravity.LEFT | Gravity.TOP;
+            params.flags |= FLAG_LAYOUT_NO_LIMITS;
+            params.x = 100;
+            params.y = 100;
+        });
+    }
+
+    private void testNoFocus() {
+        doLayoutParamTest(params -> params.flags |= FLAG_NOT_FOCUSABLE);
+    }
+
+    private void testWithMargins() {
+        doLayoutParamTest(params -> {
+            params.gravity = Gravity.LEFT | Gravity.TOP;
+            params.horizontalMargin = .25f;
+            params.verticalMargin = .35f;
+            params.width = 200;
+            params.height = 200;
+            params.x = 0;
+            params.y = 0;
+        });
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
new file mode 100644
index 0000000..18567d0
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.wm.DialogFrameTestActivity.DIALOG_WINDOW_NAME;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_EXPLICIT_POSITION_MATCH_PARENT;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS;
+import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY;
+import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN;
+import static android.server.wm.DialogFrameTestActivity.TEST_NO_FOCUS;
+import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS;
+import static android.server.wm.DialogFrameTestActivity
+        .TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS;
+import static android.server.wm.DialogFrameTestActivity.TEST_WITH_MARGINS;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState;
+import android.server.am.WindowManagerState.WindowState;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:DialogFrameTests
+ *
+ * TODO: Consolidate this class with {@link ParentChildTestBase}.
+ */
+public class DialogFrameTests extends ParentChildTestBase<DialogFrameTestActivity> {
+
+    private static final ComponentName DIALOG_FRAME_TEST_ACTIVITY = new ComponentName(
+            InstrumentationRegistry.getContext(), DialogFrameTestActivity.class);
+
+    @Rule
+    public final ActivityTestRule<DialogFrameTestActivity> mDialogTestActivity =
+            new ActivityTestRule<>(DialogFrameTestActivity.class, false /* initialTOuchMode */,
+                    false /* launchActivity */);
+
+    @Override
+    ComponentName activityName() {
+        return DIALOG_FRAME_TEST_ACTIVITY;
+    }
+
+    @Override
+    ActivityTestRule<DialogFrameTestActivity> activityRule() {
+        return mDialogTestActivity;
+    }
+
+    private WindowState getSingleWindow(final String windowName) {
+        final List<WindowState> windowList = new ArrayList<>();
+        mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, windowList);
+        assertThat(windowList.size(), greaterThan(0));
+        return windowList.get(0);
+    }
+
+    @Override
+    void doSingleTest(ParentChildTest t) throws Exception {
+        mAmWmState.computeState(WaitForValidActivityState.forWindow(DIALOG_WINDOW_NAME));
+        WindowState dialog = getSingleWindow(DIALOG_WINDOW_NAME);
+        WindowState parent = getSingleWindow(getWindowName(activityName()));
+
+        t.doTest(parent, dialog);
+    }
+
+    // With Width and Height as MATCH_PARENT we should fill
+    // the same content frame as the main activity window
+    @Test
+    public void testMatchParentDialog() throws Exception {
+        doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) ->
+                assertEquals(parent.getContentFrame(), dialog.getFrame())
+        );
+    }
+
+    // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
+    // we will not be constrained to the insets and so we will be the same size
+    // as the main window main frame.
+    @Test
+    public void testMatchParentDialogLayoutInOverscan() throws Exception {
+        doParentChildTest(TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN, (parent, dialog) ->
+                assertEquals(parent.getFrame(), dialog.getFrame())
+        );
+    }
+
+    private static final int explicitDimension = 200;
+
+    // The default gravity for dialogs should center them.
+    @Test
+    public void testExplicitSizeDefaultGravity() throws Exception {
+        doParentChildTest(TEST_EXPLICIT_SIZE, (parent, dialog) -> {
+            Rect contentFrame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(
+                    contentFrame.left + (contentFrame.width() - explicitDimension) / 2,
+                    contentFrame.top + (contentFrame.height() - explicitDimension) / 2,
+                    contentFrame.left + (contentFrame.width() + explicitDimension) / 2,
+                    contentFrame.top + (contentFrame.height() + explicitDimension) / 2);
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    @Test
+    public void testExplicitSizeTopLeftGravity() throws Exception {
+        doParentChildTest(TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY, (parent, dialog) -> {
+            Rect contentFrame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(
+                    contentFrame.left,
+                    contentFrame.top,
+                    contentFrame.left + explicitDimension,
+                    contentFrame.top + explicitDimension);
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    @Test
+    public void testExplicitSizeBottomRightGravity() throws Exception {
+        doParentChildTest(TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY, (parent, dialog) -> {
+            Rect contentFrame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(
+                    contentFrame.left + contentFrame.width() - explicitDimension,
+                    contentFrame.top + contentFrame.height() - explicitDimension,
+                    contentFrame.left + contentFrame.width(),
+                    contentFrame.top + contentFrame.height());
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    // TODO(b/30127373): Commented out for now because it doesn't work. We end up insetting the
+    // decor on the bottom. I think this is a bug probably in the default dialog flags:
+    @Ignore
+    @Test
+    public void testOversizedDimensions() throws Exception {
+        doParentChildTest(TEST_OVER_SIZED_DIMENSIONS, (parent, dialog) ->
+                // With the default flags oversize should result in clipping to
+                // parent frame.
+                assertEquals(parent.getContentFrame(), dialog.getFrame())
+        );
+    }
+
+    // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size.
+    static final int oversizedDimension = 5000;
+    // With FLAG_LAYOUT_NO_LIMITS  we should get the size we request, even if its much larger than
+    // the screen.
+    @Ignore
+    @Test
+    public void testOversizedDimensionsNoLimits() throws Exception {
+        // TODO(b/36890978): We only run this in fullscreen because of the
+        // unclear status of NO_LIMITS for non-child surfaces in MW modes
+        doFullscreenTest(TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS, (parent, dialog) -> {
+            Rect contentFrame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(contentFrame.left, contentFrame.top,
+                    contentFrame.left + oversizedDimension,
+                    contentFrame.top + oversizedDimension);
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    // If we request the MATCH_PARENT and a non-zero position, we wouldn't be
+    // able to fit all of our content, so we should be adjusted to just fit the
+    // content frame.
+    @Test
+    public void testExplicitPositionMatchParent() throws Exception {
+        doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT, (parent, dialog) ->
+                assertEquals(parent.getContentFrame(), dialog.getFrame())
+        );
+    }
+
+    // Unless we pass NO_LIMITS in which case our requested position should
+    // be honored.
+    @Test
+    public void testExplicitPositionMatchParentNoLimits() throws Exception {
+        final int explicitPosition = 100;
+        doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS, (parent, dialog) -> {
+            Rect contentFrame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(contentFrame);
+            expectedFrame.offset(explicitPosition, explicitPosition);
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    // We run the two focus tests fullscreen only because switching to the
+    // docked stack will strip away focus from the task anyway.
+    @Test
+    public void testDialogReceivesFocus() throws Exception {
+        doFullscreenTest(TEST_MATCH_PARENT, (parent, dialog) ->
+                assertEquals(dialog.getName(), mAmWmState.getWmState().getFocusedWindow())
+        );
+    }
+
+    @Test
+    public void testNoFocusDialog() throws Exception {
+        doFullscreenTest(TEST_NO_FOCUS, (parent, dialog) ->
+                assertEquals(parent.getName(), mAmWmState.getWmState().getFocusedWindow())
+        );
+    }
+
+    @Test
+    public void testMarginsArePercentagesOfContentFrame() throws Exception {
+        float horizontalMargin = .25f;
+        float verticalMargin = .35f;
+        doParentChildTest(TEST_WITH_MARGINS, (parent, dialog) -> {
+            Rect frame = parent.getContentFrame();
+            Rect expectedFrame = new Rect(
+                    (int) (horizontalMargin * frame.width() + frame.left),
+                    (int) (verticalMargin * frame.height() + frame.top),
+                    (int) (horizontalMargin * frame.width() + frame.left) + explicitDimension,
+                    (int) (verticalMargin * frame.height() + frame.top) + explicitDimension);
+            assertEquals(expectedFrame, dialog.getFrame());
+        });
+    }
+
+    @Test
+    public void testDialogPlacedAboveParent() throws Exception {
+        final WindowManagerState wmState = mAmWmState.getWmState();
+        doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) ->
+                // Not only should the dialog be higher, but it should be leave multiple layers of
+                // space in between for DimLayers, etc...
+                assertThat(wmState.getZOrder(dialog), greaterThan(wmState.getZOrder(parent)))
+        );
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
new file mode 100644
index 0000000..a27ffd9
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.StateLogger.log;
+import static android.server.wm.DialogFrameTestActivity.EXTRA_TEST_CASE;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.WindowManagerState.WindowState;
+import android.support.test.rule.ActivityTestRule;
+
+abstract class ParentChildTestBase<T extends Activity> extends ActivityManagerTestBase {
+
+    interface ParentChildTest {
+        void doTest(WindowState parent, WindowState child);
+    }
+
+    private void startTestCase(String testCase) throws Exception {
+        final Intent intent = new Intent()
+                .putExtra(EXTRA_TEST_CASE, testCase);
+        activityRule().launchActivity(intent);
+    }
+
+    private void startTestCaseDocked(String testCase) throws Exception {
+        startTestCase(testCase);
+        setActivityTaskWindowingMode(activityName(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+    }
+
+    abstract ComponentName activityName();
+    abstract ActivityTestRule<T> activityRule();
+
+    abstract void doSingleTest(ParentChildTest t) throws Exception;
+
+    void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
+        log("Running test fullscreen");
+        startTestCase(testCase);
+        doSingleTest(t);
+        activityRule().finishActivity();
+    }
+
+    private void doDockedTest(String testCase, ParentChildTest t) throws Exception {
+        log("Running test docked");
+        if (!supportsSplitScreenMultiWindow()) {
+            log("Skipping test: no split multi-window support");
+            return;
+        }
+        startTestCaseDocked(testCase);
+        doSingleTest(t);
+        activityRule().finishActivity();
+    }
+
+    void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
+        doFullscreenTest(testCase, t);
+        doDockedTest(testCase, t);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java b/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java
new file mode 100644
index 0000000..725ef09
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A class that sends log data to {@link TestLogService}.
+ */
+public class TestLogClient {
+
+    public static final String EXTRA_LOG_TAG = "logtag";
+    public static final String EXTRA_KEY = "key";
+    public static final String EXTRA_VALUE = "value";
+
+    private static final String TEST_LOGGER_PACKAGE_NAME = "android.server.cts.wm";
+    private static final String TEST_LOGGER_SERVICE_NAME = "android.server.wm.TestLogService";
+
+    private final Context mContext;
+    private final String mLogTag;
+
+    public TestLogClient(Context context, String logtag) {
+        mContext = context;
+        mLogTag = logtag;
+    }
+
+    public void record(String key, String value) {
+        Intent intent = new Intent();
+        intent.setComponent(
+                new ComponentName(TEST_LOGGER_PACKAGE_NAME, TEST_LOGGER_SERVICE_NAME));
+        intent.putExtra(EXTRA_LOG_TAG, mLogTag);
+        intent.putExtra(EXTRA_KEY, key);
+        intent.putExtra(EXTRA_VALUE, value);
+        mContext.startService(intent);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java b/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java
new file mode 100644
index 0000000..8bd5099
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *  A service collecting data from other apps used by a test.
+ *
+ *  Use {@link TestLogClient} to send data to this service.
+ */
+public class TestLogService extends Service {
+    private static final String TAG = "TestLogService";
+
+    private static final Object mLock = new Object();
+
+    static class ClientChannel {
+        final String mStopKey;
+        final CountDownLatch mLatch = new CountDownLatch(1);
+        final Map<String, String> mResults = new HashMap<>();
+
+        ClientChannel(String stopKey) {
+            mStopKey = stopKey;
+        }
+    }
+
+    private static Map<String, ClientChannel> mChannels = new HashMap<>();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        record(intent.getStringExtra(TestLogClient.EXTRA_LOG_TAG),
+                intent.getStringExtra(TestLogClient.EXTRA_KEY),
+                intent.getStringExtra(TestLogClient.EXTRA_VALUE));
+        return START_NOT_STICKY;
+    }
+
+    /**
+     * Prepare to receive results from a client with a specified tag.
+     *
+     * @param logtag Unique tag for the client.
+     * @param stopKey The key that signals that the client has completed all required actions.
+     */
+    public static void registerClient(String logtag, String stopKey) {
+        synchronized (mLock) {
+            if (mChannels.containsKey(logtag)) {
+                throw new IllegalArgumentException(logtag);
+            }
+            mChannels.put(logtag, new ClientChannel(stopKey));
+        }
+    }
+
+    /**
+     * Wait for the client to complete all required actions and return the results.
+     *
+     * @param logtag Unique tag for the client.
+     * @param timeoutMs Latch timeout in ms.
+     * @return The map of results from the client
+     */
+    public static Map<String, String> getResultsForClient(String logtag, int timeoutMs) {
+        Map<String, String> result = new HashMap<>();
+        CountDownLatch latch;
+        synchronized (mLock) {
+            if (!mChannels.containsKey(logtag)) {
+                return result;
+            }
+            latch = mChannels.get(logtag).mLatch;
+        }
+        try {
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ignore) {
+        }
+        synchronized (mLock) {
+            for (Map.Entry<String, String> e : mChannels.get(logtag).mResults.entrySet()) {
+                result.put(e.getKey(), e.getValue());
+            }
+        }
+        return result;
+    }
+
+    private static void record(String logtag, String key, String value) {
+        synchronized (mLock) {
+            if (!mChannels.containsKey(logtag)) {
+                Log.e(TAG, "Unexpected logtag: " + logtag);
+                return;
+            }
+            ClientChannel channel = mChannels.get(logtag);
+            channel.mResults.put(key, value);
+            if (key.equals(channel.mStopKey)) {
+                channel.mLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/tests/inputmethod/Android.mk b/tests/inputmethod/Android.mk
index 45824d4..e0c7a62 100644
--- a/tests/inputmethod/Android.mk
+++ b/tests/inputmethod/Android.mk
@@ -31,12 +31,18 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     compatibility-device-util \
-    ctstestrunner
+    ctstestrunner \
+    CtsMockInputMethod
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, src)
+
+LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/src
 
 LOCAL_PACKAGE_NAME := CtsInputMethodTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 11f008d..a5bc532 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -34,6 +34,50 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="android.view.inputmethod.cts.util.TestActivity"
+            android:label="TestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
+            android:label="StateInitializeActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
+        <!-- In order to test typical use cases, let this MockIME run in a separate process -->
+        <!--
+          TODO: Move this sevice definition into MockIME package and let the build system to merge
+          manifests once soong supports to build aar package.
+        -->
+        <service
+            android:name="com.android.cts.mockime.MockIme"
+            android:label="Mock IME"
+            android:permission="android.permission.BIND_INPUT_METHOD"
+            android:process=":mockime">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <meta-data
+                android:name="android.view.im"
+                android:resource="@xml/method" />
+        </service>
+
+        <!--
+          In order to test window-focus-stealing from other process, let this service run in a
+          separate process. -->
+        <service android:name="android.view.inputmethod.cts.util.WindowFocusStealerService"
+            android:process=":focusstealer"
+            android:exported="false">
+        </service>
+
     </application>
 
     <instrumentation
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index b696a52..f69fff5 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -16,9 +16,16 @@
 -->
 
 <configuration description="Config for CTS InputMethod test cases">
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="inputmethod" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <!--
+            TODO(yukawa): come up with a proper way to take care of devices that do not support
+            installable IMEs.  Ideally target_preparer should have an option to annotate required
+            features, e.g. android.software.input_methods so that we can conditionally install APKs
+            based on the feature supported in the target device.
+        -->
         <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/inputmethod/mockime/Android.mk b/tests/inputmethod/mockime/Android.mk
new file mode 100644
index 0000000..10f80a6
--- /dev/null
+++ b/tests/inputmethod/mockime/Android.mk
@@ -0,0 +1,33 @@
+# 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 := CtsMockInputMethod
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := current
+
+# 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_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+   android-support-annotations \
+   compatibility-device-util
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
new file mode 100644
index 0000000..db5c123
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
@@ -0,0 +1,81 @@
+/*
+ * 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.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+public final class ImeCommand {
+
+    private static final String NAME_KEY = "name";
+    private static final String ID_KEY = "id";
+    private static final String DISPATCH_TO_MAIN_THREAD_KEY = "dispatchToMainThread";
+    private static final String EXTRA_KEY = "extra";
+
+    @NonNull
+    private final String mName;
+    private final long mId;
+    private final boolean mDispatchToMainThread;
+    @NonNull
+    private final Bundle mExtras;
+
+    ImeCommand(@NonNull String name, long id, boolean dispatchToMainThread,
+            @NonNull Bundle extras) {
+        mName = name;
+        mId = id;
+        mDispatchToMainThread = dispatchToMainThread;
+        mExtras = extras;
+    }
+
+    private ImeCommand(@NonNull Bundle bundle) {
+        mName = bundle.getString(NAME_KEY);
+        mId = bundle.getLong(ID_KEY);
+        mDispatchToMainThread = bundle.getBoolean(DISPATCH_TO_MAIN_THREAD_KEY);
+        mExtras = bundle.getParcelable(EXTRA_KEY);
+    }
+
+    static ImeCommand fromBundle(@NonNull Bundle bundle) {
+        return new ImeCommand(bundle);
+    }
+
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString(NAME_KEY, mName);
+        bundle.putLong(ID_KEY, mId);
+        bundle.putBoolean(DISPATCH_TO_MAIN_THREAD_KEY, mDispatchToMainThread);
+        bundle.putParcelable(EXTRA_KEY, mExtras);
+        return bundle;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public boolean shouldDispatchToMainThread() {
+        return mDispatchToMainThread;
+    }
+
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
new file mode 100644
index 0000000..99e6c7a
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -0,0 +1,290 @@
+/*
+ * 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.mockime;
+
+import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+
+/**
+ * An immutable object that stores event happened in the {@link MockIme}.
+ */
+public final class ImeEvent {
+
+    private enum ReturnType {
+        Null,
+        KnownUnsupportedType,
+        Boolean,
+    }
+
+    private static ReturnType getReturnTypeFromObject(@Nullable Object object) {
+        if (object == null) {
+            return ReturnType.Null;
+        }
+        if (object instanceof AbstractInputMethodService.AbstractInputMethodImpl) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof View) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof Boolean) {
+            return ReturnType.Boolean;
+        }
+        throw new UnsupportedOperationException("Unsupported return type=" + object);
+    }
+
+    ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId,
+            boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime,
+            long exitWallTime, @NonNull ImeState enterState, @Nullable ImeState exitState,
+            @NonNull Bundle arguments, @Nullable Object returnValue) {
+        this(eventName, nestLevel, threadName, threadId, isMainThread, enterTimestamp,
+                exitTimestamp, enterWallTime, exitWallTime, enterState, exitState, arguments,
+                returnValue, getReturnTypeFromObject(returnValue));
+    }
+
+    private ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName,
+            int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp,
+            long enterWallTime, long exitWallTime, @NonNull ImeState enterState,
+            @Nullable ImeState exitState, @NonNull Bundle arguments, @Nullable Object returnValue,
+            @NonNull ReturnType returnType) {
+        mEventName = eventName;
+        mNestLevel = nestLevel;
+        mThreadName = threadName;
+        mThreadId = threadId;
+        mIsMainThread = isMainThread;
+        mEnterTimestamp = enterTimestamp;
+        mExitTimestamp = exitTimestamp;
+        mEnterWallTime = enterWallTime;
+        mExitWallTime = exitWallTime;
+        mEnterState = enterState;
+        mExitState = exitState;
+        mArguments = arguments;
+        mReturnValue = returnValue;
+        mReturnType = returnType;
+    }
+
+    @NonNull
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString("mEventName", mEventName);
+        bundle.putInt("mNestLevel", mNestLevel);
+        bundle.putString("mThreadName", mThreadName);
+        bundle.putInt("mThreadId", mThreadId);
+        bundle.putBoolean("mIsMainThread", mIsMainThread);
+        bundle.putLong("mEnterTimestamp", mEnterTimestamp);
+        bundle.putLong("mExitTimestamp", mExitTimestamp);
+        bundle.putLong("mEnterWallTime", mEnterWallTime);
+        bundle.putLong("mExitWallTime", mExitWallTime);
+        bundle.putBundle("mEnterState", mEnterState.toBundle());
+        bundle.putBundle("mExitState", mExitState != null ? mExitState.toBundle() : null);
+        bundle.putBundle("mArguments", mArguments);
+        bundle.putString("mReturnType", mReturnType.name());
+        switch (mReturnType) {
+            case Null:
+            case KnownUnsupportedType:
+                break;
+            case Boolean:
+                bundle.putBoolean("mReturnValue", getReturnBooleanValue());
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + mReturnType);
+        }
+        return bundle;
+    }
+
+    @NonNull
+    static ImeEvent fromBundle(@NonNull Bundle bundle) {
+        final String eventName = bundle.getString("mEventName");
+        final int nestLevel = bundle.getInt("mNestLevel");
+        final String threadName = bundle.getString("mThreadName");
+        final int threadId = bundle.getInt("mThreadId");
+        final boolean isMainThread = bundle.getBoolean("mIsMainThread");
+        final long enterTimestamp = bundle.getLong("mEnterTimestamp");
+        final long exitTimestamp = bundle.getLong("mExitTimestamp");
+        final long enterWallTime = bundle.getLong("mEnterWallTime");
+        final long exitWallTime = bundle.getLong("mExitWallTime");
+        final ImeState enterState = ImeState.fromBundle(bundle.getBundle("mEnterState"));
+        final ImeState exitState = ImeState.fromBundle(bundle.getBundle("mExitState"));
+        final Bundle arguments = bundle.getBundle("mArguments");
+        final Object result;
+        final ReturnType returnType = ReturnType.valueOf(bundle.getString("mReturnType"));
+        switch (returnType) {
+            case Null:
+            case KnownUnsupportedType:
+                result = null;
+                break;
+            case Boolean:
+                result = bundle.getBoolean("mReturnValue");
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + returnType);
+        }
+        return new ImeEvent(eventName, nestLevel, threadName,
+                threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
+                enterState, exitState, arguments, result, returnType);
+    }
+
+    /**
+     * Returns a string that represents the type of this event.
+     *
+     * <p>Examples: &quot;onCreate&quot;, &quot;onStartInput&quot;, ...</p>
+     *
+     * <p>TODO: Use enum type or something like that instead of raw String type.</p>
+     * @return A string that represents the type of this event.
+     */
+    @NonNull
+    public String getEventName() {
+        return mEventName;
+    }
+
+    /**
+     * Returns the nest level of this event.
+     *
+     * <p>For instance, when &quot;showSoftInput&quot; internally calls
+     * &quot;onStartInputView&quot;, the event for &quot;onStartInputView&quot; has 1 level higher
+     * nest level than &quot;showSoftInput&quot;.</p>
+     */
+    public int getNestLevel() {
+        return mNestLevel;
+    }
+
+    /**
+     * @return Name of the thread, where the event was consumed.
+     */
+    @NonNull
+    public String getThreadName() {
+        return mThreadName;
+    }
+
+    /**
+     * @return Thread ID (TID) of the thread, where the event was consumed.
+     */
+    public int getThreadId() {
+        return mThreadId;
+    }
+
+    /**
+     * @return {@code true} if the event was being consumed in the main thread.
+     */
+    public boolean isMainThread() {
+        return mIsMainThread;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler was called back.
+     */
+    public long getEnterTimestamp() {
+        return mEnterTimestamp;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler finished.
+     */
+    public long getExitTimestamp() {
+        return mExitTimestamp;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler was called back.
+     */
+    public long getEnterWallTime() {
+        return mEnterWallTime;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler finished.
+     */
+    public long getExitWallTime() {
+        return mExitWallTime;
+    }
+
+    /**
+     * @return IME state snapshot taken when the corresponding event handler was called back.
+     */
+    @NonNull
+    public ImeState getEnterState() {
+        return mEnterState;
+    }
+
+    /**
+     * @return IME state snapshot taken when the corresponding event handler finished.
+     */
+    @Nullable
+    public ImeState getExitState() {
+        return mExitState;
+    }
+
+    /**
+     * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
+     */
+    @NonNull
+    public Bundle getArguments() {
+        return mArguments;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Boolean}
+     */
+    public boolean getReturnBooleanValue() {
+        if (mReturnType == ReturnType.Null) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Boolean) {
+            throw new ClassCastException();
+        }
+        return (Boolean) mReturnValue;
+    }
+
+    /**
+     * @return {@code true} if the event is issued when the event starts, not when the event
+     * finishes.
+     */
+    public boolean isEnterEvent() {
+        return mExitState == null;
+    }
+
+    @NonNull
+    private final String mEventName;
+    private final int mNestLevel;
+    @NonNull
+    private final String mThreadName;
+    private final int mThreadId;
+    private final boolean mIsMainThread;
+    private final long mEnterTimestamp;
+    private final long mExitTimestamp;
+    private final long mEnterWallTime;
+    private final long mExitWallTime;
+    @NonNull
+    private final ImeState mEnterState;
+    @Nullable
+    private final ImeState mExitState;
+    @NonNull
+    private final Bundle mArguments;
+    @Nullable
+    private final Object mReturnValue;
+    @NonNull
+    private final ReturnType mReturnType;
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
new file mode 100644
index 0000000..c912897
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
@@ -0,0 +1,251 @@
+/*
+ * 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.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+import android.view.inputmethod.EditorInfo;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A utility class that provides basic query operations and wait primitives for a series of
+ * {@link ImeEvent} sent from the {@link MockIme}.
+ *
+ * <p>All public methods are not thread-safe.</p>
+ */
+public final class ImeEventStream {
+
+    private static final String LONG_LONG_SPACES = "                                        ";
+
+    private static DateTimeFormatter sSimpleDateTimeFormatter =
+            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+    @NonNull
+    private final Supplier<ImeEventArray> mEventSupplier;
+    private int mCurrentPosition;
+
+    ImeEventStream(@NonNull Supplier<ImeEventArray> supplier) {
+        this(supplier, 0 /* position */);
+    }
+
+    private ImeEventStream(@NonNull Supplier<ImeEventArray> supplier, int position) {
+        mEventSupplier = supplier;
+        mCurrentPosition = position;
+    }
+
+    /**
+     * Create a copy that starts from the same event position of this stream. Once a copy is created
+     * further event position change on this stream will not affect the copy.
+     *
+     * @return A new copy of this stream
+     */
+    public ImeEventStream copy() {
+        return new ImeEventStream(mEventSupplier, mCurrentPosition);
+    }
+
+    /**
+     * Advances the current event position by skipping events.
+     *
+     * @param length number of events to be skipped
+     * @throws IllegalArgumentException {@code length} is negative
+     */
+    public void skip(@IntRange(from = 0) int length) {
+        if (length < 0) {
+            throw new IllegalArgumentException("length cannot be negative: " + length);
+        }
+        mCurrentPosition += length;
+    }
+
+    /**
+     * Advances the current event position to the next to the last position.
+     */
+    public void skipAll() {
+        mCurrentPosition = mEventSupplier.get().mLength;
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event without moving the current
+     * event position.</p>
+     *
+     * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+     * current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<ImeEvent> findFirst(Predicate<ImeEvent> condition) {
+        final ImeEventArray latest = mEventSupplier.get();
+        int index = mCurrentPosition;
+        while (true) {
+            if (index >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[index])) {
+                return Optional.of(latest.mArray[index]);
+            }
+            ++index;
+        }
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event and set the current event
+     * position to that event.</p>
+     *
+     * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+     * current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<ImeEvent> seekToFirst(Predicate<ImeEvent> condition) {
+        final ImeEventArray latest = mEventSupplier.get();
+        while (true) {
+            if (mCurrentPosition >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[mCurrentPosition])) {
+                return Optional.of(latest.mArray[mCurrentPosition]);
+            }
+            ++mCurrentPosition;
+        }
+    }
+
+    private static void dumpEvent(@NonNull StringBuilder sb, @NonNull ImeEvent event,
+            boolean fused) {
+        final String indentation = getWhiteSpaces(event.getNestLevel() * 2 + 2);
+        final long wallTime =
+                fused ? event.getEnterWallTime() :
+                        event.isEnterEvent() ? event.getEnterWallTime() : event.getExitWallTime();
+        sb.append(sSimpleDateTimeFormatter.format(Instant.ofEpochMilli(wallTime)))
+                .append("  ")
+                .append(String.format("%5d", event.getThreadId()))
+                .append(indentation);
+        sb.append(fused ? "" : event.isEnterEvent() ? "[" : "]");
+        if (fused || event.isEnterEvent()) {
+            sb.append(event.getEventName())
+                    .append(':')
+                    .append(" args=");
+            dumpBundle(sb, event.getArguments());
+        }
+        sb.append('\n');
+    }
+
+    /**
+     * @return Debug info as a {@link String}.
+     */
+    public String dump() {
+        final ImeEventArray latest = mEventSupplier.get();
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ImeEventStream:\n");
+        sb.append("  latest: array[").append(latest.mArray.length).append("] + {\n");
+        for (int i = 0; i < latest.mLength; ++i) {
+            // To compress the dump message, if the current event is an enter event and the next
+            // one is a corresponding exit event, we unify the output.
+            final boolean fused = areEnterExitPairedMessages(latest, i);
+            if (i == mCurrentPosition || (fused && ((i + 1) == mCurrentPosition))) {
+                sb.append("  ======== CurrentPosition ========  \n");
+            }
+            dumpEvent(sb, latest.mArray[fused ? ++i : i], fused);
+        }
+        if (mCurrentPosition >= latest.mLength) {
+            sb.append("  ======== CurrentPosition ========  \n");
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+
+    /**
+     * @param array event array to be checked
+     * @param i index to be checked
+     * @return {@code true} if {@code array.mArray[i]} and {@code array.mArray[i + 1]} are two
+     *         paired events.
+     */
+    private static boolean areEnterExitPairedMessages(@NonNull ImeEventArray array,
+            @IntRange(from = 0) int i) {
+        return array.mArray[i] != null
+                && array.mArray[i].isEnterEvent()
+                && (i + 1) < array.mLength
+                && array.mArray[i + 1] != null
+                && array.mArray[i].getEventName().equals(array.mArray[i + 1].getEventName())
+                && array.mArray[i].getEnterTimestamp() == array.mArray[i + 1].getEnterTimestamp();
+    }
+
+    /**
+     * @param length length of the requested white space string
+     * @return {@link String} object whose length is {@code length}
+     */
+    private static String getWhiteSpaces(@IntRange(from = 0) final int length) {
+        if (length < LONG_LONG_SPACES.length()) {
+            return LONG_LONG_SPACES.substring(0, length);
+        }
+        final char[] indentationChars = new char[length];
+        Arrays.fill(indentationChars, ' ');
+        return new String(indentationChars);
+    }
+
+    private static void dumpBundle(@NonNull StringBuilder sb, @NonNull Bundle bundle) {
+        sb.append('{');
+        boolean first = true;
+        for (String key : bundle.keySet()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(' ');
+            }
+            final Object object = bundle.get(key);
+            sb.append(key);
+            sb.append('=');
+            if (object instanceof EditorInfo) {
+                final EditorInfo info = (EditorInfo) object;
+                sb.append("EditorInfo{packageName=").append(info.packageName);
+                sb.append(" fieldId=").append(info.fieldId);
+                sb.append(" hintText=").append(info.hintText);
+                sb.append(" privateImeOptions=").append(info.privateImeOptions);
+                sb.append("}");
+            } else {
+                sb.append(object);
+            }
+        }
+        sb.append('}');
+    }
+
+    static class ImeEventArray {
+        @NonNull
+        public final ImeEvent[] mArray;
+        public final int mLength;
+        ImeEventArray(ImeEvent[] array, int length) {
+            mArray = array;
+            mLength = length;
+        }
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
new file mode 100644
index 0000000..0954533
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -0,0 +1,293 @@
+/*
+ * 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.mockime;
+
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.view.inputmethod.InputBinding;
+
+import java.util.Optional;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+/**
+ * A set of utility methods to avoid boilerplate code when writing end-to-end tests.
+ */
+public final class ImeEventStreamTestUtils {
+    private static final long TIME_SLICE = 50;  // msec
+
+    /**
+     * Cannot be instantiated
+     */
+    private ImeEventStreamTestUtils() {}
+
+    /**
+     * Behavior mode of {@link #expectEvent(ImeEventStream, Predicate, EventFilterMode, long)}
+     */
+    public enum EventFilterMode {
+        /**
+         * All {@link ImeEvent} events should be checked
+         */
+        CHECK_ALL,
+        /**
+         * Only events that return {@code true} from {@link ImeEvent#isEnterEvent()} should be
+         * checked
+         */
+        CHECK_ENTER_EVENT_ONLY,
+        /**
+         * Only events that return {@code false} from {@link ImeEvent#isEnterEvent()} should be
+         * checked
+         */
+        CHECK_EXIT_EVENT_ONLY,
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code expectEventEnter} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, long timeout) throws TimeoutException {
+        return expectEvent(stream, condition, EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, EventFilterMode filterMode, long timeout)
+            throws TimeoutException {
+        try {
+            Optional<ImeEvent> result;
+            while (true) {
+                if (timeout < 0) {
+                    throw new TimeoutException(
+                            "event not found within the timeout: " + stream.dump());
+                }
+                final Predicate<ImeEvent> combinedCondition;
+                switch (filterMode) {
+                    case CHECK_ALL:
+                        combinedCondition = condition;
+                        break;
+                    case CHECK_ENTER_EVENT_ONLY:
+                        combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                        break;
+                    case CHECK_EXIT_EVENT_ONLY:
+                        combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+                }
+                result = stream.seekToFirst(combinedCondition);
+                if (result.isPresent()) {
+                    break;
+                }
+                Thread.sleep(TIME_SLICE);
+                timeout -= TIME_SLICE;
+            }
+            final ImeEvent event = result.get();
+            if (event == null) {
+                throw new NullPointerException("found event is null: " + stream.dump());
+            }
+            stream.skip(1);
+            return event;
+        } catch (InterruptedException e) {
+            throw new RuntimeException("expectEvent failed: " + stream.dump(), e);
+        }
+    }
+
+    /**
+     * Wait until an event that matches the given command is consumed by the {@link MockIme}.
+     *
+     * <p>For convenience, this method automatically filter out enter events (events that return
+     * {@code true} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code expectCommandConsumed} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param command {@link ImeCommand} to be waited for.
+     * @param timeout timeout in millisecond
+     * @return {@link ImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static ImeEvent expectCommand(@NonNull ImeEventStream stream,
+            @NonNull ImeCommand command, long timeout) throws TimeoutException {
+        final Predicate<ImeEvent> predicate = event -> {
+            if (!TextUtils.equals("onHandleCommand", event.getEventName())) {
+                return false;
+            }
+            final ImeCommand eventCommand =
+                    ImeCommand.fromBundle(event.getArguments().getBundle("command"));
+            return eventCommand.getId() == command.getId();
+        };
+        return expectEvent(stream, predicate, EventFilterMode.CHECK_EXIT_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link ImeEvent#isEnterEvent()}.</p>
+     *
+     * <p>TODO: Consider renaming this to {@code notExpectEventEnter} or something like that.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, long timeout) {
+        notExpectEvent(stream, condition, EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, EventFilterMode filterMode, long timeout) {
+        final Predicate<ImeEvent> combinedCondition;
+        switch (filterMode) {
+            case CHECK_ALL:
+                combinedCondition = condition;
+                break;
+            case CHECK_ENTER_EVENT_ONLY:
+                combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                break;
+            case CHECK_EXIT_EVENT_ONLY:
+                combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+        }
+        try {
+            while (true) {
+                if (timeout < 0) {
+                    return;
+                }
+                if (stream.findFirst(combinedCondition).isPresent()) {
+                    throw new AssertionError("notExpectEvent failed: " + stream.dump());
+                }
+                Thread.sleep(TIME_SLICE);
+                timeout -= TIME_SLICE;
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+        }
+    }
+
+    /**
+     * A specialized version of {@link #expectEvent(ImeEventStream, Predicate, long)} to wait for
+     * {@link android.view.inputmethod.InputMethod#bindInput(InputBinding)}.
+     *
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param targetProcessPid PID to be matched to {@link InputBinding#getPid()}
+     * @param timeout timeout in millisecond
+     * @throws TimeoutException when "bindInput" is not called within {@code timeout} msec
+     */
+    public static void expectBindInput(@NonNull ImeEventStream stream, int targetProcessPid,
+            long timeout) throws TimeoutException {
+        expectEvent(stream, event -> {
+            if (!TextUtils.equals("bindInput", event.getEventName())) {
+                return false;
+            }
+            final InputBinding binding = event.getArguments().getParcelable("binding");
+            return binding.getPid() == targetProcessPid;
+        }, EventFilterMode.CHECK_EXIT_EVENT_ONLY,  timeout);
+    }
+
+    /**
+     * Waits until {@code MockIme} does not send {@code "onInputViewLayoutChanged"} event
+     * for a certain period of time ({@code stableThresholdTime} msec).
+     *
+     * <p>When this returns non-null {@link ImeLayoutInfo}, the stream position will be set to
+     * the next event of the returned layout event.  Otherwise this method does not change stream
+     * position.</p>
+     * @param stream {@link ImeEventStream} to be checked.
+     * @param stableThresholdTime threshold time to consider that {@link MockIme}'s layout is
+     *                            stable, in millisecond
+     * @return last {@link ImeLayoutInfo} if {@link MockIme} sent one or more
+     *         {@code "onInputViewLayoutChanged"} event.  Otherwise {@code null}
+     */
+    public static ImeLayoutInfo waitForInputViewLayoutStable(@NonNull ImeEventStream stream,
+            long stableThresholdTime) {
+        ImeLayoutInfo lastLayout = null;
+        final Predicate<ImeEvent> layoutFilter = event ->
+                !event.isEnterEvent() && event.getEventName().equals("onInputViewLayoutChanged");
+        try {
+            long deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+            while (true) {
+                if (deadline < SystemClock.elapsedRealtime()) {
+                    return lastLayout;
+                }
+                final Optional<ImeEvent> event = stream.seekToFirst(layoutFilter);
+                if (event.isPresent()) {
+                    // Remember the last event and extend the deadline again.
+                    lastLayout = ImeLayoutInfo.readFromBundle(event.get().getArguments());
+                    deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+                    stream.skip(1);
+                }
+                Thread.sleep(TIME_SLICE);
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+        }
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
new file mode 100644
index 0000000..ef62b9a
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
@@ -0,0 +1,197 @@
+/*
+ * 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.mockime;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowInsets;
+
+/**
+ * A collection of layout-related information when
+ * {@link View.OnLayoutChangeListener#onLayoutChange(View, int, int, int, int, int, int, int, int)}
+ * is called back for the input view (the view returned from {@link MockIme#onCreateInputView()}).
+ */
+public final class ImeLayoutInfo {
+
+    private static final String NEW_LAYOUT_KEY = "newLayout";
+    private static final String OLD_LAYOUT_KEY = "oldLayout";
+    private static final String VIEW_ORIGIN_ON_SCREEN_KEY = "viewOriginOnScreen";
+    private static final String DISPLAY_SIZE_KEY = "displaySize";
+    private static final String SYSTEM_WINDOW_INSET_KEY = "systemWindowInset";
+    private static final String STABLE_INSET_KEY = "stableInset";
+
+    @NonNull
+    private final Rect mNewLayout;
+    @NonNull
+    private final Rect mOldLayout;
+    @Nullable
+    private Point mViewOriginOnScreen;
+    @Nullable
+    private Point mDisplaySize;
+    @Nullable
+    private Rect mSystemWindowInset;
+    @Nullable
+    private Rect mStableInset;
+
+    /**
+     * Returns the bounding box of the {@link View} passed to
+     * {@link android.inputmethodservice.InputMethodService#onCreateInputView()} in screen
+     * coordinates.
+     *
+     * <p>Currently this method assumes that no {@link View} in the hierarchy uses
+     * transformations such as {@link View#setRotation(float)}.</p>
+     *
+     * @return Region in screen coordinates.
+     */
+    @Nullable
+    public Rect getInputViewBoundsInScreen() {
+        return new Rect(
+                mViewOriginOnScreen.x, mViewOriginOnScreen.y,
+                mViewOriginOnScreen.x + mNewLayout.width(),
+                mViewOriginOnScreen.y + mNewLayout.height());
+    }
+
+    /**
+     * Returns the screen area in screen coordinates that does not overlap with the system
+     * window inset, which represents the area of a full-screen window that is partially or
+     * fully obscured by the status bar, navigation bar, IME or other system windows.
+     *
+     * <p>May return {@code null} when this information is not yet ready.</p>
+     *
+     * @return Region in screen coordinates. {@code null} when it is not available
+     *
+     * @see WindowInsets#hasSystemWindowInsets()
+     * @see WindowInsets#getSystemWindowInsetBottom()
+     * @see WindowInsets#getSystemWindowInsetLeft()
+     * @see WindowInsets#getSystemWindowInsetRight()
+     * @see WindowInsets#getSystemWindowInsetTop()
+     */
+    @Nullable
+    public Rect getScreenRectWithoutSystemWindowInset() {
+        if (mDisplaySize == null) {
+            return null;
+        }
+        if (mSystemWindowInset == null) {
+            return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        }
+        return new Rect(mSystemWindowInset.left, mSystemWindowInset.top,
+                mDisplaySize.x - mSystemWindowInset.right,
+                mDisplaySize.y - mSystemWindowInset.bottom);
+    }
+
+    /**
+     * Returns the screen area in screen coordinates that does not overlap with the stable
+     * inset, which represents the area of a full-screen window that <b>may</b> be partially or
+     * fully obscured by the system UI elements.
+     *
+     * <p>May return {@code null} when this information is not yet ready.</p>
+     *
+     * @return Region in screen coordinates. {@code null} when it is not available
+     *
+     * @see WindowInsets#hasStableInsets()
+     * @see WindowInsets#getStableInsetBottom()
+     * @see WindowInsets#getStableInsetLeft()
+     * @see WindowInsets#getStableInsetRight()
+     * @see WindowInsets#getStableInsetTop()
+     */
+    @Nullable
+    public Rect getScreenRectWithoutStableInset() {
+        if (mDisplaySize == null) {
+            return null;
+        }
+        if (mStableInset == null) {
+            return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        }
+        return new Rect(mStableInset.left, mStableInset.top,
+                mDisplaySize.x - mStableInset.right,
+                mDisplaySize.y - mStableInset.bottom);
+    }
+
+    ImeLayoutInfo(@NonNull Rect newLayout, @NonNull Rect oldLayout,
+            @NonNull Point viewOriginOnScreen, @Nullable Point displaySize,
+            @Nullable Rect systemWindowInset, @Nullable Rect stableInset) {
+        mNewLayout = new Rect(newLayout);
+        mOldLayout = new Rect(oldLayout);
+        mViewOriginOnScreen = new Point(viewOriginOnScreen);
+        mDisplaySize = new Point(displaySize);
+        mSystemWindowInset = systemWindowInset;
+        mStableInset = stableInset;
+    }
+
+    void writeToBundle(@NonNull Bundle bundle) {
+        bundle.putParcelable(NEW_LAYOUT_KEY, mNewLayout);
+        bundle.putParcelable(OLD_LAYOUT_KEY, mOldLayout);
+        bundle.putParcelable(VIEW_ORIGIN_ON_SCREEN_KEY, mViewOriginOnScreen);
+        bundle.putParcelable(DISPLAY_SIZE_KEY, mDisplaySize);
+        bundle.putParcelable(SYSTEM_WINDOW_INSET_KEY, mSystemWindowInset);
+        bundle.putParcelable(STABLE_INSET_KEY, mStableInset);
+    }
+
+    static ImeLayoutInfo readFromBundle(@NonNull Bundle bundle) {
+        final Rect newLayout = bundle.getParcelable(NEW_LAYOUT_KEY);
+        final Rect oldLayout = bundle.getParcelable(OLD_LAYOUT_KEY);
+        final Point viewOrigin = bundle.getParcelable(VIEW_ORIGIN_ON_SCREEN_KEY);
+        final Point displaySize = bundle.getParcelable(DISPLAY_SIZE_KEY);
+        final Rect systemWindowInset = bundle.getParcelable(SYSTEM_WINDOW_INSET_KEY);
+        final Rect stableInset = bundle.getParcelable(STABLE_INSET_KEY);
+
+        return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+                stableInset);
+    }
+
+    static ImeLayoutInfo fromLayoutListenerCallback(View v, int left, int top, int right,
+            int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+        final Rect newLayout = new Rect(left, top, right, bottom);
+        final Rect oldLayout = new Rect(oldLeft, oldTop, oldRight, oldBottom);
+        final int[] viewOriginArray = new int[2];
+        v.getLocationOnScreen(viewOriginArray);
+        final Point viewOrigin = new Point(viewOriginArray[0], viewOriginArray[1]);
+        final Display display = v.getDisplay();
+        final Point displaySize;
+        if (display != null) {
+            displaySize = new Point();
+            display.getRealSize(displaySize);
+        } else {
+            displaySize = null;
+        }
+        final WindowInsets windowInsets = v.getRootWindowInsets();
+        final Rect systemWindowInset;
+        if (windowInsets != null && windowInsets.hasSystemWindowInsets()) {
+            systemWindowInset = new Rect(
+                    windowInsets.getSystemWindowInsetLeft(), windowInsets.getSystemWindowInsetTop(),
+                    windowInsets.getSystemWindowInsetRight(),
+                    windowInsets.getSystemWindowInsetBottom());
+        } else {
+            systemWindowInset = null;
+        }
+        final Rect stableInset;
+        if (windowInsets != null && windowInsets.hasStableInsets()) {
+            stableInset = new Rect(
+                    windowInsets.getStableInsetLeft(), windowInsets.getStableInsetTop(),
+                    windowInsets.getStableInsetRight(), windowInsets.getStableInsetBottom());
+        } else {
+            stableInset = null;
+        }
+        return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+                stableInset);
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
new file mode 100644
index 0000000..d6bfb22
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -0,0 +1,207 @@
+/*
+ * 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.mockime;
+
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * An immutable data store to control the behavior of {@link MockIme}.
+ */
+public class ImeSettings {
+
+    @NonNull
+    private final String mEventCallbackActionName;
+
+    private static final String BACKGROUND_COLOR_KEY = "BackgroundColor";
+    private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor";
+    private static final String INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET =
+            "InputViewHeightWithoutSystemWindowInset";
+    private static final String WINDOW_FLAGS = "WindowFlags";
+    private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
+    private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+    private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+    private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
+            "HardKeyboardConfigurationBehaviorAllowed";
+
+    @NonNull
+    private final PersistableBundle mBundle;
+
+    ImeSettings(@NonNull Parcel parcel) {
+        mEventCallbackActionName = parcel.readString();
+        mBundle = parcel.readPersistableBundle();
+    }
+
+    @Nullable
+    String getEventCallbackActionName() {
+        return mEventCallbackActionName;
+    }
+
+    public boolean fullscreenModeAllowed(boolean defaultValue) {
+        return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
+    }
+
+    @ColorInt
+    public int getBackgroundColor(@ColorInt int defaultColor) {
+        return mBundle.getInt(BACKGROUND_COLOR_KEY, defaultColor);
+    }
+
+    public boolean hasNavigationBarColor() {
+        return mBundle.keySet().contains(NAVIGATION_BAR_COLOR_KEY);
+    }
+
+    @ColorInt
+    public int getNavigationBarColor() {
+        return mBundle.getInt(NAVIGATION_BAR_COLOR_KEY);
+    }
+
+    public int getInputViewHeightWithoutSystemWindowInset(int defaultHeight) {
+        return mBundle.getInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, defaultHeight);
+    }
+
+    public int getWindowFlags(int defaultFlags) {
+        return mBundle.getInt(WINDOW_FLAGS, defaultFlags);
+    }
+
+    public int getWindowFlagsMask(int defaultFlags) {
+        return mBundle.getInt(WINDOW_FLAGS_MASK, defaultFlags);
+    }
+
+    public int getInputViewSystemUiVisibility(int defaultFlags) {
+        return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
+    }
+
+    public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) {
+        return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
+    }
+
+    static void writeToParcel(@NonNull Parcel parcel, @NonNull String eventCallbackActionName,
+            @Nullable Builder builder) {
+        parcel.writeString(eventCallbackActionName);
+        if (builder != null) {
+            parcel.writePersistableBundle(builder.mBundle);
+        } else {
+            parcel.writePersistableBundle(PersistableBundle.EMPTY);
+        }
+    }
+
+    /**
+     * The builder class for {@link ImeSettings}.
+     */
+    public static final class Builder {
+        private final PersistableBundle mBundle = new PersistableBundle();
+
+        /**
+         * Controls whether fullscreen mode is allowed or not.
+         *
+         * <p>By default, fullscreen mode is not allowed in {@link MockIme}.</p>
+         *
+         * @param allowed {@code true} if fullscreen mode is allowed
+         * @see MockIme#onEvaluateFullscreenMode()
+         */
+        public Builder setFullscreenModeAllowed(boolean allowed) {
+            mBundle.putBoolean(FULLSCREEN_MODE_ALLOWED, allowed);
+            return this;
+        }
+
+        /**
+         * Sets the background color of the {@link MockIme}.
+         * @param color background color to be used
+         */
+        public Builder setBackgroundColor(@ColorInt int color) {
+            mBundle.putInt(BACKGROUND_COLOR_KEY, color);
+            return this;
+        }
+
+        /**
+         * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}.
+         *
+         * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)}
+         * @see android.view.View
+         */
+        public Builder setNavigationBarColor(@ColorInt int color) {
+            mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color);
+            return this;
+        }
+
+        /**
+         * Sets the input view height measured from the bottom system window inset.
+         * @param height height of the soft input view. This does not include the system window
+         *               inset such as navigation bar
+         */
+        public Builder setInputViewHeightWithoutSystemWindowInset(int height) {
+            mBundle.putInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, height);
+            return this;
+        }
+
+        /**
+         * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of
+         * the main {@link MockIme} window.
+         *
+         * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set,
+         * {@link MockIme} tries to render the navigation bar by itself.</p>
+         *
+         * @param flags flags to be specified
+         * @param flagsMask mask bits that specify what bits need to be cleared before setting
+         *                  {@code flags}
+         * @see android.view.WindowManager
+         */
+        public Builder setWindowFlags(int flags, int flagsMask) {
+            mBundle.putInt(WINDOW_FLAGS, flags);
+            mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask);
+            return this;
+        }
+
+        /**
+         * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of
+         * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}).
+         *
+         * @param visibilityFlags flags to be specified
+         * @see android.view.View
+         */
+        public Builder setInputViewSystemUiVisibility(int visibilityFlags) {
+            mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags);
+            return this;
+        }
+
+        /**
+         * Controls whether {@link MockIme} is allowed to change the behavior based on
+         * {@link android.content.res.Configuration#keyboard} and
+         * {@link android.content.res.Configuration#hardKeyboardHidden}.
+         *
+         * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and
+         * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)}
+         * change their behaviors when a hardware keyboard is attached.  This is confusing when
+         * writing tests so by default {@link MockIme} tries to cancel those behaviors.  This
+         * settings re-enables such a behavior.</p>
+         *
+         * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when
+         *                a hardware keyboard is attached
+         *
+         * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()
+         * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)
+         */
+        public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) {
+            mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed);
+            return this;
+        }
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
new file mode 100644
index 0000000..841521c
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * An immutable object that stores several runtime state of {@link MockIme}.
+ */
+public final class ImeState {
+    private final boolean mHasInputBinding;
+    private final boolean mHasDummyInputConnection;
+
+    /**
+     * @return {@code true} if {@link MockIme#getCurrentInputBinding()} returned non-null
+     *         {@link android.view.inputmethod.InputBinding} when this snapshot was taken.
+     */
+    public boolean hasInputBinding() {
+        return mHasInputBinding;
+    }
+
+    /**
+     * @return {@code true} if {@link MockIme#getCurrentInputConnection()} returned non-dummy
+     *         {@link android.view.inputmethod.InputConnection} when this snapshot was taken.
+     */
+    public boolean hasDummyInputConnection() {
+        return mHasDummyInputConnection;
+    }
+
+    ImeState(boolean hasInputBinding, boolean hasDummyInputConnection) {
+        mHasInputBinding = hasInputBinding;
+        mHasDummyInputConnection = hasDummyInputConnection;
+    }
+
+    @NonNull
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("mHasInputBinding", mHasInputBinding);
+        bundle.putBoolean("mHasDummyInputConnection", mHasDummyInputConnection);
+        return bundle;
+    }
+
+    @Nullable
+    static ImeState fromBundle(@Nullable Bundle bundle) {
+        if (bundle == null) {
+            return null;
+        }
+        final boolean hasInputBinding = bundle.getBoolean("mHasInputBinding");
+        final boolean hasDummyInputConnection = bundle.getBoolean("mHasDummyInputConnection");
+        return new ImeState(hasInputBinding, hasDummyInputConnection);
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
new file mode 100644
index 0000000..ac46df3
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -0,0 +1,698 @@
+/*
+ * 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.mockime;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import static com.android.cts.mockime.MockImeSession.MOCK_IME_SETTINGS_FILE;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.annotation.AnyThread;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Mock IME for end-to-end tests.
+ */
+public final class MockIme extends InputMethodService {
+
+    private static final String TAG = "MockIme";
+
+    static ComponentName getComponentName(@NonNull String packageName) {
+        return new ComponentName(packageName, MockIme.class.getName());
+    }
+
+    static String getImeId(@NonNull String packageName) {
+        return new ComponentName(packageName, MockIme.class.getName()).flattenToShortString();
+    }
+
+    static String getCommandActionName(@NonNull String eventActionName) {
+        return eventActionName + ".command";
+    }
+
+    private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
+
+    private final Handler mMainHandler = new Handler();
+
+    private static final class CommandReceiver extends BroadcastReceiver {
+        @NonNull
+        private final String mActionName;
+        @NonNull
+        private final Consumer<ImeCommand> mOnReceiveCommand;
+
+        CommandReceiver(@NonNull String actionName,
+                @NonNull Consumer<ImeCommand> onReceiveCommand) {
+            mActionName = actionName;
+            mOnReceiveCommand = onReceiveCommand;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                mOnReceiveCommand.accept(ImeCommand.fromBundle(intent.getExtras()));
+            }
+        }
+    }
+
+    @WorkerThread
+    private void onReceiveCommand(@NonNull ImeCommand command) {
+        getTracer().onReceiveCommand(command, () -> {
+            if (command.shouldDispatchToMainThread()) {
+                mMainHandler.post(() -> onHandleCommand(command));
+            } else {
+                onHandleCommand(command);
+            }
+        });
+    }
+
+    @AnyThread
+    private void onHandleCommand(@NonNull ImeCommand command) {
+        getTracer().onHandleCommand(command, () -> {
+            if (command.shouldDispatchToMainThread()) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new IllegalStateException("command " + command
+                            + " should be handled on the main thread");
+                }
+                switch (command.getName()) {
+                    case "commitText": {
+                        final CharSequence text = command.getExtras().getString("text");
+                        final int newCursorPosition =
+                                command.getExtras().getInt("newCursorPosition");
+                        getCurrentInputConnection().commitText(text, newCursorPosition);
+                        break;
+                    }
+                    case "reportLanguageHint": {
+                        final LocaleList languageHint =
+                                command.getExtras().getParcelable("languageHint");
+                        getCurrentInputConnection().reportLanguageHint(languageHint);
+                        break;
+                    }
+                    case "setBackDisposition": {
+                        final int backDisposition =
+                                command.getExtras().getInt("backDisposition");
+                        setBackDisposition(backDisposition);
+                        break;
+                    }
+                    case "requestHideSelf": {
+                        final int flags = command.getExtras().getInt("flags");
+                        requestHideSelf(flags);
+                        break;
+                    }
+                    case "requestShowSelf": {
+                        final int flags = command.getExtras().getInt("flags");
+                        requestShowSelf(flags);
+                        break;
+                    }
+                }
+            }
+        });
+    }
+
+    @Nullable
+    private CommandReceiver mCommandReceiver;
+
+    @Nullable
+    private ImeSettings mSettings;
+
+    private final AtomicReference<String> mImeEventActionName = new AtomicReference<>();
+
+    @Nullable
+    String getImeEventActionName() {
+        return mImeEventActionName.get();
+    }
+
+    private class MockInputMethodImpl extends InputMethodImpl {
+        @Override
+        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+            getTracer().showSoftInput(flags, resultReceiver,
+                    () -> super.showSoftInput(flags, resultReceiver));
+        }
+
+        @Override
+        public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+            getTracer().hideSoftInput(flags, resultReceiver,
+                    () -> super.hideSoftInput(flags, resultReceiver));
+        }
+
+        @Override
+        public void attachToken(IBinder token) {
+            getTracer().attachToken(token, () -> super.attachToken(token));
+        }
+
+        @Override
+        public void bindInput(InputBinding binding) {
+            getTracer().bindInput(binding, () -> super.bindInput(binding));
+        }
+
+        @Override
+        public void unbindInput() {
+            getTracer().unbindInput(() -> super.unbindInput());
+        }
+    }
+
+    @Nullable
+    private ImeSettings readSettings() {
+        try (InputStream is = openFileInput(MOCK_IME_SETTINGS_FILE)) {
+            Parcel parcel = null;
+            try {
+                parcel = Parcel.obtain();
+                final byte[] buffer = new byte[4096];
+                while (true) {
+                    final int numRead = is.read(buffer);
+                    if (numRead <= 0) {
+                        break;
+                    }
+                    parcel.unmarshall(buffer, 0, numRead);
+                }
+                parcel.setDataPosition(0);
+                return new ImeSettings(parcel);
+            } finally {
+                if (parcel != null) {
+                    parcel.recycle();
+                }
+            }
+        } catch (IOException e) {
+        }
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        // Initialize minimum settings to send events in Tracer#onCreate().
+        mSettings = readSettings();
+        if (mSettings == null) {
+            throw new IllegalStateException("Settings file is not found. "
+                    + "Make sure MockImeSession.create() is used to launch Mock IME.");
+        }
+        mImeEventActionName.set(mSettings.getEventCallbackActionName());
+
+        getTracer().onCreate(() -> {
+            super.onCreate();
+            mHandlerThread.start();
+            final String actionName = getCommandActionName(mSettings.getEventCallbackActionName());
+            mCommandReceiver = new CommandReceiver(actionName, this::onReceiveCommand);
+            registerReceiver(mCommandReceiver,
+                    new IntentFilter(actionName), null /* broadcastPermission */,
+                    new Handler(mHandlerThread.getLooper()));
+
+            final int windowFlags = mSettings.getWindowFlags(0);
+            final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
+            if (windowFlags != 0 || windowFlagsMask != 0) {
+                final int prevFlags = getWindow().getWindow().getAttributes().flags;
+                getWindow().getWindow().setFlags(windowFlags, windowFlagsMask);
+                // For some reasons, seems that we need to post another requestLayout() when
+                // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS bit is changed.
+                // TODO: Investigate the reason.
+                if ((windowFlagsMask & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+                    final boolean hadFlag = (prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+                    final boolean hasFlag = (windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+                    if (hadFlag != hasFlag) {
+                        final View decorView = getWindow().getWindow().getDecorView();
+                        decorView.post(() -> decorView.requestLayout());
+                    }
+                }
+            }
+
+            if (mSettings.hasNavigationBarColor()) {
+                getWindow().getWindow().setNavigationBarColor(mSettings.getNavigationBarColor());
+            }
+        });
+    }
+
+    @Override
+    public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) {
+        getTracer().onConfigureWindow(win, isFullscreen, isCandidatesOnly,
+                () -> super.onConfigureWindow(win, isFullscreen, isCandidatesOnly));
+    }
+
+    @Override
+    public boolean onEvaluateFullscreenMode() {
+        return getTracer().onEvaluateFullscreenMode(() ->
+                mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
+    }
+
+    private static final class KeyboardLayoutView extends LinearLayout {
+        @NonNull
+        private final ImeSettings mSettings;
+        @NonNull
+        private final View.OnLayoutChangeListener mLayoutListener;
+
+        KeyboardLayoutView(Context context, @NonNull ImeSettings imeSettings,
+                @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback) {
+            super(context);
+
+            mSettings = imeSettings;
+
+            setOrientation(VERTICAL);
+
+            final int defaultBackgroundColor =
+                    getResources().getColor(android.R.color.holo_orange_dark, null);
+            setBackgroundColor(mSettings.getBackgroundColor(defaultBackgroundColor));
+
+            final int mainSpacerHeight = mSettings.getInputViewHeightWithoutSystemWindowInset(
+                    LayoutParams.WRAP_CONTENT);
+            {
+                final RelativeLayout layout = new RelativeLayout(getContext());
+                final TextView textView = new TextView(getContext());
+                final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+                        RelativeLayout.LayoutParams.MATCH_PARENT,
+                        RelativeLayout.LayoutParams.WRAP_CONTENT);
+                params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
+                textView.setLayoutParams(params);
+                textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
+                textView.setGravity(Gravity.CENTER);
+                textView.setText(getImeId(getContext().getPackageName()));
+                layout.addView(textView);
+                addView(layout, LayoutParams.MATCH_PARENT, mainSpacerHeight);
+            }
+
+            final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0);
+            if (systemUiVisibility != 0) {
+                setSystemUiVisibility(systemUiVisibility);
+            }
+
+            mLayoutListener = (View v, int left, int top, int right, int bottom, int oldLeft,
+                    int oldTop, int oldRight, int oldBottom) ->
+                    onInputViewLayoutChangedCallback.accept(
+                            ImeLayoutInfo.fromLayoutListenerCallback(
+                                    v, left, top, right, bottom, oldLeft, oldTop, oldRight,
+                                    oldBottom));
+            this.addOnLayoutChangeListener(mLayoutListener);
+        }
+
+        private void updateBottomPaddingIfNecessary(int newPaddingBottom) {
+            if (getPaddingBottom() != newPaddingBottom) {
+                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom);
+            }
+        }
+
+        @Override
+        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            if (insets.isConsumed()
+                    || (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0) {
+                // In this case we are not interested in consuming NavBar region.
+                // Make sure that the bottom padding is empty.
+                updateBottomPaddingIfNecessary(0);
+                return insets;
+            }
+
+            // In some cases the bottom system window inset is not a navigation bar. Wear devices
+            // that have bottom chin are examples.  For now, assume that it's a navigation bar if it
+            // has the same height as the root window's stable bottom inset.
+            final WindowInsets rootWindowInsets = getRootWindowInsets();
+            if (rootWindowInsets != null && (rootWindowInsets.getStableInsetBottom()
+                    != insets.getSystemWindowInsetBottom())) {
+                // This is probably not a NavBar.
+                updateBottomPaddingIfNecessary(0);
+                return insets;
+            }
+
+            final int possibleNavBarHeight = insets.getSystemWindowInsetBottom();
+            updateBottomPaddingIfNecessary(possibleNavBarHeight);
+            return possibleNavBarHeight <= 0
+                    ? insets
+                    : insets.replaceSystemWindowInsets(
+                            insets.getSystemWindowInsetLeft(),
+                            insets.getSystemWindowInsetTop(),
+                            insets.getSystemWindowInsetRight(),
+                            0 /* bottom */);
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            removeOnLayoutChangeListener(mLayoutListener);
+        }
+    }
+
+    private void onInputViewLayoutChanged(@NonNull ImeLayoutInfo layoutInfo) {
+        getTracer().onInputViewLayoutChanged(layoutInfo, () -> { });
+    }
+
+    @Override
+    public View onCreateInputView() {
+        return getTracer().onCreateInputView(() ->
+                new KeyboardLayoutView(this, mSettings, this::onInputViewLayoutChanged));
+    }
+
+    @Override
+    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+        getTracer().onStartInput(editorInfo, restarting,
+                () -> super.onStartInput(editorInfo, restarting));
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+        getTracer().onStartInputView(editorInfo, restarting,
+                () -> super.onStartInputView(editorInfo, restarting));
+    }
+
+    @Override
+    public void onFinishInputView(boolean finishingInput) {
+        getTracer().onFinishInputView(finishingInput,
+                () -> super.onFinishInputView(finishingInput));
+    }
+
+    @Override
+    public void onFinishInput() {
+        getTracer().onFinishInput(() -> super.onFinishInput());
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return getTracer().onKeyDown(keyCode, event, () -> super.onKeyDown(keyCode, event));
+    }
+
+    @CallSuper
+    public boolean onEvaluateInputViewShown() {
+        return getTracer().onEvaluateInputViewShown(() -> {
+            // onShowInputRequested() is indeed @CallSuper so we always call this, even when the
+            // result is ignored.
+            final boolean originalResult = super.onEvaluateInputViewShown();
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                final Configuration config = getResources().getConfiguration();
+                if (config.keyboard != Configuration.KEYBOARD_NOKEYS
+                        && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) {
+                    // Override the behavior of InputMethodService#onEvaluateInputViewShown()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
+    @Override
+    public boolean onShowInputRequested(int flags, boolean configChange) {
+        return getTracer().onShowInputRequested(flags, configChange, () -> {
+            // onShowInputRequested() is not marked with @CallSuper, but just in case.
+            final boolean originalResult = super.onShowInputRequested(flags, configChange);
+            if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+                if ((flags & InputMethod.SHOW_EXPLICIT) == 0
+                        && getResources().getConfiguration().keyboard
+                        != Configuration.KEYBOARD_NOKEYS) {
+                    // Override the behavior of InputMethodService#onShowInputRequested()
+                    return true;
+                }
+            }
+            return originalResult;
+        });
+    }
+
+    @Override
+    public void onDestroy() {
+        getTracer().onDestroy(() -> {
+            super.onDestroy();
+            unregisterReceiver(mCommandReceiver);
+            mHandlerThread.quitSafely();
+        });
+    }
+
+    @Override
+    public AbstractInputMethodImpl onCreateInputMethodInterface() {
+        return getTracer().onCreateInputMethodInterface(() -> new MockInputMethodImpl());
+    }
+
+    private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>();
+
+    private Tracer getTracer() {
+        Tracer tracer = mThreadLocalTracer.get();
+        if (tracer == null) {
+            tracer = new Tracer(this);
+            mThreadLocalTracer.set(tracer);
+        }
+        return tracer;
+    }
+
+    @NonNull
+    private ImeState getState() {
+        final boolean hasInputBinding = getCurrentInputBinding() != null;
+        final boolean hasDummyInputConnectionConnection =
+                !hasInputBinding
+                        || getCurrentInputConnection() == getCurrentInputBinding().getConnection();
+        return new ImeState(hasInputBinding, hasDummyInputConnectionConnection);
+    }
+
+    /**
+     * Event tracing helper class for {@link MockIme}.
+     */
+    private static final class Tracer {
+
+        @NonNull
+        private final MockIme mIme;
+
+        private final int mThreadId = Process.myTid();
+
+        @NonNull
+        private final String mThreadName =
+                Thread.currentThread().getName() != null ? Thread.currentThread().getName() : "";
+
+        private final boolean mIsMainThread =
+                Looper.getMainLooper().getThread() == Thread.currentThread();
+
+        private int mNestLevel = 0;
+
+        private String mImeEventActionName;
+
+        Tracer(@NonNull MockIme mockIme) {
+            mIme = mockIme;
+        }
+
+        private void sendEventInternal(@NonNull ImeEvent event) {
+            final Intent intent = new Intent();
+            intent.setPackage(mIme.getPackageName());
+            if (mImeEventActionName == null) {
+                mImeEventActionName = mIme.getImeEventActionName();
+            }
+            if (mImeEventActionName == null) {
+                Log.e(TAG, "Tracer cannot be used before onCreate()");
+                return;
+            }
+            intent.setAction(mImeEventActionName);
+            intent.putExtras(event.toBundle());
+            mIme.sendBroadcast(intent);
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) {
+            recordEventInternal(eventName, runnable, new Bundle());
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable,
+                @NonNull Bundle arguments) {
+            recordEventInternal(eventName, () -> {
+                runnable.run(); return null;
+            }, arguments);
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier) {
+            return recordEventInternal(eventName, supplier, new Bundle());
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier, @NonNull Bundle arguments) {
+            final ImeState enterState = mIme.getState();
+            final long enterTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long enterWallTime = System.currentTimeMillis();
+            final int nestLevel = mNestLevel;
+            // Send enter event
+            sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, 0, enterWallTime,
+                    0, enterState, null, arguments, null));
+            ++mNestLevel;
+            T result;
+            try {
+                result = supplier.get();
+            } finally {
+                --mNestLevel;
+            }
+            final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long exitWallTime = System.currentTimeMillis();
+            final ImeState exitState = mIme.getState();
+            // Send exit event
+            sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
+                    exitWallTime, enterState, exitState, arguments, result));
+            return result;
+        }
+
+        public void onCreate(@NonNull Runnable runnable) {
+            recordEventInternal("onCreate", runnable);
+        }
+
+        public void onConfigureWindow(Window win, boolean isFullscreen,
+                boolean isCandidatesOnly, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBoolean("isFullscreen", isFullscreen);
+            arguments.putBoolean("isCandidatesOnly", isCandidatesOnly);
+            recordEventInternal("onConfigureWindow", runnable, arguments);
+        }
+
+        public boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean);
+        }
+
+        public boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) {
+            return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean);
+        }
+
+        public View onCreateInputView(@NonNull Supplier<View> supplier) {
+            return recordEventInternal("onCreateInputView", supplier);
+        }
+
+        public void onStartInput(EditorInfo editorInfo, boolean restarting,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("editorInfo", editorInfo);
+            arguments.putBoolean("restarting", restarting);
+            recordEventInternal("onStartInput", runnable, arguments);
+        }
+
+        public void onStartInputView(EditorInfo editorInfo, boolean restarting,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("editorInfo", editorInfo);
+            arguments.putBoolean("restarting", restarting);
+            recordEventInternal("onStartInputView", runnable, arguments);
+        }
+
+        public void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBoolean("finishingInput", finishingInput);
+            recordEventInternal("onFinishInputView", runnable, arguments);
+        }
+
+        public void onFinishInput(@NonNull Runnable runnable) {
+            recordEventInternal("onFinishInput", runnable);
+        }
+
+        public boolean onKeyDown(int keyCode, KeyEvent event, @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("keyCode", keyCode);
+            arguments.putParcelable("event", event);
+            return recordEventInternal("onKeyDown", supplier::getAsBoolean, arguments);
+        }
+
+        public boolean onShowInputRequested(int flags, boolean configChange,
+                @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putBoolean("configChange", configChange);
+            return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments);
+        }
+
+        public void onDestroy(@NonNull Runnable runnable) {
+            recordEventInternal("onDestroy", runnable);
+        }
+
+        public void attachToken(IBinder token, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBinder("token", token);
+            recordEventInternal("attachToken", runnable, arguments);
+        }
+
+        public void bindInput(InputBinding binding, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("binding", binding);
+            recordEventInternal("bindInput", runnable, arguments);
+        }
+
+        public void unbindInput(@NonNull Runnable runnable) {
+            recordEventInternal("unbindInput", runnable);
+        }
+
+        public void showSoftInput(int flags, ResultReceiver resultReceiver,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putParcelable("resultReceiver", resultReceiver);
+            recordEventInternal("showSoftInput", runnable, arguments);
+        }
+
+        public void hideSoftInput(int flags, ResultReceiver resultReceiver,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("flags", flags);
+            arguments.putParcelable("resultReceiver", resultReceiver);
+            recordEventInternal("hideSoftInput", runnable, arguments);
+        }
+
+        public AbstractInputMethodImpl onCreateInputMethodInterface(
+                @NonNull Supplier<AbstractInputMethodImpl> supplier) {
+            return recordEventInternal("onCreateInputMethodInterface", supplier);
+        }
+
+        public void onReceiveCommand(
+                @NonNull ImeCommand command, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBundle("command", command.toBundle());
+            recordEventInternal("onReceiveCommand", runnable, arguments);
+        }
+
+        public void onHandleCommand(
+                @NonNull ImeCommand command, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putBundle("command", command.toBundle());
+            recordEventInternal("onHandleCommand", runnable, arguments);
+        }
+
+        public void onInputViewLayoutChanged(@NonNull ImeLayoutInfo imeLayoutInfo,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            imeLayoutInfo.writeToBundle(arguments);
+            recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
+        }
+    }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
new file mode 100644
index 0000000..64e7265
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -0,0 +1,369 @@
+/*
+ * 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.mockime;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
+ * for IME APIs.
+ *
+ * <p>To use {@link MockIme} via {@link MockImeSession}, you need to </p>
+ * <p>Public methods are not thread-safe.</p>
+ */
+public class MockImeSession implements AutoCloseable {
+    private final String mImeEventActionName =
+            "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
+
+    /** Setting file name to store initialization settings for {@link MockIme}. */
+    static final String MOCK_IME_SETTINGS_FILE = "mockimesettings.data";
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final UiAutomation mUiAutomation;
+
+    private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
+
+    private static final class EventStore {
+        private static final int INITIAL_ARRAY_SIZE = 32;
+
+        @NonNull
+        public final ImeEvent[] mArray;
+        public int mLength;
+
+        EventStore() {
+            mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
+            mLength = 0;
+        }
+
+        EventStore(EventStore src, int newLength) {
+            mArray = new ImeEvent[newLength];
+            mLength = src.mLength;
+            System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
+        }
+
+        public EventStore add(ImeEvent event) {
+            if (mLength + 1 <= mArray.length) {
+                mArray[mLength] = event;
+                ++mLength;
+                return this;
+            } else {
+                return new EventStore(this, mLength * 2).add(event);
+            }
+        }
+
+        public ImeEventStream.ImeEventArray takeSnapshot() {
+            return new ImeEventStream.ImeEventArray(mArray, mLength);
+        }
+    }
+
+    private static final class MockImeEventReceiver extends BroadcastReceiver {
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        @NonNull
+        private EventStore mCurrentEventStore = new EventStore();
+
+        @NonNull
+        private final String mActionName;
+
+        MockImeEventReceiver(@NonNull String actionName) {
+            mActionName = actionName;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                synchronized (mLock) {
+                    mCurrentEventStore =
+                            mCurrentEventStore.add(ImeEvent.fromBundle(intent.getExtras()));
+                }
+            }
+        }
+
+        public ImeEventStream.ImeEventArray takeEventSnapshot() {
+            synchronized (mLock) {
+                return mCurrentEventStore.takeSnapshot();
+            }
+        }
+    }
+    private final MockImeEventReceiver mEventReceiver =
+            new MockImeEventReceiver(mImeEventActionName);
+
+    private final ImeEventStream mEventStream =
+            new ImeEventStream(mEventReceiver::takeEventSnapshot);
+
+    private static String executeShellCommand(
+            @NonNull UiAutomation uiAutomation, @NonNull String command) throws IOException {
+        try (ParcelFileDescriptor.AutoCloseInputStream in =
+                     new ParcelFileDescriptor.AutoCloseInputStream(
+                             uiAutomation.executeShellCommand(command))) {
+            final StringBuilder sb = new StringBuilder();
+            final byte[] buffer = new byte[4096];
+            while (true) {
+                final int numRead = in.read(buffer);
+                if (numRead <= 0) {
+                    break;
+                }
+                sb.append(new String(buffer, 0, numRead));
+            }
+            return sb.toString();
+        }
+    }
+
+    @Nullable
+    private String getCurrentInputMethodId() {
+        // TODO: Replace this with IMM#getCurrentInputMethodIdForTesting()
+        return Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD);
+    }
+
+    @Nullable
+    private static void writeMockImeSettings(@NonNull Context context,
+            @NonNull String imeEventActionName,
+            @Nullable ImeSettings.Builder imeSettings) throws Exception {
+        context.deleteFile(MOCK_IME_SETTINGS_FILE);
+        try (OutputStream os = context.openFileOutput(MOCK_IME_SETTINGS_FILE, MODE_PRIVATE)) {
+            Parcel parcel = null;
+            try {
+                parcel = Parcel.obtain();
+                ImeSettings.writeToParcel(parcel, imeEventActionName, imeSettings);
+                os.write(parcel.marshall());
+            } finally {
+                if (parcel != null) {
+                    parcel.recycle();
+                }
+            }
+            os.flush();
+        }
+    }
+
+    private ComponentName getMockImeComponentName() {
+        return MockIme.getComponentName(mContext.getPackageName());
+    }
+
+    private String getMockImeId() {
+        return MockIme.getImeId(mContext.getPackageName());
+    }
+
+    private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
+        mContext = context;
+        mUiAutomation = uiAutomation;
+    }
+
+    private void initialize(@Nullable ImeSettings.Builder imeSettings) throws Exception {
+        // Make sure that MockIME is not selected.
+        if (mContext.getSystemService(InputMethodManager.class)
+                .getInputMethodList()
+                .stream()
+                .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+            executeShellCommand(mUiAutomation, "ime reset");
+        }
+        if (mContext.getSystemService(InputMethodManager.class)
+                .getEnabledInputMethodList()
+                .stream()
+                .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+            throw new IllegalStateException();
+        }
+
+        writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
+
+        mHandlerThread.start();
+        mContext.registerReceiver(mEventReceiver,
+                new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+                new Handler(mHandlerThread.getLooper()));
+
+        executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
+        executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
+
+        PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT,
+                () -> getMockImeId().equals(getCurrentInputMethodId()));
+    }
+
+    /**
+     * Creates a new Mock IME session. During this session, you can receive various events from
+     * {@link MockIme}.
+     *
+     * @param context {@link Context} to be used to receive inter-process events from the
+     *                {@link MockIme} (e.g. via {@link BroadcastReceiver}
+     * @param uiAutomation {@link UiAutomation} object to change the device state that are typically
+     *                     guarded by permissions.
+     * @param imeSettings Key-value pairs to be passed to the {@link MockIme}.
+     * @return A session object, with which you can retrieve event logs from the {@link MockIme} and
+     *         can clean up the session.
+     */
+    @NonNull
+    public static MockImeSession create(
+            @NonNull Context context,
+            @NonNull UiAutomation uiAutomation,
+            @Nullable ImeSettings.Builder imeSettings) throws Exception {
+        final MockImeSession client = new MockImeSession(context, uiAutomation);
+        client.initialize(imeSettings);
+        return client;
+    }
+
+    /**
+     * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
+     *         session is created.
+     */
+    public ImeEventStream openEventStream() {
+        return mEventStream.copy();
+    }
+
+    /**
+     * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
+     * selected next is up to the system.
+     */
+    public void close() throws Exception {
+        executeShellCommand(mUiAutomation, "ime reset");
+
+        PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+                mContext.getSystemService(InputMethodManager.class)
+                        .getEnabledInputMethodList()
+                        .stream()
+                        .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+        mContext.unregisterReceiver(mEventReceiver);
+        mHandlerThread.quitSafely();
+        mContext.deleteFile(MOCK_IME_SETTINGS_FILE);
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link android.view.inputmethod.InputConnection#commitText(CharSequence, int)} with the given
+     * parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().commitText(text, newCursorPosition)}.</p>
+     *
+     * @param text to be passed as the {@code text} parameter
+     * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callCommitText(@NonNull CharSequence text, int newCursorPosition) {
+        final Bundle params = new Bundle();
+        params.putCharSequence("text", text);
+        params.putInt("newCursorPosition", newCursorPosition);
+        final ImeCommand command = new ImeCommand(
+                "commitText", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link android.view.inputmethod.InputConnection#reportLanguageHint(LocaleList)} with the
+     * given parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().reportLanguageHint(languageHint)}.</p>
+     *
+     * @param languageHint to be passed as the {@code languageHint} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callReportLnaguageHint(@NonNull LocaleList languageHint) {
+        final Bundle params = new Bundle();
+        params.putParcelable("languageHint", languageHint);
+        final ImeCommand command = new ImeCommand(
+                "reportLanguageHint", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callSetBackDisposition(int backDisposition) {
+        final Bundle params = new Bundle();
+        params.putInt("backDisposition", backDisposition);
+        final ImeCommand command = new ImeCommand(
+                "setBackDisposition", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callRequestHideSelf(int flags) {
+        final Bundle params = new Bundle();
+        params.putInt("flags", flags);
+        final ImeCommand command = new ImeCommand(
+                "requestHideSelf", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    public ImeCommand callRequestShowSelf(int flags) {
+        final Bundle params = new Bundle();
+        params.putInt("flags", flags);
+        final ImeCommand command = new ImeCommand(
+                "requestShowSelf", SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+}
diff --git a/tests/inputmethod/res/xml/method.xml b/tests/inputmethod/res/xml/method.xml
new file mode 100644
index 0000000..2266fba
--- /dev/null
+++ b/tests/inputmethod/res/xml/method.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+</input-method>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index d5ace2e..d62f7be 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -45,7 +45,6 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.cts.R;
 import android.view.inputmethod.cts.util.InputConnectionTestUtils;
 import android.widget.EditText;
 
@@ -493,8 +492,8 @@
     public void testCloseConnection() {
         final CharSequence source = "0123456789";
         mConnection.commitText(source, source.length());
+        mConnection.setComposingRegion(2, 5);
         final Editable text = mConnection.getEditable();
-        BaseInputConnection.setComposingSpans(text, 2, 5);
         assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
         assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
index 41e3efa..d4dccce 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
@@ -18,9 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.graphics.Typeface;
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.StyleSpan;
 import android.view.inputmethod.ExtractedText;
 
 import org.junit.Test;
@@ -38,6 +43,9 @@
         extractedText.startOffset = 1;
         CharSequence text = "test";
         extractedText.text = text;
+        SpannableStringBuilder hint = new SpannableStringBuilder("hint");
+        hint.setSpan(new StyleSpan(Typeface.BOLD), 1, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        extractedText.hint = hint;
         Parcel p = Parcel.obtain();
         extractedText.writeToParcel(p, 0);
         p.setDataPosition(0);
@@ -49,7 +57,12 @@
         assertEquals(extractedText.partialStartOffset, target.partialStartOffset);
         assertEquals(extractedText.partialEndOffset, target.partialEndOffset);
         assertEquals(extractedText.text.toString(), target.text.toString());
+        assertEquals(extractedText.hint.toString(), target.hint.toString());
+        final Spannable hintText = (Spannable) extractedText.hint;
+        assertEquals(1, hintText.getSpans(0, hintText.length(), StyleSpan.class).length);
 
         assertEquals(0, extractedText.describeContents());
+
+        p.recycle();
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
new file mode 100644
index 0000000..2978c12
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -0,0 +1,451 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertFalse;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.view.inputmethod.cts.util.TestUtils;
+import android.view.inputmethod.cts.util.WindowFocusStealer;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeCommand;
+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.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FocusHandlingTest extends EndToEndImeTestBase {
+    static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+    private static final String TEST_MARKER = "android.view.inputmethod.cts.FocusHandlingTest";
+
+    public EditText launchTestActivity() {
+        final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity-> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setPrivateImeOptions(TEST_MARKER);
+            editText.setHint("editText");
+            editText.requestFocus();
+            editTextRef.set(editText);
+
+            layout.addView(editText);
+            return layout;
+        });
+        return editTextRef.get();
+    }
+
+    @Test
+    public void testOnStartInputCalledOnceIme() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final EditText editText = launchTestActivity();
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), editText);
+
+            // Wait until "onStartInput" gets called for the EditText.
+            final ImeEvent onStart = expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+            assertFalse(stream.dump(), onStart.getEnterState().hasDummyInputConnection());
+            assertFalse(stream.dump(), onStart.getArguments().getBoolean("restarting"));
+
+            // There shouldn't be onStartInput any more.
+            notExpectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+                    NOT_EXPECT_TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testSoftInputStateAlwaysVisibleWithoutFocusedEditorView() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final TestActivity testActivity = TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final TextView textView = new TextView(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return false;
+                    }
+                };
+                textView.setText("textView");
+                textView.requestFocus();
+
+                activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                layout.addView(textView);
+                return layout;
+            });
+
+            if (testActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                // Input shouldn't start
+                notExpectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+                        TIMEOUT);
+                // There shouldn't be onStartInput because the focused view is not an editor.
+                notExpectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                        TIMEOUT);
+            } else {
+                // Wait until the MockIme gets bound to the TestActivity.
+                expectBindInput(stream, Process.myPid(), TIMEOUT);
+                // For apps that target pre-P devices, onStartInput() should be called.
+                expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            }
+        }
+    }
+
+    @Test
+    public void testNoEditorNoStartInput() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final TextView textView = new TextView(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return false;
+                    }
+                };
+                textView.setText("textView");
+                textView.requestFocus();
+                layout.addView(textView);
+                return layout;
+            });
+
+            // Input shouldn't start
+            notExpectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testEditorStartsInput() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setText("Editable");
+                editText.requestFocus();
+                layout.addView(editText);
+                return layout;
+            });
+
+            // Input should start
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testDelayedAddEditorStartsInput() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final AtomicReference<LinearLayout> layoutRef = new AtomicReference<>();
+            final TestActivity testActivity = TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                layoutRef.set(layout);
+
+                return layout;
+            });
+
+            // Activity adds EditText at a later point.
+            TestUtils.waitOnMainUntil(() -> layoutRef.get().hasWindowFocus(), TIMEOUT);
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            testActivity.runOnUiThread(() -> {
+                final EditText editText = new EditText(testActivity);
+                editText.setText("Editable");
+                layoutRef.get().addView(editText);
+                editText.requestFocus();
+            });
+
+            // Input should start
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testSoftInputStateAlwaysVisibleFocusedEditorView() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setText("editText");
+                editText.requestFocus();
+
+                activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                layout.addView(editText);
+                return layout;
+            });
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    /**
+     * Makes sure that an existing {@link android.view.inputmethod.InputConnection} will not be
+     * invalidated by showing a focusable {@link PopupWindow} with
+     * {@link PopupWindow#INPUT_METHOD_NOT_NEEDED}.
+     *
+     * <p>If {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} is set and
+     * {@link android.view.WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} is not set to a
+     * {@link android.view.Window}, showing that window must not invalidate an existing valid
+     * {@link android.view.inputmethod.InputConnection}.</p>
+     *
+     * @see android.view.WindowManager.LayoutParams#mayUseInputMethod(int)
+     */
+    @Test
+    public void testFocusableWindowDoesNotInvalidateExistingInputConnection() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                instrumentation.getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final EditText editText = launchTestActivity();
+            instrumentation.runOnMainSync(() -> editText.requestFocus());
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+
+            // Make sure that InputConnection#commitText() works.
+            final ImeCommand commit1 = imeSession.callCommitText("test commit", 1);
+            expectCommand(stream, commit1, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(editText.getText(), "test commit"), TIMEOUT);
+            instrumentation.runOnMainSync(() -> editText.setText(""));
+
+            // Create a popup window that cannot be the IME target.
+            final PopupWindow popupWindow = TestUtils.getOnMainSync(() -> {
+                final Context context = instrumentation.getTargetContext();
+                final PopupWindow popup = new PopupWindow(context);
+                popup.setFocusable(true);
+                popup.setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
+                final TextView textView = new TextView(context);
+                textView.setText("Test Text");
+                popup.setContentView(textView);
+                return popup;
+            });
+
+            // Show the popup window.
+            instrumentation.runOnMainSync(() -> popupWindow.showAsDropDown(editText));
+            instrumentation.waitForIdleSync();
+
+            // Make sure that the EditText no longer has window-focus
+            TestUtils.waitOnMainUntil(() -> !editText.hasWindowFocus(), TIMEOUT);
+
+            // Make sure that InputConnection#commitText() works.
+            final ImeCommand commit2 = imeSession.callCommitText("Hello!", 1);
+            expectCommand(stream, commit2, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(editText.getText(), "Hello!"), TIMEOUT);
+            instrumentation.runOnMainSync(() -> editText.setText(""));
+
+            stream.skipAll();
+
+            // Call InputMethodManager#restartInput()
+            instrumentation.runOnMainSync(() -> {
+                editText.getContext()
+                        .getSystemService(InputMethodManager.class)
+                        .restartInput(editText);
+            });
+
+            // Make sure that onStartInput() is called with restarting == true.
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                if (!event.getArguments().getBoolean("restarting")) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+
+            // Make sure that InputConnection#commitText() works.
+            final ImeCommand commit3 = imeSession.callCommitText("World!", 1);
+            expectCommand(stream, commit3, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(editText.getText(), "World!"), TIMEOUT);
+            instrumentation.runOnMainSync(() -> editText.setText(""));
+
+            // Dismiss the popup window.
+            instrumentation.runOnMainSync(() -> popupWindow.dismiss());
+            instrumentation.waitForIdleSync();
+
+            // Make sure that the EditText now has window-focus again.
+            TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+
+            // Make sure that InputConnection#commitText() works.
+            final ImeCommand commit4 = imeSession.callCommitText("Done!", 1);
+            expectCommand(stream, commit4, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(editText.getText(), "Done!"), TIMEOUT);
+            instrumentation.runOnMainSync(() -> editText.setText(""));
+        }
+    }
+
+    /**
+     * Test case for Bug 70629102.
+     *
+     * {@link InputMethodManager#restartInput(View)} can be called even when another process
+     * temporarily owns focused window. {@link InputMethodManager} should continue to work after
+     * the IME target application gains window focus again.
+     */
+    @Test
+    public void testRestartInputWhileOtherProcessHasWindowFocus() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                instrumentation.getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final EditText editText = launchTestActivity();
+            instrumentation.runOnMainSync(() -> editText.requestFocus());
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+
+            // Get app window token
+            final IBinder appWindowToken = TestUtils.getOnMainSync(
+                    () -> editText.getApplicationWindowToken());
+
+            try (WindowFocusStealer focusStealer =
+                         WindowFocusStealer.connect(instrumentation.getTargetContext(), TIMEOUT)) {
+
+                focusStealer.stealWindowFocus(appWindowToken, TIMEOUT);
+
+                // Wait until the edit text loses window focus.
+                TestUtils.waitOnMainUntil(() -> !editText.hasWindowFocus(), TIMEOUT);
+
+                // Call InputMethodManager#restartInput()
+                instrumentation.runOnMainSync(() -> {
+                    editText.getContext()
+                            .getSystemService(InputMethodManager.class)
+                            .restartInput(editText);
+                });
+            }
+
+            // Wait until the edit text gains window focus again.
+            TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+
+            // Make sure that InputConnection#commitText() still works.
+            final ImeCommand command = imeSession.callCommitText("test commit", 1);
+            expectCommand(stream, command, TIMEOUT);
+
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(editText.getText(), "test commit"), TIMEOUT);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
index faaff3d..6af7cdc 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
@@ -59,5 +59,7 @@
         assertEquals(uid, target.getUid());
         assertEquals(pid, target.getPid());
         assertSame(binder, target.getConnectionToken());
+
+        p.recycle();
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionTest.java
new file mode 100644
index 0000000..3527dab
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionTest.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.view.inputmethod.cts;
+
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.LocaleList;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    @Test
+    public void testReportLanguageHint() throws Exception {
+        final String testMarker = "testReportLanguageHint-" + SystemClock.elapsedRealtimeNanos();
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final AtomicReference<ArrayList<LocaleList>> languageHintHistoryRef =
+                    new AtomicReference<>();
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                final EditText editText = new EditText(activity) {
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
+                        final InputConnection original = super.onCreateInputConnection(editorInfo);
+                        final ArrayList<LocaleList> languageHintHistory = new ArrayList<>();
+                        final InputConnectionWrapper wrapper =
+                                new InputConnectionWrapper(original, false) {
+                                    @Override
+                                    public void reportLanguageHint(LocaleList languageHint) {
+                                        languageHintHistory.add(languageHint);
+                                    }
+                                };
+                        // In case onCreateInputConnection() gets called twice, make sure that only
+                        // the first call is used in later tests.
+                        if (languageHintHistoryRef.compareAndSet(null, languageHintHistory)) {
+                            editorInfo.privateImeOptions = testMarker;
+                        }
+                        return wrapper;
+                    }
+                };
+                editText.requestFocus();
+                layout.addView(editText);
+                return layout;
+            });
+
+            // Wait until "onStartInput" gets called for the EditText.
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(testMarker, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+
+            final List<LocaleList> languageHintHistory = languageHintHistoryRef.get();
+            assertNotNull(languageHintHistory);
+            assertTrue(getOnMainSync(() -> languageHintHistoryRef.get().isEmpty()));
+
+            final LocaleList localeList1 = LocaleList.forLanguageTags("sr-Cyrl-RS");
+            final ImeCommand reportLanguageHint1 = imeSession.callReportLnaguageHint(localeList1);
+            expectCommand(stream, reportLanguageHint1, TIMEOUT);
+            waitOnMainUntil(() -> languageHintHistoryRef.get().size() == 1
+                    && localeList1.equals(languageHintHistoryRef.get().get(0)), TIMEOUT);
+
+            final LocaleList localeList2 = LocaleList.forLanguageTags("sr-Latn-RS-x-android,en-US");
+            final ImeCommand reportLanguageHint2 = imeSession.callReportLnaguageHint(localeList2);
+            expectCommand(stream, reportLanguageHint2, TIMEOUT);
+            waitOnMainUntil(() -> languageHintHistoryRef.get().size() == 2
+                    && localeList2.equals(languageHintHistoryRef.get().get(1)), TIMEOUT);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
index 9501d44..309ebe8 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
@@ -18,7 +18,6 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.view.inputmethod.cts.R;
 
 public class InputMethodCtsActivity extends Activity {
     @Override
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 2b5d7ed..11cb412 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -31,7 +31,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.support.test.InstrumentationRegistry;
@@ -53,8 +52,6 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 @SmallTest
@@ -72,8 +69,8 @@
     private int mSubtypeIconResId;
     private String mSubtypeLocale;
     private String mSubtypeMode;
-    private String mSubtypeExtraValue_key;
-    private String mSubtypeExtraValue_value;
+    private String mSubtypeExtraValueKey;
+    private String mSubtypeExtraValueValue;
     private String mSubtypeExtraValue;
     private boolean mSubtypeIsAuxiliary;
     private boolean mSubtypeOverridesImplicitlyEnabledSubtype;
@@ -93,9 +90,9 @@
         mSubtypeIconResId = 0;
         mSubtypeLocale = "en_US";
         mSubtypeMode = "keyboard";
-        mSubtypeExtraValue_key = "key1";
-        mSubtypeExtraValue_value = "value1";
-        mSubtypeExtraValue = "tag," + mSubtypeExtraValue_key + "=" + mSubtypeExtraValue_value;
+        mSubtypeExtraValueKey = "key1";
+        mSubtypeExtraValueValue = "value1";
+        mSubtypeExtraValue = "tag," + mSubtypeExtraValueKey + "=" + mSubtypeExtraValueValue;
         mSubtypeIsAuxiliary = false;
         mSubtypeOverridesImplicitlyEnabledSubtype = false;
         mSubtypeId = 99;
@@ -132,9 +129,9 @@
         assertEquals(mSubtypeLocale, mInputMethodSubtype.getLocale());
         assertEquals(mSubtypeMode, mInputMethodSubtype.getMode());
         assertEquals(mSubtypeExtraValue, mInputMethodSubtype.getExtraValue());
-        assertTrue(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key));
-        assertEquals(mSubtypeExtraValue_value,
-                mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key));
+        assertTrue(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValueKey));
+        assertEquals(mSubtypeExtraValueValue,
+                mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValueKey));
         assertEquals(mSubtypeIsAuxiliary, mInputMethodSubtype.isAuxiliary());
         assertEquals(mSubtypeOverridesImplicitlyEnabledSubtype,
                 mInputMethodSubtype.overridesImplicitlyEnabledSubtype());
@@ -211,11 +208,11 @@
         final InputMethodSubtype subtype = InputMethodSubtype.CREATOR.createFromParcel(p);
         p.recycle();
 
-        assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key),
-                subtype.containsExtraValueKey(mSubtypeExtraValue_key));
+        assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValueKey),
+                subtype.containsExtraValueKey(mSubtypeExtraValueKey));
         assertEquals(mInputMethodSubtype.getExtraValue(), subtype.getExtraValue());
-        assertEquals(mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key),
-                subtype.getExtraValueOf(mSubtypeExtraValue_key));
+        assertEquals(mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValueKey),
+                subtype.getExtraValueOf(mSubtypeExtraValueKey));
         assertEquals(mInputMethodSubtype.getIconResId(), subtype.getIconResId());
         assertEquals(mInputMethodSubtype.getLocale(), subtype.getLocale());
         assertEquals(mInputMethodSubtype.getMode(), subtype.getMode());
@@ -246,11 +243,11 @@
             if (serviceInfo == null) {
                 continue;
             }
-            if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) !=
-                    ApplicationInfo.FLAG_SYSTEM) {
+            if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                    != ApplicationInfo.FLAG_SYSTEM) {
                 continue;
             }
-            if (serviceInfo.encryptionAware) {
+            if (serviceInfo.directBootAware) {
                 hasEncryptionAwareInputMethod = true;
                 break;
             }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
index 16eaad0..50da9f2 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -16,121 +16,179 @@
 
 package android.view.inputmethod.cts;
 
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
+
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.ResultReceiver;
+import android.content.Intent;
+import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
-import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
-import android.view.Window;
-import android.view.inputmethod.BaseInputConnection;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.cts.R;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.cts.util.TestActivity;
 import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
 
-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 java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodManagerTest {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private Instrumentation mInstrumentation;
-    private InputMethodCtsActivity mActivity;
-
-    @Rule
-    public ActivityTestRule<InputMethodCtsActivity> mActivityRule =
-            new ActivityTestRule<>(InputMethodCtsActivity.class);
+    private Context mContext;
+    private InputMethodManager mImManager;
 
     @Before
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.getActivity();
-    }
-
-    @After
-    public void teardown() {
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        mContext = mInstrumentation.getTargetContext();
+        mImManager = mContext.getSystemService(InputMethodManager.class);
     }
 
     @Test
-    public void testInputMethodManager() throws Throwable {
-        if (!mActivity.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INPUT_METHODS)) {
-            return;
-        }
+    public void testIsActive() throws Throwable {
+        final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
+        final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
 
-        Window window = mActivity.getWindow();
-        final EditText view = (EditText) window.findViewById(R.id.entry);
+            final EditText focusedEditText = new EditText(activity);
+            layout.addView(focusedEditText);
+            focusedEditTextRef.set(focusedEditText);
+            focusedEditText.requestFocus();
 
-        PollingCheck.waitFor(1000, view::hasWindowFocus);
+            final EditText nonFocusedEditText = new EditText(activity);
+            layout.addView(nonFocusedEditText);
+            nonFocusedEditTextRef.set(nonFocusedEditText);
 
-        mActivityRule.runOnUiThread(view::requestFocus);
-        mInstrumentation.waitForIdleSync();
-        assertTrue(view.isFocused());
-
-        BaseInputConnection connection = new BaseInputConnection(view, false);
-        Context context = mInstrumentation.getTargetContext();
-        final InputMethodManager imManager = (InputMethodManager) context
-                .getSystemService(Context.INPUT_METHOD_SERVICE);
-
-        PollingCheck.waitFor(imManager::isActive);
-
-        assertTrue(imManager.isAcceptingText());
-        assertTrue(imManager.isActive(view));
-
-        assertFalse(imManager.isFullscreenMode());
-        connection.reportFullscreenMode(true);
-        // Only IMEs are allowed to report full-screen mode.  Calling this method from the
-        // application should have no effect.
-        assertFalse(imManager.isFullscreenMode());
-
-        mActivityRule.runOnUiThread(() -> {
-            IBinder token = view.getWindowToken();
-
-            // Show and hide input method.
-            assertTrue(imManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT));
-            assertTrue(imManager.hideSoftInputFromWindow(token, 0));
-
-            Handler handler = new Handler();
-            ResultReceiver receiver = new ResultReceiver(handler);
-            assertTrue(imManager.showSoftInput(view, 0, receiver));
-            receiver = new ResultReceiver(handler);
-            assertTrue(imManager.hideSoftInputFromWindow(token, 0, receiver));
-
-            imManager.showSoftInputFromInputMethod(token, InputMethodManager.SHOW_FORCED);
-            imManager.hideSoftInputFromInputMethod(token, InputMethodManager.HIDE_NOT_ALWAYS);
-
-            // status: hide to show to hide
-            imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
-            imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
-
-            List<InputMethodInfo> enabledImList = imManager.getEnabledInputMethodList();
-            if (enabledImList != null && enabledImList.size() > 0) {
-                imManager.setInputMethod(token, enabledImList.get(0).getId());
-                // cannot test whether setting was successful
-            }
-
-            List<InputMethodInfo> imList = imManager.getInputMethodList();
-            if (imList != null && enabledImList != null) {
-                assertTrue(imList.size() >= enabledImList.size());
-            }
+            return layout;
         });
-        mInstrumentation.waitForIdleSync();
+        waitOnMainUntil(() -> mImManager.isActive(), TIMEOUT);
+        assertTrue(mImManager.isAcceptingText());
+        assertTrue(mImManager.isActive(focusedEditTextRef.get()));
+        assertFalse(mImManager.isActive(nonFocusedEditTextRef.get()));
+    }
+
+    @Test
+    public void testIsAcceptingText() throws Throwable {
+        final AtomicReference<EditText> focusedFakeEditTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText focusedFakeEditText = new EditText(activity) {
+                @Override
+                public InputConnection onCreateInputConnection(EditorInfo info) {
+                    super.onCreateInputConnection(info);
+                    return null;
+                }
+            };
+            layout.addView(focusedFakeEditText);
+            focusedFakeEditTextRef.set(focusedFakeEditText);
+            focusedFakeEditText.requestFocus();
+            return layout;
+        });
+        waitOnMainUntil(() -> mImManager.isActive(), TIMEOUT);
+        assertTrue(mImManager.isActive(focusedFakeEditTextRef.get()));
+        assertFalse("InputMethodManager#isAcceptingText() must return false "
+                + "if target View returns null from onCreateInputConnection().",
+                mImManager.isAcceptingText());
+    }
+
+    @Test
+    public void testGetInputMethodList() throws Exception {
+        final List<InputMethodInfo> enabledImes = mImManager.getEnabledInputMethodList();
+        assertNotNull(enabledImes);
+        final List<InputMethodInfo> imes = mImManager.getInputMethodList();
+        assertNotNull(imes);
+
+        // Make sure that IMM#getEnabledInputMethodList() is a subset of IMM#getInputMethodList().
+        // TODO: Consider moving this to hostside test to test more realistic and useful scenario.
+        if (!imes.containsAll(enabledImes)) {
+            fail("Enabled IMEs must be a subset of all the IMEs.\n"
+                    + "all=" + dumpInputMethodInfoList(imes) + "\n"
+                    + "enabled=" + dumpInputMethodInfoList(enabledImes));
+        }
+    }
+
+    private static String dumpInputMethodInfoList(@NonNull List<InputMethodInfo> imiList) {
+        return "[" + imiList.stream().map(imi -> {
+            final StringBuilder sb = new StringBuilder();
+            final int subtypeCount = imi.getSubtypeCount();
+            sb.append("InputMethodInfo{id=").append(imi.getId())
+                    .append(", subtypeCount=").append(subtypeCount)
+                    .append(", subtypes=[");
+            for (int i = 0; i < subtypeCount; ++i) {
+                if (i != 0) {
+                    sb.append(",");
+                }
+                final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+                sb.append("{id=0x").append(Integer.toHexString(subtype.hashCode()));
+                if (!TextUtils.isEmpty(subtype.getMode())) {
+                    sb.append(",mode=").append(subtype.getMode());
+                }
+                if (!TextUtils.isEmpty(subtype.getLocale())) {
+                    sb.append(",locale=").append(subtype.getLocale());
+                }
+                if (!TextUtils.isEmpty(subtype.getLanguageTag())) {
+                    sb.append(",languageTag=").append(subtype.getLanguageTag());
+                }
+                sb.append("}");
+            }
+            sb.append("]");
+            return sb.toString();
+        }).collect(Collectors.joining(", ")) + "]";
+    }
+
+    @Test
+    public void testShowInputMethodPicker() throws Exception {
+        TestActivity.startSync(activity -> {
+            final View view = new View(activity);
+            view.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            return view;
+        });
+
+        // Make sure that InputMethodPicker is not shown in the initial state.
+        mContext.sendBroadcast(
+                new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+        waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "InputMethod picker should be closed");
+
+        // Test InputMethodManager#showInputMethodPicker() works as expected.
+        mImManager.showInputMethodPicker();
+        waitOnMainUntil(() -> mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "InputMethod picker should be shown");
+
+        // Make sure that InputMethodPicker can be closed with ACTION_CLOSE_SYSTEM_DIALOGS
+        mContext.sendBroadcast(
+                new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+        waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "InputMethod picker should be closed");
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
new file mode 100644
index 0000000..56cd41d
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.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.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.inputmethodservice.InputMethodService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.cts.mockime.ImeCommand;
+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 org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+/**
+ * Tests for {@link InputMethodService} methods.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodServiceTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+    private Instrumentation mInstrumentation;
+
+    private static Predicate<ImeEvent> backKeyDownMatcher(boolean expectedReturnValue) {
+        return event -> {
+            if (!TextUtils.equals("onKeyDown", event.getEventName())) {
+                return false;
+            }
+            final int keyCode = event.getArguments().getInt("keyCode");
+            if (keyCode != KeyEvent.KEYCODE_BACK) {
+                return false;
+            }
+            return event.getReturnBooleanValue() == expectedReturnValue;
+        };
+    }
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    private TestActivity createTestActivity(final int windowFlags) {
+        return TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setText("Editable");
+            layout.addView(editText);
+            editText.requestFocus();
+
+            activity.getWindow().setSoftInputMode(windowFlags);
+            return layout;
+        });
+    }
+
+    @Test
+    public void testSetBackDispositionWillDismiss() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final TestActivity testActivity = createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
+
+            final ImeCommand command = imeSession.callSetBackDisposition(
+                    InputMethodService.BACK_DISPOSITION_WILL_DISMISS);
+            expectCommand(stream, command, TIMEOUT);
+
+            testActivity.setIgnoreBackKey(true);
+            assertEquals(0,
+                    (long) getOnMainSync(() -> testActivity.getOnBackPressedCallCount()));
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+
+            // InputMethodService#onKeyDown() should handle back key event.
+            // TODO: Also check InputMethodService#requestHideSelf()
+            expectEvent(stream, backKeyDownMatcher(true), CHECK_EXIT_EVENT_ONLY, TIMEOUT);
+
+            // keyboard will hide
+            expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
+
+            // Make sure TestActivity#onBackPressed() is NOT called.
+            try {
+                waitOnMainUntil(() -> testActivity.getOnBackPressedCallCount() > 0,
+                        EXPECTED_TIMEOUT);
+                fail("Activity#onBackPressed() should not be called");
+            } catch (TimeoutException e){
+                // This is fine.  We actually expect timeout.
+            }
+        }
+    }
+
+    @Test
+    public void testSetBackDispositionWillNotDismiss() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final TestActivity testActivity = createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
+
+            final ImeCommand command = imeSession.callSetBackDisposition(
+                    InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS);
+            expectCommand(stream, command, TIMEOUT);
+
+            testActivity.setIgnoreBackKey(true);
+            assertEquals(0,
+                    (long) getOnMainSync(() -> testActivity.getOnBackPressedCallCount()));
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+
+            // InputMethodService#onKeyDown() will not handle back key event.
+            // TODO: Also check InputMethodService#requestHideSelf()
+            expectEvent(stream, backKeyDownMatcher(false), CHECK_EXIT_EVENT_ONLY, TIMEOUT);
+
+            // keyboard will not hide
+            notExpectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()),
+                    EXPECTED_TIMEOUT);
+
+            // Activity#onBackPressed() should be called.
+            waitOnMainUntil(() -> testActivity.getOnBackPressedCallCount() > 0, TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testRequestHideSelf() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
+
+            imeSession.callRequestHideSelf(0);
+            expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, event -> "onFinishInputView".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testRequestShowSelf() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+            notExpectEvent(
+                    stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
+
+            imeSession.callRequestShowSelf(0);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
index 3f61093..fb0e542 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
@@ -25,7 +25,6 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.inputmethod.cts.R;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
new file mode 100644
index 0000000..81400b4
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+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.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.KeyboardVisibilityControlTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    private static Predicate<ImeEvent> editorMatcher(
+            @NonNull String eventName, @NonNull String marker) {
+        return event -> {
+            if (!TextUtils.equals(eventName, event.getEventName())) {
+                return false;
+            }
+            final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+            return TextUtils.equals(marker, editorInfo.privateImeOptions);
+        };
+    }
+
+    private static Predicate<ImeEvent> showSoftInputMatcher(int requiredFlags) {
+        return event -> {
+            if (!TextUtils.equals("showSoftInput", event.getEventName())) {
+                return false;
+            }
+            final int flags = event.getArguments().getInt("flags");
+            return (flags & requiredFlags) == requiredFlags;
+        };
+    }
+
+    private static Predicate<ImeEvent> hideSoftInputMatcher() {
+        return event -> TextUtils.equals("hideSoftInput", event.getEventName());
+    }
+
+    private static Predicate<ImeEvent> onFinishInputViewMatcher(boolean expectedFinishingInput) {
+        return event -> {
+            if (!TextUtils.equals("onFinishInputView", event.getEventName())) {
+                return false;
+            }
+            final boolean finishingInput = event.getArguments().getBoolean("finishingInput");
+            return finishingInput == expectedFinishingInput;
+        };
+    }
+
+    private Pair<EditText, EditText> launchTestActivity(@NonNull String focusedMarker,
+            @NonNull String nonFocusedMarker) {
+        final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
+        final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText focusedEditText = new EditText(activity);
+            focusedEditText.setHint("focused editText");
+            focusedEditText.setPrivateImeOptions(focusedMarker);
+            focusedEditText.requestFocus();
+            focusedEditTextRef.set(focusedEditText);
+            layout.addView(focusedEditText);
+
+            final EditText nonFocusedEditText = new EditText(activity);
+            nonFocusedEditText.setPrivateImeOptions(nonFocusedMarker);
+            nonFocusedEditText.setHint("target editText");
+            nonFocusedEditTextRef.set(nonFocusedEditText);
+            layout.addView(nonFocusedEditText);
+            return layout;
+        });
+        return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get());
+    }
+
+    private EditText launchTestActivity(@NonNull String marker) {
+        return launchTestActivity(marker, getTestMarker()).first;
+    }
+
+    @Test
+    public void testBasicShowHideSoftInput() throws Exception {
+        final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext().getSystemService(InputMethodManager.class);
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+            assertTrue("isActive() must return true if the View has IME focus",
+                    getOnMainSync(() -> imm.isActive(editText)));
+
+            // Test showSoftInput() flow
+            assertTrue("showSoftInput must success if the View has IME focus",
+                    getOnMainSync(() -> imm.showSoftInput(editText, 0)));
+
+            expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+            // Test hideSoftInputFromWindow() flow
+            assertTrue("hideSoftInputFromWindow must success if the View has IME focus",
+                    getOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0)));
+
+            expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
+            expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testShowHideSoftInputShouldBeIgnoredOnNonFocusedView() throws Exception {
+        final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext().getSystemService(InputMethodManager.class);
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String focusedMarker = getTestMarker();
+            final String nonFocusedMarker = getTestMarker();
+            final Pair<EditText, EditText> editTextPair =
+                    launchTestActivity(focusedMarker, nonFocusedMarker);
+            final EditText nonFocusedEditText = editTextPair.second;
+
+            expectEvent(stream, editorMatcher("onStartInput", focusedMarker), TIMEOUT);
+
+            assertFalse("isActive() must return false if the View does not have IME focus",
+                    getOnMainSync(() -> imm.isActive(nonFocusedEditText)));
+            assertFalse("showSoftInput must fail if the View does not have IME focus",
+                    getOnMainSync(() -> imm.showSoftInput(nonFocusedEditText, 0)));
+            notExpectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
+
+            assertFalse("hideSoftInputFromWindow must fail if the View does not have IME focus",
+                    getOnMainSync(() -> imm.hideSoftInputFromWindow(
+                            nonFocusedEditText.getWindowToken(), 0)));
+            notExpectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testToggleSoftInput() throws Exception {
+        final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext().getSystemService(InputMethodManager.class);
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+            // Test toggleSoftInputFromWindow() flow
+            runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
+
+            expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
+            expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT);
+
+            // Calling toggleSoftInputFromWindow() must hide the IME.
+            runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
+
+            expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
+            expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
new file mode 100644
index 0000000..69856e3
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.inputmethod.cts.util.LightNavigationBarVerifier.expectLightNavigationBarNotSupported;
+import static android.view.inputmethod.cts.util.LightNavigationBarVerifier.expectLightNavigationBarSupported;
+import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorNotSupported;
+import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorSupported;
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.UiAutomation;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Process;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.ImeAwareEditText;
+import android.view.inputmethod.cts.util.NavigationBarInfo;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class NavigationBarColorTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
+
+    private static final String TEST_MARKER = "android.view.inputmethod.cts.NavigationBarColorTest";
+
+    private static void updateSystemUiVisibility(@NonNull View view, int flags, int mask) {
+        final int currentFlags = view.getSystemUiVisibility();
+        final int newFlags = (currentFlags & ~mask) | (flags & mask);
+        if (currentFlags != newFlags) {
+            view.setSystemUiVisibility(newFlags);
+        }
+    }
+
+    @BeforeClass
+    public static void checkNavigationBar() throws Exception {
+        assumeTrue("This test does not make sense if there is no navigation bar",
+                NavigationBarInfo.getInstance().hasBottomNavigationBar());
+
+        assumeTrue("This test does not make sense if custom navigation bar color is not supported"
+                        + " even for typical Activity",
+                NavigationBarInfo.getInstance().supportsNavigationBarColor());
+    }
+
+    /**
+     * Represents test scenarios regarding how a {@link android.view.Window} that has
+     * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} interacts with a different
+     * {@link android.view.Window} that has
+     * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR}.
+     */
+    private enum DimmingTestMode {
+        /**
+         * No {@link AlertDialog} is shown when testing.
+         */
+        NO_DIMMING_DIALOG,
+        /**
+         * An {@link AlertDialog} that has dimming effect is shown above the IME window.
+         */
+        DIMMING_DIALOG_ABOVE_IME,
+        /**
+         * An {@link AlertDialog} that has dimming effect is shown behind the IME window.
+         */
+        DIMMING_DIALOG_BEHIND_IME,
+    }
+
+    @NonNull
+    public TestActivity launchTestActivity(@ColorInt int navigationBarColor,
+            boolean lightNavigationBar, @NonNull DimmingTestMode dimmingTestMode) {
+        return TestActivity.startSync(activity -> {
+            final View contentView;
+            switch (dimmingTestMode) {
+                case NO_DIMMING_DIALOG:
+                case DIMMING_DIALOG_ABOVE_IME: {
+                    final LinearLayout layout = new LinearLayout(activity);
+                    layout.setOrientation(LinearLayout.VERTICAL);
+                    final ImeAwareEditText editText = new ImeAwareEditText(activity);
+                    editText.setPrivateImeOptions(TEST_MARKER);
+                    editText.setHint("editText");
+                    editText.requestFocus();
+                    editText.scheduleShowSoftInput();
+                    layout.addView(editText);
+                    contentView = layout;
+                    break;
+                }
+                case DIMMING_DIALOG_BEHIND_IME: {
+                    final View view = new View(activity);
+                    view.setLayoutParams(new ViewGroup.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+                    contentView = view;
+                    break;
+                }
+                default:
+                    throw new IllegalStateException("unknown mode=" + dimmingTestMode);
+            }
+            activity.getWindow().setNavigationBarColor(navigationBarColor);
+            updateSystemUiVisibility(contentView,
+                    lightNavigationBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0,
+                    SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+            return contentView;
+        });
+    }
+
+    private AutoCloseable showDialogIfNecessary(
+            @NonNull Activity activity, @NonNull DimmingTestMode dimmingTestMode) {
+        switch (dimmingTestMode) {
+            case NO_DIMMING_DIALOG:
+                // Dialog is not necessary.
+                return () -> { };
+            case DIMMING_DIALOG_ABOVE_IME: {
+                final AlertDialog alertDialog = getOnMainSync(() -> {
+                    final TextView textView = new TextView(activity);
+                    textView.setText("Dummy");
+                    textView.requestFocus();
+                    final AlertDialog dialog = new AlertDialog.Builder(activity)
+                            .setView(textView)
+                            .create();
+                    dialog.getWindow().setFlags(FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM,
+                            FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+                    dialog.show();
+                    return dialog;
+                });
+                // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
+                // the UI thread.
+                return () -> alertDialog.dismiss();
+            }
+            case DIMMING_DIALOG_BEHIND_IME: {
+                final AlertDialog alertDialog = getOnMainSync(() -> {
+                    final ImeAwareEditText editText = new ImeAwareEditText(activity);
+                    editText.setPrivateImeOptions(TEST_MARKER);
+                    editText.setHint("editText");
+                    editText.requestFocus();
+                    editText.scheduleShowSoftInput();
+                    final AlertDialog dialog = new AlertDialog.Builder(activity)
+                            .setView(editText)
+                            .create();
+                    dialog.getWindow().setFlags(FLAG_DIM_BEHIND,
+                            FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+                    dialog.show();
+                    return dialog;
+                });
+                // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
+                // the UI thread.
+                return () -> alertDialog.dismiss();
+            }
+            default:
+                throw new IllegalStateException("unknown mode=" + dimmingTestMode);
+        }
+    }
+
+    @NonNull
+    private ImeSettings.Builder imeSettingForSolidNavigationBar(@ColorInt int navigationBarColor,
+            boolean lightNavigationBar) {
+        final ImeSettings.Builder builder = new ImeSettings.Builder();
+        builder.setNavigationBarColor(navigationBarColor);
+        if (lightNavigationBar) {
+            builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+        }
+        return builder;
+    }
+
+    @NonNull
+    private ImeSettings.Builder imeSettingForFloatingIme(@ColorInt int navigationBarColor,
+            boolean lightNavigationBar) {
+        final ImeSettings.Builder builder = new ImeSettings.Builder();
+        builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        // As documented, Window#setNavigationBarColor() is actually ignored when the IME window
+        // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We are calling setNavigationBarColor()
+        // to ensure it.
+        builder.setNavigationBarColor(navigationBarColor);
+        if (lightNavigationBar) {
+            // As documented, SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR is actually ignored when the IME
+            // window does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We set this flag just to
+            // ensure it.
+            builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+        }
+        return builder;
+    }
+
+    @NonNull
+    private Bitmap getNavigationBarBitmap(@NonNull ImeSettings.Builder builder,
+            @ColorInt int appNavigationBarColor, boolean appLightNavigationBar,
+            int navigationBarHeight, @NonNull DimmingTestMode dimmingTestMode)
+            throws Exception {
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(), uiAutomation, builder)) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final TestActivity activity = launchTestActivity(
+                    appNavigationBarColor, appLightNavigationBar, dimmingTestMode);
+
+            // Show AlertDialog if necessary, based on the dimming test mode.
+            try (AutoCloseable dialogCloser = showDialogIfNecessary(
+                    activity, dimmingTestMode)) {
+                // Wait until the MockIme gets bound to the TestActivity.
+                expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+                // Wait until "onStartInput" gets called for the EditText.
+                expectEvent(stream, event -> {
+                    if (!TextUtils.equals("onStartInputView", event.getEventName())) {
+                        return false;
+                    }
+                    final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                    return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+                }, TIMEOUT);
+
+                // Wait until MockIme's layout becomes stable.
+                final ImeLayoutInfo lastLayout =
+                        waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+                assertNotNull(lastLayout);
+
+                final Bitmap bitmap = uiAutomation.takeScreenshot();
+                return Bitmap.createBitmap(bitmap, 0, bitmap.getHeight() - navigationBarHeight,
+                        bitmap.getWidth(), navigationBarHeight);
+            }
+        }
+    }
+
+    @Test
+    public void testSetNavigationBarColor() throws Exception {
+        final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+        // Make sure that Window#setNavigationBarColor() works for IMEs.
+        expectNavigationBarColorSupported(color ->
+                getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, false),
+                        Color.BLACK, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.NO_DIMMING_DIALOG));
+
+        // Make sure that IME's navigation bar can be transparent
+        expectNavigationBarColorSupported(color ->
+                getNavigationBarBitmap(imeSettingForSolidNavigationBar(Color.TRANSPARENT, false),
+                        color, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.NO_DIMMING_DIALOG));
+
+        // Make sure that Window#setNavigationBarColor() is ignored when
+        // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is unset
+        expectNavigationBarColorNotSupported(color ->
+                getNavigationBarBitmap(imeSettingForFloatingIme(color, false),
+                        Color.BLACK, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.NO_DIMMING_DIALOG));
+    }
+
+    @Test
+    public void testLightNavigationBar() throws Exception {
+        final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+        assumeTrue("This test does not make sense if light navigation bar is not supported"
+                + " even for typical Activity", info.supportsLightNavigationBar());
+
+        // Make sure that SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR works for IMEs (Bug 69002467).
+        expectLightNavigationBarSupported((color, lightMode) ->
+                getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+                        Color.BLACK, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.NO_DIMMING_DIALOG));
+
+        // Make sure that IMEs can opt-out navigation bar custom rendering, including
+        // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, by un-setting FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag
+        // so that it can be controlled by the target application instead (Bug 69111208).
+        expectLightNavigationBarSupported((color, lightMode) ->
+                getNavigationBarBitmap(imeSettingForFloatingIme(Color.BLACK, false),
+                        color, lightMode, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.NO_DIMMING_DIALOG));
+    }
+
+    @Test
+    public void testDimmingWindow() throws Exception {
+        final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+        assumeTrue("This test does not make sense if dimming windows do not affect light "
+                + " light navigation bar for typical Activities",
+                info.supportsDimmingWindowLightNavigationBarOverride());
+
+        // Make sure that SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR works for IMEs, even if a dimming
+        // window is shown behind the IME window.
+        expectLightNavigationBarSupported((color, lightMode) ->
+                getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+                        Color.BLACK, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.DIMMING_DIALOG_BEHIND_IME));
+
+        // If a dimming window is shown above the IME window, IME window's
+        // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR should be canceled.
+        expectLightNavigationBarNotSupported((color, lightMode) ->
+                getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+                        Color.BLACK, false, info.getBottomNavigationBerHeight(),
+                        DimmingTestMode.DIMMING_DIALOG_ABOVE_IME));
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
new file mode 100644
index 0000000..b320f88
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class OnScreenPositionTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
+
+    private static final String TEST_MARKER = "android.view.inputmethod.cts.OnScreenPositionTest";
+
+    public EditText launchTestActivity() {
+        final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setPrivateImeOptions(TEST_MARKER);
+            editText.setHint("editText");
+            editText.requestFocus();
+            editTextRef.set(editText);
+
+            layout.addView(editText);
+            return layout;
+        });
+        return editTextRef.get();
+    }
+
+    private static final int EXPECTED_KEYBOARD_HEIGHT = 100;
+
+    /**
+     * Regression test for Bug 33308065.
+     */
+    @Test
+    public void testImeIsNotBehindNavBar() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder()
+                        .setInputViewHeightWithoutSystemWindowInset(EXPECTED_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final EditText editText = launchTestActivity();
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), editText);
+
+            // Wait until "onStartInput" gets called for the EditText.
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInputView", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+            }, TIMEOUT);
+
+            // Wait until MockIme's layout becomes stable.
+            final ImeLayoutInfo lastLayout =
+                    waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+            assertNotNull(lastLayout);
+
+            // We consider that the screenRectWithoutNavBar is a union of those two rects.
+            // See the following methods for details.
+            //  - DecorView#getColorViewTopInset(int, int)
+            //  - DecorView#getColorViewBottomInset(int, int)
+            //  - DecorView#getColorViewRightInset(int, int)
+            //  - DecorView#getColorViewLeftInset(int, int)
+            final Rect screenRectWithoutNavBar = lastLayout.getScreenRectWithoutStableInset();
+            screenRectWithoutNavBar.union(lastLayout.getScreenRectWithoutSystemWindowInset());
+
+            final Rect keyboardViewBounds = lastLayout.getInputViewBoundsInScreen();
+            // By default, the above region must contain the keyboard view region.
+            assertTrue("screenRectWithoutNavBar(" + screenRectWithoutNavBar + ") must"
+                    + " contain keyboardViewBounds(" + keyboardViewBounds + ")",
+                    screenRectWithoutNavBar.contains(keyboardViewBounds));
+
+            // Make sure that the keyboard height is expected.  Here we assume that the expected
+            // height is small enough for all the Android-based devices to show.
+            assertEquals(EXPECTED_KEYBOARD_HEIGHT,
+                    lastLayout.getInputViewBoundsInScreen().height());
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
new file mode 100644
index 0000000..619c852
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.SearchView;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SearchViewTest extends EndToEndImeTestBase {
+    static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    public SearchView launchTestActivity() {
+        final AtomicReference<SearchView> searchViewRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText initialFocusedEditText = new EditText(activity);
+            initialFocusedEditText.setHint("initialFocusedTextView");
+
+            final SearchView searchView = new SearchView(activity);
+            searchViewRef.set(searchView);
+
+            searchView.setQueryHint("hint");
+            searchView.setIconifiedByDefault(false);
+            searchView.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
+            searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
+
+            layout.addView(initialFocusedEditText);
+            layout.addView(searchView);
+            return layout;
+        });
+        return searchViewRef.get();
+    }
+
+    @Test
+    public void testTapThenSetQuery() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final SearchView searchView = launchTestActivity();
+
+            // Emulate tap event on SearchView
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), searchView);
+
+            // Expect input to bind since EditText is focused.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Wait until "showSoftInput" gets called with a real InputConnection
+            expectEvent(stream, event ->
+                    "showSoftInput".equals(event.getEventName())
+                            && !event.getExitState().hasDummyInputConnection(),
+                    CHECK_EXIT_EVENT_ONLY, TIMEOUT);
+
+            // Make sure that "setQuery" triggers "hideSoftInput" in the IME side.
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> searchView.setQuery("test", true /* submit */));
+            expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
new file mode 100644
index 0000000..48710ec
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -0,0 +1,47 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+public class EndToEndImeTestBase {
+
+    @BeforeClass
+    public static void assumeFeatureInputMethod() {
+        assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
+                InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_INPUT_METHODS));
+    }
+
+    @Before
+    public void showStateInitializeActivity() {
+        final Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(InstrumentationRegistry.getTargetContext(), StateInitializeActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl b/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl
new file mode 100644
index 0000000..961f977f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.os.ResultReceiver;
+
+interface IWindowFocusStealer {
+    void stealWindowFocus(in IBinder parentAppWindowToken, in ResultReceiver resultReceiver);
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java
new file mode 100644
index 0000000..b48e750
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java
@@ -0,0 +1,92 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+public class ImeAwareEditText extends EditText {
+    private boolean mHasPendingShowSoftInputRequest;
+    final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
+
+    public ImeAwareEditText(Context context) {
+        super(context, null);
+    }
+
+    public ImeAwareEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * This method is called back by the system when the system is about to establish a connection
+     * to the current input method.
+     *
+     * <p>This is a good and reliable signal to schedule a pending task to call
+     * {@link InputMethodManager#showSoftInput(View, int)}.</p>
+     *
+     * @param editorInfo context about the text input field.
+     * @return {@link InputConnection} to be passed to the input method.
+     */
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
+        final InputConnection ic = super.onCreateInputConnection(editorInfo);
+        if (mHasPendingShowSoftInputRequest) {
+            removeCallbacks(mRunShowSoftInputIfNecessary);
+            post(mRunShowSoftInputIfNecessary);
+        }
+        return ic;
+    }
+
+    private void showSoftInputIfNecessary() {
+        if (mHasPendingShowSoftInputRequest) {
+            final InputMethodManager imm =
+                    getContext().getSystemService(InputMethodManager.class);
+            imm.showSoftInput(this, 0);
+            mHasPendingShowSoftInputRequest = false;
+        }
+    }
+
+    public void scheduleShowSoftInput() {
+        final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+        if (imm.isActive(this)) {
+            // This means that ImeAwareEditText is already connected to the IME.
+            // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
+            mHasPendingShowSoftInputRequest = false;
+            removeCallbacks(mRunShowSoftInputIfNecessary);
+            imm.showSoftInput(this, 0);
+            return;
+        }
+
+        // Otherwise, InputMethodManager#showSoftInput() should be deferred after
+        // onCreateInputConnection().
+        mHasPendingShowSoftInputRequest = true;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
index 3735c33..fa6fa9e 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
@@ -21,6 +21,8 @@
 
 public final class InputConnectionTestUtils {
 
+    private static final String U1F427 = "\uD83D\uDC27";
+
     /**
      * A utility function to generate test string for input method APIs.  There are several
      * pre-defined meta characters that are useful for unit tests.
@@ -41,7 +43,6 @@
      * @return A {@link CharSequence} object with text selection specified by the meta characters.
      */
     public static CharSequence formatString(final String formatString) {
-        final String U1F427 = "\uD83D\uDC27";
         final SpannableStringBuilder builder = new SpannableStringBuilder();
         int selectionStart = -1;
         int selectionEnd = -1;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
new file mode 100644
index 0000000..875fc9f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.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 android.view.inputmethod.cts.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.SparseIntArray;
+
+import java.util.Arrays;
+import java.util.OptionalDouble;
+import java.util.function.Supplier;
+
+/**
+ * A utility class to evaluate if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is
+ * supported on the device or not.
+ */
+public class LightNavigationBarVerifier {
+
+    /** This value actually does not have strong rationale. */
+    private static final float LIGHT_NAVBAR_SUPPORTED_THRESHOLD = 20.0f;
+
+    /** This value actually does not have strong rationale. */
+    private static final float LIGHT_NAVBAR_NOT_SUPPORTED_THRESHOLD = 5.0f;
+
+    @FunctionalInterface
+    public interface ScreenshotSupplier {
+        @NonNull
+        Bitmap takeScreenshot(@ColorInt int navigationBarColor, boolean lightMode) throws Exception;
+    }
+
+    public enum ResultType {
+        /**
+         * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seems to be not supported.
+         */
+        NOT_SUPPORTED,
+        /**
+         * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seems to be supported.
+         */
+        SUPPORTED,
+        /**
+         * Not sure if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is supported.
+         */
+        UNKNOWN,
+    }
+
+    static final class Result {
+        @NonNull
+        public final ResultType mResult;
+        @NonNull
+        public final Supplier<String> mAssertionMessageProvider;
+
+        Result(@NonNull ResultType result,
+                @Nullable Supplier<String> assertionMessageProvider) {
+            mResult = result;
+            mAssertionMessageProvider = assertionMessageProvider;
+        }
+
+        @NonNull
+        public ResultType getResult() {
+            return mResult;
+        }
+
+        @NonNull
+        public String getAssertionMessage() {
+            return mAssertionMessageProvider.get();
+        }
+    }
+
+    /**
+     * Asserts that {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is supported on
+     * this device.
+     *
+     * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+     */
+    public static void expectLightNavigationBarSupported(
+            @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final Result result = verify(screenshotSupplier);
+        assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult());
+    }
+
+    /**
+     * Asserts that {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is not supported
+     * on this device.
+     *
+     * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+     */
+    public static void expectLightNavigationBarNotSupported(
+            @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final Result result = verify(screenshotSupplier);
+        assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult());
+    }
+
+    @FunctionalInterface
+    private interface ColorOperator {
+        int operate(@ColorInt int color1, @ColorInt int color2);
+    }
+
+    private static int[] operateColorArrays(@NonNull int[] pixels1, @NonNull int[] pixels2,
+            @NonNull ColorOperator operator) {
+        assertEquals(pixels1.length, pixels2.length);
+        final int numPixels = pixels1.length;
+        final int[] result = new int[numPixels];
+        for (int i = 0; i < numPixels; ++i) {
+            result[i] = operator.operate(pixels1[i], pixels2[i]);
+        }
+        return result;
+    }
+
+    @NonNull
+    private static int[] getPixels(@NonNull Bitmap bitmap) {
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+        final int[] pixels = new int[width * height];
+        bitmap.getPixels(pixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */,
+                width, height);
+        return pixels;
+    }
+
+    @NonNull
+    static Result verify(
+            @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final int[] darkNavBarPixels = getPixels(
+                screenshotSupplier.takeScreenshot(Color.BLACK, false));
+        final int[] lightNavBarPixels = getPixels(
+                screenshotSupplier.takeScreenshot(Color.BLACK, true));
+
+        if (darkNavBarPixels.length != lightNavBarPixels.length) {
+            throw new IllegalStateException("Pixel count mismatch."
+                    + " dark=" + darkNavBarPixels.length + " light=" + lightNavBarPixels.length);
+        }
+
+        final int[][] channelDiffs = new int[][] {
+                operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+                        (dark, light) -> Color.red(dark) - Color.red(light)),
+                operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+                        (dark, light) -> Color.green(dark) - Color.green(light)),
+                operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+                        (dark, light) -> Color.blue(dark) - Color.blue(light)),
+        };
+
+        if (Arrays.stream(channelDiffs).allMatch(
+                diffs -> Arrays.stream(diffs).allMatch(diff -> diff == 0))) {
+            // Exactly the same image.  Safe to conclude that light navigation bar is not supported.
+            return new Result(ResultType.NOT_SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+        }
+
+        if (Arrays.stream(channelDiffs).anyMatch(diffs -> {
+            final OptionalDouble average = Arrays.stream(diffs).filter(diff -> diff != 0).average();
+            return average.isPresent() && average.getAsDouble() > LIGHT_NAVBAR_SUPPORTED_THRESHOLD;
+        })) {
+            // If darkNavBarPixels have brighter pixels in at least one color channel
+            // (red, green, blue), we consider that it is because light navigation bar takes effect.
+            // Keep in mind that with SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR navigation bar buttons
+            // are expected to become darker.  So if everything works fine, darkNavBarPixels should
+            // have brighter pixels.
+            return new Result(ResultType.SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+        }
+
+        if (Arrays.stream(channelDiffs).allMatch(diffs -> {
+            final OptionalDouble average = Arrays.stream(diffs).filter(diff -> diff != 0).average();
+            return average.isPresent()
+                    && Math.abs(average.getAsDouble()) < LIGHT_NAVBAR_NOT_SUPPORTED_THRESHOLD;
+        })) {
+            // If all color channels (red, green, blue) have diffs less than a certain threshold,
+            // consider light navigation bar is not supported.  For instance, some devices may
+            // intentionally add some fluctuations to the navigation bar button colors/positions.
+            return new Result(ResultType.NOT_SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+        }
+
+        return new Result(ResultType.UNKNOWN, () -> dumpDiffStreams(channelDiffs));
+    }
+
+    @NonNull
+    private static String dumpDiffStreams(@NonNull int[][] diffStreams) {
+        final String[] channelNames = {"red", "green", "blue"};
+        final StringBuilder sb = new StringBuilder();
+        sb.append("diff histogram: ");
+        for (int i = 0; i < diffStreams.length; ++i) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            final int[] channel = diffStreams[i];
+            final SparseIntArray histogram = new SparseIntArray();
+            Arrays.stream(channel).sorted().forEachOrdered(
+                    diff -> histogram.put(diff, histogram.get(diff, 0) + 1));
+            sb.append(channelNames[i]).append(": ").append(histogram);
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
new file mode 100644
index 0000000..9d2e308
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
@@ -0,0 +1,172 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * A utility class to evaluate if {@link android.view.Window#setNavigationBarColor(int)} is
+ * supported on the device or not.
+ */
+public class NavigationBarColorVerifier {
+
+    private static final class ScreenShot {
+        @ColorInt
+        public final int mBackgroundColor;
+        @NonNull
+        public final int[] mPixels;
+
+        ScreenShot(@ColorInt int backgroundColor, @NonNull Bitmap bitmap) {
+            mBackgroundColor = backgroundColor;
+            final int width = bitmap.getWidth();
+            final int height = bitmap.getHeight();
+            mPixels = new int[width * height];
+            bitmap.getPixels(mPixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */,
+                    width, height);
+        }
+    }
+
+    public enum ResultType {
+        /**
+         * {@link android.view.Window#setNavigationBarColor(int)} seems to be not supported.
+         */
+        NOT_SUPPORTED,
+        /**
+         * {@link android.view.Window#setNavigationBarColor(int)} seems to be supported.
+         */
+        SUPPORTED,
+    }
+
+    static final class Result {
+        @NonNull
+        public final ResultType mResult;
+        @NonNull
+        public final String mAssertionMessage;
+
+        Result(@NonNull ResultType result, @NonNull String assertionMessage) {
+            mResult = result;
+            mAssertionMessage = assertionMessage;
+        }
+
+        @NonNull
+        public ResultType getResult() {
+            return mResult;
+        }
+
+        @NonNull
+        public String getAssertionMessage() {
+            return mAssertionMessage;
+        }
+    }
+
+    @FunctionalInterface
+    public interface ScreenshotSupplier {
+        @NonNull
+        Bitmap takeScreenshot(@ColorInt int navigationBarColor) throws Exception;
+    }
+
+    @NonNull
+    static Result verify(@NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final ArrayList<ScreenShot> screenShots = new ArrayList<>();
+        final int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
+        for (int color : colors) {
+            screenShots.add(new ScreenShot(color, screenshotSupplier.takeScreenshot(color)));
+        }
+        return verifyInternal(screenShots);
+    }
+
+    /**
+     * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is supported on this
+     * device.
+     *
+     * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+     */
+    public static void expectNavigationBarColorSupported(
+            @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final Result result = verify(screenshotSupplier);
+        assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult());
+    }
+
+    /**
+     * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is not supported on this
+     * device.
+     *
+     * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+     */
+    public static void expectNavigationBarColorNotSupported(
+            @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+        final Result result = verify(screenshotSupplier);
+        assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult());
+    }
+
+    private static Result verifyInternal(@NonNull ArrayList<ScreenShot> screenShots) {
+        final int numScreenShots = screenShots.size();
+        assertTrue(
+                "This algorithm requires at least 3 screen shots. size=" + numScreenShots,
+                numScreenShots >= 3);
+        assertEquals("All screenshots must have different background colors",
+                numScreenShots,
+                screenShots.stream()
+                        .mapToInt(screenShot -> screenShot.mBackgroundColor)
+                        .distinct()
+                        .count());
+        assertEquals("All screenshots must have the same pixel count",
+                1,
+                screenShots.stream()
+                        .mapToInt(screenShot -> screenShot.mPixels.length)
+                        .distinct()
+                        .count());
+
+        long numCompletelyFixedColorPixels = 0;
+        long numColorChangedAsRequestedPixels = 0;
+        final int numPixels = screenShots.get(0).mPixels.length;
+        for (int pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) {
+            final int i = pixelIndex;
+            final long numFoundColors = screenShots.stream()
+                    .mapToInt(screenShot -> screenShot.mPixels[i])
+                    .distinct()
+                    .count();
+            if (numFoundColors == 1) {
+                numCompletelyFixedColorPixels++;
+            }
+            final long matchingScore =  screenShots.stream()
+                    .filter(screenShot -> screenShot.mPixels[i] == screenShot.mBackgroundColor)
+                    .count();
+            if (matchingScore == numScreenShots) {
+                numColorChangedAsRequestedPixels++;
+            }
+        }
+        final String assertionMessage = "numPixels=" + numPixels
+                + " numColorChangedAsRequestedPixels=" + numColorChangedAsRequestedPixels
+                + " numCompletelyFixedColorPixels=" + numCompletelyFixedColorPixels;
+
+        // OK, even 1 pixel is enough.
+        if (numColorChangedAsRequestedPixels > 0) {
+            return new Result(ResultType.SUPPORTED, assertionMessage);
+        }
+
+        return new Result(ResultType.NOT_SUPPORTED, assertionMessage);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
new file mode 100644
index 0000000..d572716
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
@@ -0,0 +1,241 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+
+import android.app.AlertDialog;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.util.Size;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.TextView;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A utility class to write tests that depend on some capabilities related to navigation bar.
+ */
+public class NavigationBarInfo {
+    private static final long BEFORE_SCREENSHOT_WAIT = TimeUnit.SECONDS.toMillis(1);
+
+    private final boolean mHasBottomNavigationBar;
+    private final int mBottomNavigationBerHeight;
+    private final boolean mSupportsNavigationBarColor;
+    private final boolean mSupportsLightNavigationBar;
+    private final boolean mSupportsDimmingWindowLightNavigationBarOverride;
+
+    private NavigationBarInfo(boolean hasBottomNavigationBar, int bottomNavigationBerHeight,
+            boolean supportsNavigationBarColor, boolean supportsLightNavigationBar,
+            boolean supportsDimmingWindowLightNavigationBarOverride) {
+        mHasBottomNavigationBar = hasBottomNavigationBar;
+        mBottomNavigationBerHeight = bottomNavigationBerHeight;
+        mSupportsNavigationBarColor = supportsNavigationBarColor;
+        mSupportsLightNavigationBar = supportsLightNavigationBar;
+        mSupportsDimmingWindowLightNavigationBarOverride =
+                supportsDimmingWindowLightNavigationBarOverride;
+    }
+
+    @Nullable
+    private static NavigationBarInfo sInstance;
+
+    /**
+     * Returns a {@link NavigationBarInfo} instance.
+     *
+     * <p>As a performance optimizations, this method internally caches the previous result and
+     * returns the same result if this gets called multiple times.</p>
+     *
+     * <p>Note: The caller should be aware that this method may launch {@link TestActivity}
+     * internally.</p>
+     *
+     * @return {@link NavigationBarInfo} obtained with {@link TestActivity}.
+     */
+    @NonNull
+    public static NavigationBarInfo getInstance() throws Exception {
+        if (sInstance != null) {
+            return sInstance;
+        }
+
+        final int actualBottomInset;
+        {
+            final AtomicReference<View> viewRef = new AtomicReference<>();
+            TestActivity.startSync(activity -> {
+                final View view = new View(activity);
+                view.setLayoutParams(new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+                viewRef.set(view);
+                return view;
+            }, Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+            final View view = viewRef.get();
+
+            final WindowInsets windowInsets = getOnMainSync(() -> view.getRootWindowInsets());
+            if (!windowInsets.hasStableInsets() || windowInsets.getStableInsetBottom() <= 0) {
+                return new NavigationBarInfo(false, 0, false, false, false);
+            }
+            final Size displaySize = getOnMainSync(() -> {
+                final Point size = new Point();
+                view.getDisplay().getRealSize(size);
+                return new Size(size.x, size.y);
+            });
+
+            final Rect viewBoundsOnScreen = getOnMainSync(() -> {
+                final int[] xy = new int[2];
+                view.getLocationOnScreen(xy);
+                final int x = xy[0];
+                final int y = xy[1];
+                return new Rect(x, y, x + view.getWidth(), y + view.getHeight());
+            });
+            actualBottomInset = displaySize.getHeight() - viewBoundsOnScreen.bottom;
+            if (actualBottomInset != windowInsets.getStableInsetBottom()) {
+                sInstance = new NavigationBarInfo(false, 0, false, false, false);
+                return sInstance;
+            }
+        }
+
+        final boolean colorSupported = NavigationBarColorVerifier.verify(
+                color -> getBottomNavigationBarBitmapForActivity(
+                        color, false /* lightNavigationBar */, actualBottomInset,
+                        false /* showDimmingDialog */)).getResult()
+                == NavigationBarColorVerifier.ResultType.SUPPORTED;
+
+        final boolean lightModeSupported = LightNavigationBarVerifier.verify(
+                (color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
+                        color, lightNavigationBar, actualBottomInset,
+                        false /* showDimmingDialog */)).getResult()
+                == LightNavigationBarVerifier.ResultType.SUPPORTED;
+
+        final boolean dimmingSupported = lightModeSupported && LightNavigationBarVerifier.verify(
+                (color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
+                        color, lightNavigationBar, actualBottomInset,
+                        true /* showDimmingDialog */)).getResult()
+                == LightNavigationBarVerifier.ResultType.NOT_SUPPORTED;
+
+        sInstance = new NavigationBarInfo(
+                true, actualBottomInset, colorSupported, lightModeSupported, dimmingSupported);
+        return sInstance;
+    }
+
+    @NonNull
+    private static Bitmap getBottomNavigationBarBitmapForActivity(
+            @ColorInt int navigationBarColor, boolean lightNavigationBar,
+            int bottomNavigationBarHeight, boolean showDimmingDialog) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+
+        final TestActivity testActivity = TestActivity.startSync(activity -> {
+            final View view = new View(activity);
+            activity.getWindow().setNavigationBarColor(navigationBarColor);
+
+            // Set/unset SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR if necessary.
+            final int currentVis = view.getSystemUiVisibility();
+            final int newVis = (currentVis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
+                    | (lightNavigationBar ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0);
+            if (currentVis != newVis) {
+                view.setSystemUiVisibility(newVis);
+            }
+
+            view.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+            return view;
+        });
+        instrumentation.waitForIdleSync();
+
+        final AlertDialog dialog;
+        if (showDimmingDialog) {
+            dialog = getOnMainSync(() -> {
+                final TextView textView = new TextView(testActivity);
+                textView.setText("Dimming Window");
+                final AlertDialog alertDialog = new AlertDialog.Builder(testActivity)
+                        .setView(textView)
+                        .create();
+                alertDialog.getWindow().setFlags(FLAG_DIM_BEHIND, FLAG_DIM_BEHIND);
+                alertDialog.show();
+                return alertDialog;
+            });
+        } else {
+            dialog = null;
+        }
+
+        Thread.sleep(BEFORE_SCREENSHOT_WAIT);
+
+        final Bitmap fullBitmap = getInstrumentation().getUiAutomation().takeScreenshot();
+        final Bitmap bottomNavBarBitmap = Bitmap.createBitmap(fullBitmap, 0,
+                fullBitmap.getHeight() - bottomNavigationBarHeight, fullBitmap.getWidth(),
+                bottomNavigationBarHeight);
+        if (dialog != null) {
+            // Dialog#dismiss() is a thread safe method so we don't need to call this from the UI
+            // thread.
+            dialog.dismiss();
+        }
+        return bottomNavBarBitmap;
+    }
+
+    /**
+     * @return {@code true} if this device seems to have bottom navigation bar.
+     */
+    public boolean hasBottomNavigationBar() {
+        return mHasBottomNavigationBar;
+    }
+
+    /**
+     * @return height of the bottom navigation bar. Valid only when
+     *         {@link #hasBottomNavigationBar()} returns {@code true}
+     */
+    public int getBottomNavigationBerHeight() {
+        return mBottomNavigationBerHeight;
+    }
+
+    /**
+     * @return {@code true} if {@link android.view.Window#setNavigationBarColor(int)} seem to take
+     *         effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
+     *         {@code true}
+     */
+    public boolean supportsNavigationBarColor() {
+        return mSupportsNavigationBarColor;
+    }
+
+    /**
+     * @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seem to
+     *         take effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
+     *         {@code true}
+     */
+    public boolean supportsLightNavigationBar() {
+        return mSupportsLightNavigationBar;
+    }
+
+    /**
+     * @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} will be
+     *         canceled when a {@link android.view.Window} with
+     *         {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} is shown.
+     */
+    public boolean supportsDimmingWindowLightNavigationBarOverride() {
+        return mSupportsDimmingWindowLightNavigationBarOverride;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java
new file mode 100644
index 0000000..c4cc95f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A nop {@link Activity} to make sure that the test starts from a deterministic state.
+ *
+ * <p>Currently this {@link Activity} makes sure the following things</p>
+ * <li>
+ *     <ul>Hide the software keyboard with
+ *     {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul>
+ *     <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}.
+ *     </ul>
+ * </li>
+ */
+public class StateInitializeActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        final View view = new View(this);
+        view.setBackgroundColor(Color.WHITE);
+        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+
+        // Make sure that IME is hidden in the initial state
+        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+        // Make sure that navigation bar is rendered with black (if supported).
+        getWindow().setNavigationBarColor(Color.BLACK);
+
+        setContentView(view);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
new file mode 100644
index 0000000..c98ab6f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+public final class TestActivity extends Activity {
+
+    private static final AtomicReference<Function<TestActivity, View>> sInitializer =
+            new AtomicReference<>();
+
+    private Function<TestActivity, View> mInitializer = null;
+
+    private AtomicBoolean mIgnoreBackKey = new AtomicBoolean();
+
+    private long mOnBackPressedCallCount;
+
+    /**
+     * Controls how {@link #onBackPressed()} behaves.
+     *
+     * <p>TODO: Use {@link android.app.AppComponentFactory} instead to customise the behavior of
+     * {@link TestActivity}.</p>
+     *
+     * @param ignore {@code true} when {@link TestActivity} should do nothing when
+     *               {@link #onBackPressed()} is called
+     */
+    @AnyThread
+    public void setIgnoreBackKey(boolean ignore) {
+        mIgnoreBackKey.set(ignore);
+    }
+
+    @UiThread
+    public long getOnBackPressedCallCount() {
+        return mOnBackPressedCallCount;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (mInitializer == null) {
+            mInitializer = sInitializer.get();
+        }
+        setContentView(mInitializer.apply(this));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onBackPressed() {
+        ++mOnBackPressedCallCount;
+        if (mIgnoreBackKey.get()) {
+            return;
+        }
+        super.onBackPressed();
+    }
+
+    /**
+     * 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
+     * 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 startSync(
+            @NonNull Function<TestActivity, View> activityInitializer) {
+        return startSync(activityInitializer, 0 /* noAnimation */);
+    }
+
+    /**
+     * 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
+     * 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)}
+     * @param additionalFlags flags to be set to {@link Intent#setFlags(int)}
+     * @return {@link TestActivity} launched
+     */
+    public static TestActivity startSync(
+            @NonNull Function<TestActivity, View> activityInitializer,
+            int additionalFlags) {
+        sInitializer.set(activityInitializer);
+        final Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(InstrumentationRegistry.getContext(), TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .addFlags(additionalFlags);
+        return (TestActivity) InstrumentationRegistry
+                .getInstrumentation().startActivitySync(intent);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
new file mode 100644
index 0000000..63d9fe9
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.app.Instrumentation;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+
+public final class TestUtils {
+    private static final long TIME_SLICE = 100;  // msec
+
+    /**
+     * Executes a call on the application's main thread, blocking until it is complete.
+     *
+     * <p>A simple wrapper for {@link Instrumentation#runOnMainSync(Runnable)}.</p>
+     *
+     * @param task task to be called on the UI thread
+     */
+    public static void runOnMainSync(@NonNull Runnable task) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(task);
+    }
+
+    /**
+     * Retrieves a value that needs to be obtained on the main thread.
+     *
+     * <p>A simple utility method that helps to return an object from the UI thread.</p>
+     *
+     * @param supplier callback to be called on the UI thread to return a value
+     * @param <T> Type of the value to be returned
+     * @return Value returned from {@code supplier}
+     */
+    public static <T> T getOnMainSync(@NonNull Supplier<T> supplier) {
+        final AtomicReference<T> result = new AtomicReference<>();
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(() -> result.set(supplier.get()));
+        return result.get();
+    }
+
+    /**
+     * Does polling loop on the UI thread to wait until the given condition is met.
+     *
+     * @param condition Condition to be satisfied. This is guaranteed to run on the UI thread.
+     * @param timeout timeout in millisecond
+     * @param message message to display when timeout occurs.
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    public static void waitOnMainUntil(
+            @NonNull BooleanSupplier condition, long timeout, String message)
+            throws TimeoutException {
+        final AtomicBoolean result = new AtomicBoolean();
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        while (!result.get()) {
+            if (timeout < 0) {
+                throw new TimeoutException(message);
+            }
+            instrumentation.runOnMainSync(() -> {
+                if (condition.getAsBoolean()) {
+                    result.set(true);
+                }
+            });
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException(e);
+            }
+            timeout -= TIME_SLICE;
+        }
+    }
+
+    /**
+     * Does polling loop on the UI thread to wait until the given condition is met.
+     *
+     * @param condition Condition to be satisfied. This is guaranteed to run on the UI thread.
+     * @param timeout timeout in millisecond
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    public static void waitOnMainUntil(@NonNull BooleanSupplier condition, long timeout)
+            throws TimeoutException {
+        waitOnMainUntil(condition, timeout, "");
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
new file mode 100644
index 0000000..d015ceb
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
@@ -0,0 +1,144 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.annotation.NonNull;
+
+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;
+
+/**
+ * Helper class to trigger the situation where window in a different process gains focus.
+ */
+public class WindowFocusStealer implements AutoCloseable {
+
+    private static final class MyResultReceiver extends ResultReceiver {
+        final BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<>(1);
+
+        MyResultReceiver() {
+            super(null);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueue.add(resultCode);
+        }
+
+        public void waitResult(long timeout) throws TimeoutException {
+            final Object result;
+            try {
+                result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException(e);
+            }
+            if (result == null) {
+                throw new TimeoutException();
+            }
+        }
+    }
+
+    @NonNull
+    private final IWindowFocusStealer mService;
+    @NonNull
+    private final Runnable mCloser;
+
+    /**
+     * Let a window in a different process gain the window focus.
+     *
+     * @param parentAppWindowToken Token returned from
+     *                             {@link android.view.View#getApplicationWindowToken()}
+     * @param timeout              timeout in millisecond
+     * @throws TimeoutException when failed to have the window focused within {@code timeout}
+     */
+    public void stealWindowFocus(IBinder parentAppWindowToken, long timeout)
+            throws TimeoutException {
+        final MyResultReceiver resultReceiver = new MyResultReceiver();
+        try {
+            mService.stealWindowFocus(parentAppWindowToken, resultReceiver);
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        }
+        resultReceiver.waitResult(timeout);
+    }
+
+    private WindowFocusStealer(@NonNull IWindowFocusStealer service, @NonNull Runnable closer) {
+        mService = service;
+        mCloser = closer;
+    }
+
+    /**
+     * Establishes a connection to the service.
+     *
+     * @param context {@link Context} to which {@link WindowFocusStealerService} belongs
+     * @param timeout timeout in millisecond
+     * @throws TimeoutException when failed to establish the connection within {@code timeout}
+     */
+    public static WindowFocusStealer connect(Context context, long timeout)
+            throws TimeoutException {
+        final BlockingQueue<IWindowFocusStealer> queue = new ArrayBlockingQueue<>(1);
+
+        final ServiceConnection connection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                queue.add(IWindowFocusStealer.Stub.asInterface(service));
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+            }
+        };
+
+        final Intent intent = new Intent();
+        intent.setClass(context, WindowFocusStealerService.class);
+        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+
+        final IWindowFocusStealer focusStealer;
+        try {
+            focusStealer = queue.poll(timeout, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+
+        if (focusStealer == null) {
+            throw new TimeoutException();
+        }
+
+        final AtomicBoolean closed = new AtomicBoolean(false);
+        return new WindowFocusStealer(focusStealer, () -> {
+            if (closed.compareAndSet(false, true)) {
+                context.unbindService(connection);
+            }
+        });
+    }
+
+    /**
+     * Removes the temporary window and clean up everything.
+     */
+    @Override
+    public void close() throws Exception {
+        mCloser.run();
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
new file mode 100644
index 0000000..afcd98d
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
@@ -0,0 +1,109 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.support.annotation.BinderThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public final class WindowFocusStealerService extends Service {
+
+    @Nullable
+    private TextView mCustomView;
+
+    private final class BinderService extends IWindowFocusStealer.Stub {
+        @BinderThread
+        @Override
+        public void stealWindowFocus(IBinder parentAppWindowToken, ResultReceiver resultReceiver) {
+            mMainHandler.post(() -> handleStealWindowFocus(parentAppWindowToken, resultReceiver));
+        }
+    }
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new BinderService();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        handleReset();
+        return false;
+    }
+
+    @MainThread
+    public void handleStealWindowFocus(
+            IBinder parentAppWindowToken, ResultReceiver resultReceiver) {
+        handleReset();
+
+        mCustomView = new TextView(this) {
+            private boolean mWindowInitiallyFocused = false;
+            /**
+             * {@inheritDoc}
+             */
+            @Override
+            public void onWindowFocusChanged(boolean hasWindowFocus) {
+                if (!mWindowInitiallyFocused && hasWindowFocus) {
+                    mWindowInitiallyFocused = true;
+                    resultReceiver.send(0, null);
+                }
+            }
+        };
+        mCustomView.setText("Popup");
+        mCustomView.setBackground(new ColorDrawable(Color.CYAN));
+        mCustomView.setElevation(0.5f);
+
+        WindowManager mWm = getSystemService(WindowManager.class);
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                150, 150, 10, 10,
+                WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.OPAQUE);
+        params.packageName = getPackageName();
+        params.token = parentAppWindowToken;
+
+        mWm.addView(mCustomView, params);
+    }
+
+    @MainThread
+    public void handleReset() {
+        if (mCustomView != null) {
+            getSystemService(WindowManager.class).removeView(mCustomView);
+            mCustomView = null;
+        }
+    }
+
+    @MainThread
+    public void onDestroy() {
+        super.onDestroy();
+        handleReset();
+    }
+
+}
diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk
index 2604bd2..df7aa54 100644
--- a/tests/jank/Android.mk
+++ b/tests/jank/Android.mk
@@ -33,7 +33,8 @@
     ctstestrunner \
     ub-uiautomator \
     ub-janktesthelper \
-    junit \
-    legacy-android-test
+    junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/jdwp/AndroidTest.xml b/tests/jdwp/AndroidTest.xml
index 19d1e76..462ed34 100644
--- a/tests/jdwp/AndroidTest.xml
+++ b/tests/jdwp/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JDWP test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctsjdwp/java.io.tmpdir" />
diff --git a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
index 51ded92..6a83791 100644
--- a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
+++ b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
@@ -21,13 +21,13 @@
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.MultiLineReceiver;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
@@ -41,20 +41,16 @@
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.ArrayUtil;
 import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.TimeVal;
-import com.google.common.base.Splitter;
 
-import vogar.ExpectationStore;
-import vogar.ModeId;
+import com.google.common.base.Splitter;
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FilenameFilter;
 import java.io.FileReader;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -62,6 +58,9 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import vogar.ExpectationStore;
+import vogar.ModeId;
+
 /**
  * A wrapper to run tests against Dalvik.
  */
@@ -378,7 +377,7 @@
                 dalvikArgs, abiName, runnerArgs,
                 includeFilters, excludeFilters, includeFile, excludeFile, collectTestsOnlyString);
         IShellOutputReceiver receiver = new MultiLineReceiver() {
-            private TestIdentifier test;
+            private TestDescription test;
 
             @Override
             public boolean isCancelled() {
@@ -399,12 +398,12 @@
                                 Collections.<String, String>emptyMap());
                         Log.logAndDisplay(LogLevel.INFO, TAG, line);
                     } else if (tag.equals(START_TEST)) {
-                        test = getTestIdentifier(parts[1]);
+                        test = getTestDescription(parts[1]);
                         listener.testStarted(test);
                     } else if (tag.equals(FAILURE)) {
                         listener.testFailed(test, parts[1]);
                     } else if (tag.equals(END_TEST)) {
-                        listener.testEnded(getTestIdentifier(parts[1]),
+                        listener.testEnded(getTestDescription(parts[1]),
                                 Collections.<String, String>emptyMap());
                     } else {
                         Log.logAndDisplay(LogLevel.INFO, TAG, line);
@@ -412,14 +411,14 @@
                 }
             }
 
-            private TestIdentifier getTestIdentifier(String name) {
+            private TestDescription getTestDescription(String name) {
                 String[] parts = name.split("#");
                 String className = parts[0];
                 String testName = "";
                 if (parts.length > 1) {
                     testName = parts[1];
                 }
-                return new TestIdentifier(className, testName);
+                return new TestDescription(className, testName);
             }
 
         };
diff --git a/tests/leanbackjank/Android.mk b/tests/leanbackjank/Android.mk
index 2b1b925..ab7eedc 100644
--- a/tests/leanbackjank/Android.mk
+++ b/tests/leanbackjank/Android.mk
@@ -33,11 +33,9 @@
     compatibility-device-util \
     ctstestrunner \
     ub-uiautomator \
-    ub-janktesthelper \
-    android-support-v17-leanback \
-    android-support-v7-recyclerview \
-    android-support-v4 \
-    legacy-android-test
+    ub-janktesthelper
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/leanbackjank/AndroidTest.xml b/tests/leanbackjank/AndroidTest.xml
index 31504b6..d941325 100644
--- a/tests/leanbackjank/AndroidTest.xml
+++ b/tests/leanbackjank/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS LeanbackJank test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="tv" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/leanbackjank/app/Android.mk b/tests/leanbackjank/app/Android.mk
index f539437..6ef8e87 100644
--- a/tests/leanbackjank/app/Android.mk
+++ b/tests/leanbackjank/app/Android.mk
@@ -28,25 +28,21 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_RESOURCE_DIR := \
-    $(TOP)/frameworks/support/v17/leanback/res \
-    $(TOP)/frameworks/support/v7/recyclerview/res \
-    $(LOCAL_PATH)/res
+LOCAL_USE_AAPT2 := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
     ctstestrunner \
     ub-uiautomator \
     ub-janktesthelper \
-    android-support-v4 \
-    android-support-v7-recyclerview \
-    android-support-v17-leanback \
     glide
 
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v17.leanback \
-        --extra-packages android.support.v7.recyclerview
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
diff --git a/tests/leanbackjank/app/AndroidManifest.xml b/tests/leanbackjank/app/AndroidManifest.xml
index 1e1ff3b..1ea3f3f 100644
--- a/tests/leanbackjank/app/AndroidManifest.xml
+++ b/tests/leanbackjank/app/AndroidManifest.xml
@@ -40,6 +40,8 @@
         android:label="@string/app_name"
         android:logo="@drawable/videos_by_google_banner"
         android:theme="@style/Theme.Example.Leanback" >
+        <uses-library android:name="android.test.runner" />
+
         <activity
             android:name=".ui.MainActivity"
             android:icon="@drawable/videos_by_google_banner"
diff --git a/tests/libcore/javautilcollections/AndroidTest.xml b/tests/libcore/javautilcollections/AndroidTest.xml
index 75be288..7ea7634 100644
--- a/tests/libcore/javautilcollections/AndroidTest.xml
+++ b/tests/libcore/javautilcollections/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore java.util Collection test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/libcore/jsr166/AndroidManifest.xml b/tests/libcore/jsr166/AndroidManifest.xml
index baae488..5fd3f6e 100644
--- a/tests/libcore/jsr166/AndroidManifest.xml
+++ b/tests/libcore/jsr166/AndroidManifest.xml
@@ -22,6 +22,9 @@
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.jsr166"
-                     android:label="CTS Libcore JSR166 test cases" />
+                     android:label="CTS Libcore JSR166 test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index 2a3e5d2..9926f57 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore JSR166 test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
     </target_preparer>
     <test class="com.android.compatibility.testtype.LibcoreTest" >
         <option name="package" value="android.libcore.cts.jsr166" />
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener" />
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/luni/AndroidManifest.xml b/tests/libcore/luni/AndroidManifest.xml
index 0389be6..4df4b93 100644
--- a/tests/libcore/luni/AndroidManifest.xml
+++ b/tests/libcore/luni/AndroidManifest.xml
@@ -23,6 +23,9 @@
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts"
-                     android:label="CTS Libcore test cases" />
+                     android:label="CTS Libcore test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index a8c4142..5a397f4 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
     </target_preparer>
     <test class="com.android.compatibility.testtype.LibcoreTest" >
         <option name="package" value="android.libcore.cts" />
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener" />
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/ojluni/AndroidManifest.xml b/tests/libcore/ojluni/AndroidManifest.xml
index c010a32..8c45c30 100644
--- a/tests/libcore/ojluni/AndroidManifest.xml
+++ b/tests/libcore/ojluni/AndroidManifest.xml
@@ -21,6 +21,9 @@
     <!-- important: instrument another package which actually contains AJUR -->
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.runner"
-                     android:label="CTS Libcore OJ test cases" />
+                     android:label="CTS Libcore OJ test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 1cebd4e..a222270 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore OJ test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -22,7 +23,7 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <!-- this has the CoreTestRunner which needs to be in a separate APK -->
+        <!-- this has the AJUR which needs to be in a separate APK -->
         <option name="test-file-name" value="CtsLibcoreTestRunner.apk" />
         <!-- this has just the instrumentation which acts as the tests we want to run -->
         <option name="test-file-name" value="CtsLibcoreOjTestCases.apk" />
@@ -31,8 +32,6 @@
         <option name="package" value="android.libcore.cts.oj" />
         <option name="instrumentation-arg" key="runnerBuilder"
                 value="com.android.cts.core.runner.support.TestNgRunnerBuilder"/>
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener"/>
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/okhttp/AndroidManifest.xml b/tests/libcore/okhttp/AndroidManifest.xml
index 151fda9..dfff563 100644
--- a/tests/libcore/okhttp/AndroidManifest.xml
+++ b/tests/libcore/okhttp/AndroidManifest.xml
@@ -17,12 +17,15 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.libcore.cts.okhttp">
     <uses-permission android:name="android.permission.INTERNET" />
-    <application>
+    <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.okhttp"
-                     android:label="CTS Libcore OkHttp test cases" />
+                     android:label="CTS Libcore OkHttp test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 4e79b80..1445c20 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore OkHttp test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
     </target_preparer>
     <test class="com.android.compatibility.testtype.LibcoreTest" >
         <option name="package" value="android.libcore.cts.okhttp" />
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener" />
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/wycheproof-bc/AndroidManifest.xml b/tests/libcore/wycheproof-bc/AndroidManifest.xml
index 15c5fd5..fb53977 100644
--- a/tests/libcore/wycheproof-bc/AndroidManifest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidManifest.xml
@@ -23,6 +23,9 @@
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.wycheproof.bouncycastle"
-                     android:label="CTS Libcore Wycheproof Bouncy Castle test cases" />
+                     android:label="CTS Libcore Wycheproof Bouncy Castle test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index 08238ce..2e92706 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore Wycheproof Bouncy Castle test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -26,8 +27,6 @@
              context of one of the suites, so we have to limit the test
              infrastructure to only running the test suites. -->
         <option name="test-package" value="android.libcore.cts.wycheproof" />
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener" />
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/wycheproof/AndroidManifest.xml b/tests/libcore/wycheproof/AndroidManifest.xml
index 765c677..5e8058f 100644
--- a/tests/libcore/wycheproof/AndroidManifest.xml
+++ b/tests/libcore/wycheproof/AndroidManifest.xml
@@ -23,6 +23,9 @@
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.libcore.cts.wycheproof.conscrypt"
-                     android:label="CTS Libcore Wycheproof Conscrypt test cases" />
+                     android:label="CTS Libcore Wycheproof Conscrypt test cases">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
 
 </manifest>
diff --git a/tests/libcore/wycheproof/AndroidTest.xml b/tests/libcore/wycheproof/AndroidTest.xml
index b5874fd..2a1f2b5 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Libcore Wycheproof Conscrypt test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -26,8 +27,6 @@
              context of one of the suites, so we have to limit the test
              infrastructure to only running the test suites. -->
         <option name="test-package" value="android.libcore.cts.wycheproof" />
-        <option name="instrumentation-arg" key="listener"
-                value="com.android.cts.runner.CtsTestRunListener" />
         <option name="instrumentation-arg" key="filter"
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/mocking/Android.mk b/tests/mocking/Android.mk
index c3fabef..2afa4d2 100644
--- a/tests/mocking/Android.mk
+++ b/tests/mocking/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_MODULE_TAGS := \
     tests
 LOCAL_JAVA_LIBRARIES := \
-    android.test.runner
+    android.test.runner.stubs
 LOCAL_STATIC_JAVA_LIBRARIES = \
     mockito-target \
     android-support-test \
diff --git a/tests/mocking/AndroidTest.xml b/tests/mocking/AndroidTest.xml
index 2741eba..9e53939 100644
--- a/tests/mocking/AndroidTest.xml
+++ b/tests/mocking/AndroidTest.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 <configuration description="Config for Mockito mocking test cases">
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+   <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="mocking" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/mocking/inline/Android.mk b/tests/mocking/inline/Android.mk
index 1bc133b..9fa873d 100644
--- a/tests/mocking/inline/Android.mk
+++ b/tests/mocking/inline/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_MODULE_TAGS := \
     tests
 LOCAL_JAVA_LIBRARIES := \
-    android.test.runner
+    android.test.runner.stubs
 LOCAL_STATIC_JAVA_LIBRARIES = \
     mockito-target-inline \
     android-support-test \
diff --git a/tests/mocking/inline/AndroidTest.xml b/tests/mocking/inline/AndroidTest.xml
index e642329..bfade92 100644
--- a/tests/mocking/inline/AndroidTest.xml
+++ b/tests/mocking/inline/AndroidTest.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 <configuration description="Config for Mockito inline mocking test cases">
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="mocking" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/netlegacy22.api/Android.mk b/tests/netlegacy22.api/Android.mk
index 8639514..5a330e5 100644
--- a/tests/netlegacy22.api/Android.mk
+++ b/tests/netlegacy22.api/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SDK_VERSION := 22
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/netlegacy22.permission/Android.mk b/tests/netlegacy22.permission/Android.mk
index 262e600..f5cc38b 100644
--- a/tests/netlegacy22.permission/Android.mk
+++ b/tests/netlegacy22.permission/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SDK_VERSION := 22
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/netlegacy22.permission/AndroidManifest.xml b/tests/netlegacy22.permission/AndroidManifest.xml
index af22b47..2accd27 100644
--- a/tests/netlegacy22.permission/AndroidManifest.xml
+++ b/tests/netlegacy22.permission/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="android.netlegacy22.permission.cts">
 
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
+    <uses-permission android:name="android.permission.INTERNET" />
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.permission.cts.PermissionStubActivity"
diff --git a/tests/netlegacy22.permission/AndroidTest.xml b/tests/netlegacy22.permission/AndroidTest.xml
index db6f294..83983a7 100644
--- a/tests/netlegacy22.permission/AndroidTest.xml
+++ b/tests/netlegacy22.permission/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Legacy android.net Permission test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/netlegacy22.permission/src/android/net/cts/legacy/api22/permission/QtaguidPermissionTest.java b/tests/netlegacy22.permission/src/android/net/cts/legacy/api22/permission/QtaguidPermissionTest.java
new file mode 100644
index 0000000..08fd753
--- /dev/null
+++ b/tests/netlegacy22.permission/src/android/net/cts/legacy/api22/permission/QtaguidPermissionTest.java
@@ -0,0 +1,129 @@
+package android.net.cts.legacy.api22.permission;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.TrafficStats;
+import android.support.test.filters.MediumTest;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+
+public class QtaguidPermissionTest extends AndroidTestCase {
+
+    private static final String QTAGUID_STATS_FILE = "/proc/net/xt_qtaguid/stats";
+
+    @MediumTest
+    public void testDevQtaguidSane() throws Exception {
+        File f = new File("/dev/xt_qtaguid");
+        assertTrue(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    public void testAccessPrivateTrafficStats() throws IOException {
+
+        final int ownAppUid = getContext().getApplicationInfo().uid;
+        try {
+            BufferedReader qtaguidReader = new BufferedReader(new FileReader(QTAGUID_STATS_FILE));
+            String line;
+            // Skip the header line;
+            qtaguidReader.readLine();
+            while ((line = qtaguidReader.readLine()) != null) {
+                String tokens[] = line.split(" ");
+                // Go through all the entries we find the qtaguid stats and fail if we find a stats
+                // with different uid.
+                if (tokens.length > 3 && !tokens[3].equals(String.valueOf(ownAppUid))) {
+                    fail("Other apps detailed traffic stats leaked, self uid: "
+                         + String.valueOf(ownAppUid) + " find uid: " + tokens[3]);
+                }
+            }
+            qtaguidReader.close();
+        } catch (FileNotFoundException e) {
+            fail("Was not able to access qtaguid/stats: " + e);
+        }
+    }
+
+    private void accessOwnTrafficStats() throws IOException {
+
+        final int ownAppUid = getContext().getApplicationInfo().uid;
+
+        boolean foundOwnDetailedStats = false;
+        try {
+            BufferedReader qtaguidReader = new BufferedReader(new FileReader(QTAGUID_STATS_FILE));
+            String line;
+            while ((line = qtaguidReader.readLine()) != null) {
+                String tokens[] = line.split(" ");
+                if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
+                    if (!tokens[2].equals("0x0")) {
+                      foundOwnDetailedStats = true;
+                    }
+                }
+            }
+            qtaguidReader.close();
+        } catch (FileNotFoundException e) {
+            fail("Was not able to access qtaguid/stats: " + e);
+        }
+        assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats);
+    }
+
+    public void testAccessOwnQtaguidTrafficStats() throws IOException {
+
+        // Transfer 1MB of data across an explicitly localhost socket.
+        final int byteCount = 1024;
+        final int packetCount = 1024;
+
+        final ServerSocket server = new ServerSocket(0);
+        new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") {
+            @Override
+            public void run() {
+                try {
+                    Socket socket = new Socket("localhost", server.getLocalPort());
+                    // Make sure that each write()+flush() turns into a packet:
+                    // disable Nagle.
+                    socket.setTcpNoDelay(true);
+                    OutputStream out = socket.getOutputStream();
+                    byte[] buf = new byte[byteCount];
+                    for (int i = 0; i < packetCount; i++) {
+                        TrafficStats.setThreadStatsTag(i % 10);
+                        TrafficStats.tagSocket(socket);
+                        out.write(buf);
+                        out.flush();
+                    }
+                    out.close();
+                    socket.close();
+                } catch (IOException e) {
+                  assertTrue("io exception" + e, false);
+                }
+            }
+        }.start();
+
+        try {
+            Socket socket = server.accept();
+            InputStream in = socket.getInputStream();
+            byte[] buf = new byte[byteCount];
+            int read = 0;
+            while (read < byteCount * packetCount) {
+                int n = in.read(buf);
+                assertTrue("Unexpected EOF", n > 0);
+                read += n;
+            }
+        } finally {
+            server.close();
+        }
+
+        accessOwnTrafficStats();
+    }
+}
diff --git a/tests/openglperf2/AndroidTest.xml b/tests/openglperf2/AndroidTest.xml
index 5d8f9d1..4ac0455 100644
--- a/tests/openglperf2/AndroidTest.xml
+++ b/tests/openglperf2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS OpenGL test cases">
+    <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" />
diff --git a/tests/pdf/Android.mk b/tests/pdf/Android.mk
index 2b301af..4f166ea 100644
--- a/tests/pdf/Android.mk
+++ b/tests/pdf/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android-support-test \
@@ -28,8 +28,7 @@
     compatibility-device-util \
     ctstestrunner \
     android-support-annotations \
-    junit \
-    legacy-android-test
+    junit
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/pdf/AndroidTest.xml b/tests/pdf/AndroidTest.xml
index ab88726..a402e10 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -14,8 +14,11 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Pdf test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="not-shardable" value="true" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <!-- Printing is a large user of PDF, hence run the tests in the same component -->
+    <option name="config-descriptor:metadata" key="component" value="print" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/pdf/res/raw/protected_pdf.pdf b/tests/pdf/res/raw/protected_pdf.pdf
new file mode 100644
index 0000000..e4092b9
--- /dev/null
+++ b/tests/pdf/res/raw/protected_pdf.pdf
Binary files differ
diff --git a/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java b/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
index 2f0158e..2ce69d1 100644
--- a/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
+++ b/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
@@ -62,6 +62,7 @@
     private static final int A5_PORTRAIT_PRINTSCALING_NONE =
             R.raw.a5_portrait_rgbb_1_6_printscaling_none;
     private static final int TWO_PAGES = R.raw.two_pages;
+    private static final int PROTECTED_PDF = R.raw.protected_pdf;
 
     private Context mContext;
 
@@ -76,12 +77,28 @@
     }
 
     @Test
-    @Ignore("Makes all subsequent tests fail")
     public void constructRendererFromNonPDF() throws Exception {
         // Open jpg as if it was a PDF
-        ParcelFileDescriptor fd = mContext.getResources().openRawResourceFd(R.raw.testimage)
-                .getParcelFileDescriptor();
-        verifyException(() -> new PdfRenderer(fd), IOException.class);
+        verifyException(() -> createRenderer(R.raw.testimage, mContext), IOException.class);
+    }
+
+    @Test
+    public void constructRendererFromProtectedPDF() throws Exception {
+        verifyException(() -> createRenderer(PROTECTED_PDF, mContext), SecurityException.class);
+    }
+
+    @Test
+    public void rendererRecoversAfterFailure() throws Exception {
+        // Create rendered to prevent lib from being unloaded
+        PdfRenderer firstRenderer = createRenderer(A4_PORTRAIT, mContext);
+
+        verifyException(() -> createRenderer(PROTECTED_PDF, mContext), SecurityException.class);
+
+        // We can create new renderers after we failed to create one
+        PdfRenderer renderer = createRenderer(TWO_PAGES, mContext);
+        renderer.close();
+
+        firstRenderer.close();
     }
 
     @Test
diff --git a/tests/sample/Android.mk b/tests/sample/Android.mk
index f6d8760..47acf07 100755
--- a/tests/sample/Android.mk
+++ b/tests/sample/Android.mk
@@ -27,8 +27,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
-    android-support-test \
-    legacy-android-test
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -39,4 +40,4 @@
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index c8d1949..8a7d2d9 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Sample test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/security/Android.mk b/tests/security/Android.mk
new file mode 100755
index 0000000..aecc076
--- /dev/null
+++ b/tests/security/Android.mk
@@ -0,0 +1,26 @@
+# 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)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle bouncycastle-bcpkix guava
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE := cts-security-test-support-library
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/keystore/src/android/keystore/cts/Asn1Utils.java b/tests/security/src/android/keystore/cts/Asn1Utils.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/Asn1Utils.java
rename to tests/security/src/android/keystore/cts/Asn1Utils.java
diff --git a/tests/tests/keystore/src/android/keystore/cts/Attestation.java b/tests/security/src/android/keystore/cts/Attestation.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/Attestation.java
rename to tests/security/src/android/keystore/cts/Attestation.java
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestationApplicationId.java b/tests/security/src/android/keystore/cts/AttestationApplicationId.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/AttestationApplicationId.java
rename to tests/security/src/android/keystore/cts/AttestationApplicationId.java
diff --git a/tests/security/src/android/keystore/cts/AttestationPackageInfo.java b/tests/security/src/android/keystore/cts/AttestationPackageInfo.java
new file mode 100644
index 0000000..3c3e2bd
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/AttestationPackageInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 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 com.android.org.bouncycastle.asn1.ASN1Encodable;
+import com.android.org.bouncycastle.asn1.ASN1Sequence;
+
+import java.security.cert.CertificateParsingException;
+
+import java.io.UnsupportedEncodingException;
+
+public class AttestationPackageInfo implements java.lang.Comparable<AttestationPackageInfo> {
+    private static final int PACKAGE_NAME_INDEX = 0;
+    private static final int VERSION_INDEX = 1;
+
+    private final String packageName;
+    private final long version;
+
+    public AttestationPackageInfo(String packageName, long version) {
+        this.packageName = packageName;
+        this.version = version;
+    }
+
+    public AttestationPackageInfo(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+        if (!(asn1Encodable instanceof ASN1Sequence)) {
+            throw new CertificateParsingException(
+                    "Expected sequence for AttestationPackageInfo, found "
+                            + asn1Encodable.getClass().getName());
+        }
+
+        ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
+        try {
+            packageName = Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(
+                    sequence.getObjectAt(PACKAGE_NAME_INDEX));
+        } catch (UnsupportedEncodingException e) {
+            throw new CertificateParsingException(
+                    "Converting octet stream to String triggered an UnsupportedEncodingException",
+                    e);
+        }
+        version = Asn1Utils.getLongFromAsn1(sequence.getObjectAt(VERSION_INDEX));
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public long getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder().append("Package name: ").append(getPackageName())
+                .append("\nVersion: " + getVersion()).toString();
+    }
+
+    @Override
+    public int compareTo(AttestationPackageInfo other) {
+        int res = packageName.compareTo(other.packageName);
+        if (res != 0) return res;
+        res = Long.compare(version, other.version);
+        if (res != 0) return res;
+        return res;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return (o instanceof AttestationPackageInfo)
+                && (0 == compareTo((AttestationPackageInfo) o));
+    }
+}
diff --git a/tests/security/src/android/keystore/cts/AuthorizationList.java b/tests/security/src/android/keystore/cts/AuthorizationList.java
new file mode 100644
index 0000000..460bbf7
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/AuthorizationList.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.common.base.Functions.forMap;
+import static com.google.common.collect.Collections2.transform;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import com.android.org.bouncycastle.asn1.ASN1Encodable;
+import com.android.org.bouncycastle.asn1.ASN1Primitive;
+import com.android.org.bouncycastle.asn1.ASN1Sequence;
+import com.android.org.bouncycastle.asn1.ASN1SequenceParser;
+import com.android.org.bouncycastle.asn1.ASN1TaggedObject;
+import com.android.org.bouncycastle.asn1.ASN1InputStream;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+public class AuthorizationList {
+    // Algorithm values.
+    public static final int KM_ALGORITHM_RSA = 1;
+    public static final int KM_ALGORITHM_EC = 3;
+
+    // EC Curves
+    public static final int KM_EC_CURVE_P224 = 0;
+    public static final int KM_EC_CURVE_P256 = 1;
+    public static final int KM_EC_CURVE_P384 = 2;
+    public static final int KM_EC_CURVE_P521 = 3;
+
+    // Padding modes.
+    public static final int KM_PAD_NONE = 1;
+    public static final int KM_PAD_RSA_OAEP = 2;
+    public static final int KM_PAD_RSA_PSS = 3;
+    public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
+    public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
+
+    // Digest modes.
+    public static final int KM_DIGEST_NONE = 0;
+    public static final int KM_DIGEST_MD5 = 1;
+    public static final int KM_DIGEST_SHA1 = 2;
+    public static final int KM_DIGEST_SHA_2_224 = 3;
+    public static final int KM_DIGEST_SHA_2_256 = 4;
+    public static final int KM_DIGEST_SHA_2_384 = 5;
+    public static final int KM_DIGEST_SHA_2_512 = 6;
+
+    // Key origins.
+    public static final int KM_ORIGIN_GENERATED = 0;
+    public static final int KM_ORIGIN_IMPORTED = 2;
+    public static final int KM_ORIGIN_UNKNOWN = 3;
+
+    // Operation Purposes.
+    public static final int KM_PURPOSE_ENCRYPT = 0;
+    public static final int KM_PURPOSE_DECRYPT = 1;
+    public static final int KM_PURPOSE_SIGN = 2;
+    public static final int KM_PURPOSE_VERIFY = 3;
+
+    // User authenticators.
+    public static final int HW_AUTH_PASSWORD = 1 << 0;
+    public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+
+    // Keymaster tag classes
+    private static final int KM_ENUM = 1 << 28;
+    private static final int KM_ENUM_REP = 2 << 28;
+    private static final int KM_UINT = 3 << 28;
+    private static final int KM_ULONG = 5 << 28;
+    private static final int KM_DATE = 6 << 28;
+    private static final int KM_BOOL = 7 << 28;
+    private static final int KM_BYTES = 9 << 28;
+
+    // Tag class removal mask
+    private static final int KEYMASTER_TAG_TYPE_MASK = 0x0FFFFFFF;
+
+    // Keymaster tags
+    private static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
+    private static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
+    private static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
+    private static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
+    private static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
+    private static final int KM_TAG_EC_CURVE = KM_ENUM | 10;
+    private static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
+    private static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
+    private static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
+    private static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
+    private static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
+    private static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
+    private static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
+    private static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
+    private static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
+    private static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
+    private static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
+    private static final int KM_TAG_ORIGIN = KM_ENUM | 702;
+    private static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
+    private static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+    private static final int KM_TAG_OS_VERSION = KM_UINT | 705;
+    private static final int KM_TAG_OS_PATCHLEVEL = KM_UINT | 706;
+    private static final int KM_TAG_ATTESTATION_APPLICATION_ID = KM_BYTES | 709;
+    private static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
+    private static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
+    private static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
+    private static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
+    private static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
+    private static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
+    private static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
+    private static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
+
+    // Map for converting padding values to strings
+    private static final ImmutableMap<Integer, String> paddingMap = ImmutableMap
+            .<Integer, String> builder()
+            .put(KM_PAD_NONE, "NONE")
+            .put(KM_PAD_RSA_OAEP, "OAEP")
+            .put(KM_PAD_RSA_PSS, "PSS")
+            .put(KM_PAD_RSA_PKCS1_1_5_ENCRYPT, "PKCS1 ENCRYPT")
+            .put(KM_PAD_RSA_PKCS1_1_5_SIGN, "PKCS1 SIGN")
+            .build();
+
+    // Map for converting digest values to strings
+    private static final ImmutableMap<Integer, String> digestMap = ImmutableMap
+            .<Integer, String> builder()
+            .put(KM_DIGEST_NONE, "NONE")
+            .put(KM_DIGEST_MD5, "MD5")
+            .put(KM_DIGEST_SHA1, "SHA1")
+            .put(KM_DIGEST_SHA_2_224, "SHA224")
+            .put(KM_DIGEST_SHA_2_256, "SHA256")
+            .put(KM_DIGEST_SHA_2_384, "SHA384")
+            .put(KM_DIGEST_SHA_2_512, "SHA512")
+            .build();
+
+    // Map for converting purpose values to strings
+    private static final ImmutableMap<Integer, String> purposeMap = ImmutableMap
+            .<Integer, String> builder()
+            .put(KM_PURPOSE_DECRYPT, "DECRYPT")
+            .put(KM_PURPOSE_ENCRYPT, "ENCRYPT")
+            .put(KM_PURPOSE_SIGN, "SIGN")
+            .put(KM_PURPOSE_VERIFY, "VERIFY")
+            .build();
+
+    private Set<Integer> purposes;
+    private Integer algorithm;
+    private Integer keySize;
+    private Set<Integer> digests;
+    private Set<Integer> paddingModes;
+    private Integer ecCurve;
+    private Long rsaPublicExponent;
+    private Date activeDateTime;
+    private Date originationExpireDateTime;
+    private Date usageExpireDateTime;
+    private boolean noAuthRequired;
+    private Integer userAuthType;
+    private Integer authTimeout;
+    private boolean allowWhileOnBody;
+    private boolean allApplications;
+    private byte[] applicationId;
+    private Date creationDateTime;
+    private Integer origin;
+    private boolean rollbackResistant;
+    private RootOfTrust rootOfTrust;
+    private Integer osVersion;
+    private Integer osPatchLevel;
+    private AttestationApplicationId attestationApplicationId;
+    private String brand;
+    private String device;
+    private String serialNumber;
+    private String imei;
+    private String meid;
+    private String product;
+    private String manufacturer;
+    private String model;
+
+    public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
+        if (!(sequence instanceof ASN1Sequence)) {
+            throw new CertificateParsingException("Expected sequence for authorization list, found "
+                    + sequence.getClass().getName());
+        }
+
+        ASN1SequenceParser parser = ((ASN1Sequence) sequence).parser();
+        ASN1TaggedObject entry = parseAsn1TaggedObject(parser);
+        for (; entry != null; entry = parseAsn1TaggedObject(parser)) {
+            int tag = entry.getTagNo();
+            ASN1Primitive value = entry.getObject();
+            Log.i("Attestation", "Parsing tag: [" + tag + "], value: [" + value + "]");
+            switch (tag) {
+                default:
+                    throw new CertificateParsingException("Unknown tag " + tag + " found");
+
+                case KM_TAG_PURPOSE & KEYMASTER_TAG_TYPE_MASK:
+                    purposes = Asn1Utils.getIntegersFromAsn1Set(value);
+                    break;
+                case KM_TAG_ALGORITHM & KEYMASTER_TAG_TYPE_MASK:
+                    algorithm = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_KEY_SIZE & KEYMASTER_TAG_TYPE_MASK:
+                    keySize = Asn1Utils.getIntegerFromAsn1(value);
+                    Log.i("Attestation", "Found KEY SIZE, value: " + keySize);
+                    break;
+                case KM_TAG_DIGEST & KEYMASTER_TAG_TYPE_MASK:
+                    digests = Asn1Utils.getIntegersFromAsn1Set(value);
+                    break;
+                case KM_TAG_PADDING & KEYMASTER_TAG_TYPE_MASK:
+                    paddingModes = Asn1Utils.getIntegersFromAsn1Set(value);
+                    break;
+                case KM_TAG_RSA_PUBLIC_EXPONENT & KEYMASTER_TAG_TYPE_MASK:
+                    rsaPublicExponent = Asn1Utils.getLongFromAsn1(value);
+                    break;
+                case KM_TAG_NO_AUTH_REQUIRED & KEYMASTER_TAG_TYPE_MASK:
+                    noAuthRequired = true;
+                    break;
+                case KM_TAG_CREATION_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+                    creationDateTime = Asn1Utils.getDateFromAsn1(value);
+                    break;
+                case KM_TAG_ORIGIN & KEYMASTER_TAG_TYPE_MASK:
+                    origin = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_OS_VERSION & KEYMASTER_TAG_TYPE_MASK:
+                    osVersion = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_OS_PATCHLEVEL & KEYMASTER_TAG_TYPE_MASK:
+                    osPatchLevel = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_ACTIVE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+                    activeDateTime = Asn1Utils.getDateFromAsn1(value);
+                    break;
+                case KM_TAG_ORIGINATION_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+                    originationExpireDateTime = Asn1Utils.getDateFromAsn1(value);
+                    break;
+                case KM_TAG_USAGE_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
+                    usageExpireDateTime = Asn1Utils.getDateFromAsn1(value);
+                    break;
+                case KM_TAG_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
+                    applicationId = Asn1Utils.getByteArrayFromAsn1(value);
+                    break;
+                case KM_TAG_ROLLBACK_RESISTANT & KEYMASTER_TAG_TYPE_MASK:
+                    rollbackResistant = true;
+                    break;
+                case KM_TAG_AUTH_TIMEOUT & KEYMASTER_TAG_TYPE_MASK:
+                    authTimeout = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_ALLOW_WHILE_ON_BODY & KEYMASTER_TAG_TYPE_MASK:
+                    allowWhileOnBody = true;
+                    break;
+                case KM_TAG_EC_CURVE & KEYMASTER_TAG_TYPE_MASK:
+                    ecCurve = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_USER_AUTH_TYPE & KEYMASTER_TAG_TYPE_MASK:
+                    userAuthType = Asn1Utils.getIntegerFromAsn1(value);
+                    break;
+                case KM_TAG_ROOT_OF_TRUST & KEYMASTER_TAG_TYPE_MASK:
+                    rootOfTrust = new RootOfTrust(value);
+                    break;
+                case KM_TAG_ATTESTATION_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
+                    attestationApplicationId = new AttestationApplicationId(Asn1Utils
+                            .getAsn1EncodableFromBytes(Asn1Utils.getByteArrayFromAsn1(value)));
+                    break;
+                case KM_TAG_ATTESTATION_ID_BRAND & KEYMASTER_TAG_TYPE_MASK:
+                    brand = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_DEVICE & KEYMASTER_TAG_TYPE_MASK:
+                    device = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_PRODUCT & KEYMASTER_TAG_TYPE_MASK:
+                    product = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_SERIAL & KEYMASTER_TAG_TYPE_MASK:
+                    serialNumber = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_IMEI & KEYMASTER_TAG_TYPE_MASK:
+                    imei = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_MEID & KEYMASTER_TAG_TYPE_MASK:
+                    meid = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_MANUFACTURER & KEYMASTER_TAG_TYPE_MASK:
+                    manufacturer = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ATTESTATION_ID_MODEL & KEYMASTER_TAG_TYPE_MASK:
+                    model = getStringFromAsn1Value(value);
+                    break;
+                case KM_TAG_ALL_APPLICATIONS & KEYMASTER_TAG_TYPE_MASK:
+                    allApplications = true;
+                    break;
+            }
+        }
+
+    }
+
+    public static String algorithmToString(int algorithm) {
+        switch (algorithm) {
+            case KM_ALGORITHM_RSA:
+                return "RSA";
+            case KM_ALGORITHM_EC:
+                return "ECDSA";
+            default:
+                return "Unknown";
+        }
+    }
+
+    public static String paddingModesToString(final Set<Integer> paddingModes) {
+        return joinStrings(transform(paddingModes, forMap(paddingMap, "Unknown")));
+    }
+
+    public static String paddingModeToString(int paddingMode) {
+        return forMap(paddingMap, "Unknown").apply(paddingMode);
+    }
+
+    public static String digestsToString(Set<Integer> digests) {
+        return joinStrings(transform(digests, forMap(digestMap, "Unknown")));
+    }
+
+    public static String digestToString(int digest) {
+        return forMap(digestMap, "Unknown").apply(digest);
+    }
+
+    public static String purposesToString(Set<Integer> purposes) {
+        return joinStrings(transform(purposes, forMap(purposeMap, "Unknown")));
+    }
+
+    public static String userAuthTypeToString(int userAuthType) {
+        List<String> types = Lists.newArrayList();
+        if ((userAuthType & HW_AUTH_FINGERPRINT) != 0)
+            types.add("Fingerprint");
+        if ((userAuthType & HW_AUTH_PASSWORD) != 0)
+            types.add("Password");
+        return joinStrings(types);
+    }
+
+    public static String originToString(int origin) {
+        switch (origin) {
+            case KM_ORIGIN_GENERATED:
+                return "Generated";
+            case KM_ORIGIN_IMPORTED:
+                return "Imported";
+            case KM_ORIGIN_UNKNOWN:
+                return "Unknown (KM0)";
+            default:
+                return "Unknown";
+        }
+    }
+
+    private static String joinStrings(Collection<String> collection) {
+        return new StringBuilder()
+                .append("[")
+                .append(Joiner.on(", ").join(collection))
+                .append("]")
+                .toString();
+    }
+
+    private static String formatDate(Date date) {
+        return DateFormat.getDateTimeInstance().format(date);
+    }
+
+    private static ASN1TaggedObject parseAsn1TaggedObject(ASN1SequenceParser parser)
+            throws CertificateParsingException {
+        ASN1Encodable asn1Encodable = parseAsn1Encodable(parser);
+        if (asn1Encodable == null || asn1Encodable instanceof ASN1TaggedObject) {
+            return (ASN1TaggedObject) asn1Encodable;
+        }
+        throw new CertificateParsingException(
+                "Expected tagged object, found " + asn1Encodable.getClass().getName());
+    }
+
+    private static ASN1Encodable parseAsn1Encodable(ASN1SequenceParser parser)
+            throws CertificateParsingException {
+        try {
+            return parser.readObject();
+        } catch (IOException e) {
+            throw new CertificateParsingException("Failed to parse ASN1 sequence", e);
+        }
+    }
+
+    public Set<Integer> getPurposes() {
+        return purposes;
+    }
+
+    public Integer getAlgorithm() {
+        return algorithm;
+    }
+
+    public Integer getKeySize() {
+        return keySize;
+    }
+
+    public Set<Integer> getDigests() {
+        return digests;
+    }
+
+    public Set<Integer> getPaddingModes() {
+        return paddingModes;
+    }
+
+    public Set<String> getPaddingModesAsStrings() throws CertificateParsingException {
+        if (paddingModes == null) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+        for (int paddingMode : paddingModes) {
+            switch (paddingMode) {
+                case KM_PAD_NONE:
+                    builder.add(KeyProperties.ENCRYPTION_PADDING_NONE);
+                    break;
+                case KM_PAD_RSA_OAEP:
+                    builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+                    break;
+                case KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+                    builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+                    break;
+                case KM_PAD_RSA_PKCS1_1_5_SIGN:
+                    builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+                    break;
+                case KM_PAD_RSA_PSS:
+                    builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+                    break;
+                default:
+                    throw new CertificateParsingException("Invalid padding mode " + paddingMode);
+            }
+        }
+        return builder.build();
+    }
+
+    public Integer getEcCurve() {
+        return ecCurve;
+    }
+
+    public String ecCurveAsString() {
+        if (ecCurve == null)
+            return "NULL";
+
+        switch (ecCurve) {
+            case KM_EC_CURVE_P224:
+                return "secp224r1";
+            case KM_EC_CURVE_P256:
+                return "secp256r1";
+            case KM_EC_CURVE_P384:
+                return "secp384r1";
+            case KM_EC_CURVE_P521:
+                return "secp521r1";
+            default:
+                return "unknown";
+        }
+    }
+
+    public Long getRsaPublicExponent() {
+        return rsaPublicExponent;
+    }
+
+    public Date getActiveDateTime() {
+        return activeDateTime;
+    }
+
+    public Date getOriginationExpireDateTime() {
+        return originationExpireDateTime;
+    }
+
+    public Date getUsageExpireDateTime() {
+        return usageExpireDateTime;
+    }
+
+    public boolean isNoAuthRequired() {
+        return noAuthRequired;
+    }
+
+    public Integer getUserAuthType() {
+        return userAuthType;
+    }
+
+    public Integer getAuthTimeout() {
+        return authTimeout;
+    }
+
+    public boolean isAllowWhileOnBody() {
+        return allowWhileOnBody;
+    }
+
+    public boolean isAllApplications() {
+        return allApplications;
+    }
+
+    public byte[] getApplicationId() {
+        return applicationId;
+    }
+
+    public Date getCreationDateTime() {
+        return creationDateTime;
+    }
+
+    public Integer getOrigin() {
+        return origin;
+    }
+
+    public boolean isRollbackResistant() {
+        return rollbackResistant;
+    }
+
+    public RootOfTrust getRootOfTrust() {
+        return rootOfTrust;
+    }
+
+    public Integer getOsVersion() {
+        return osVersion;
+    }
+
+    public Integer getOsPatchLevel() {
+        return osPatchLevel;
+    }
+
+    public AttestationApplicationId getAttestationApplicationId() {
+        return attestationApplicationId;
+    }
+
+    public String getBrand() {
+        return brand;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public String getSerialNumber() {
+        return serialNumber;
+    };
+
+    public String getImei() {
+        return imei;
+    };
+
+    public String getMeid() {
+        return meid;
+    };
+
+    public String getProduct() {
+        return product;
+    };
+
+    public String getManufacturer() {
+        return manufacturer;
+    };
+
+    public String getModel() {
+        return model;
+    };
+
+    private String getStringFromAsn1Value(ASN1Primitive value) throws CertificateParsingException {
+        try {
+            return Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(value);
+        } catch (UnsupportedEncodingException e) {
+            throw new CertificateParsingException("Error parsing ASN.1 value", e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+
+        if (algorithm != null) {
+            s.append("\nAlgorithm: ").append(algorithmToString(algorithm));
+        }
+
+        if (keySize != null) {
+            s.append("\nKeySize: ").append(keySize);
+        }
+
+        if (purposes != null && !purposes.isEmpty()) {
+            s.append("\nPurposes: ").append(purposesToString(purposes));
+        }
+
+        if (digests != null && !digests.isEmpty()) {
+            s.append("\nDigests: ").append(digestsToString(digests));
+        }
+
+        if (paddingModes != null && !paddingModes.isEmpty()) {
+            s.append("\nPadding modes: ").append(paddingModesToString(paddingModes));
+        }
+
+        if (ecCurve != null) {
+            s.append("\nEC Curve: ").append(ecCurveAsString());
+        }
+
+        String label = "\nRSA exponent: ";
+        if (rsaPublicExponent != null) {
+            s.append(label).append(rsaPublicExponent);
+        }
+
+        if (activeDateTime != null) {
+            s.append("\nActive: ").append(formatDate(activeDateTime));
+        }
+
+        if (originationExpireDateTime != null) {
+            s.append("\nOrigination expire: ").append(formatDate(originationExpireDateTime));
+        }
+
+        if (usageExpireDateTime != null) {
+            s.append("\nUsage expire: ").append(formatDate(usageExpireDateTime));
+        }
+
+        if (!noAuthRequired && userAuthType != null) {
+            s.append("\nAuth types: ").append(userAuthTypeToString(userAuthType));
+            if (authTimeout != null) {
+                s.append("\nAuth timeout: ").append(authTimeout);
+            }
+        }
+
+        if (applicationId != null) {
+            s.append("\nApplication ID: ").append(new String(applicationId));
+        }
+
+        if (creationDateTime != null) {
+            s.append("\nCreated: ").append(formatDate(creationDateTime));
+        }
+
+        if (origin != null) {
+            s.append("\nOrigin: ").append(originToString(origin));
+        }
+
+        if (rollbackResistant) {
+            s.append("\nRollback resistant: true");
+        }
+
+        if (rootOfTrust != null) {
+            s.append("\nRoot of Trust:\n");
+            s.append(rootOfTrust);
+        }
+
+        if (osVersion != null) {
+            s.append("\nOS Version: ").append(osVersion);
+        }
+
+        if (osPatchLevel != null) {
+            s.append("\nOS Patchlevel: ").append(osPatchLevel);
+        }
+
+        if (attestationApplicationId != null) {
+            s.append("\nAttestation Application Id:").append(attestationApplicationId);
+        }
+
+        if (brand != null) {
+            s.append("\nBrand: ").append(brand);
+        }
+        if (device != null) {
+            s.append("\nDevice type: ").append(device);
+        }
+        return s.toString();
+    }
+}
diff --git a/tests/security/src/android/keystore/cts/CertificateUtils.java b/tests/security/src/android/keystore/cts/CertificateUtils.java
new file mode 100644
index 0000000..68e936e
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/CertificateUtils.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.keystore.cts;
+
+import com.android.org.bouncycastle.asn1.x500.X500Name;
+import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.org.bouncycastle.cert.X509CertificateHolder;
+import com.android.org.bouncycastle.cert.X509v3CertificateBuilder;
+import com.android.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+
+public class CertificateUtils {
+    /** Creates a self-signed X.509 certificate, given a key pair, subject and issuer. */
+    public static X509Certificate createCertificate(
+            KeyPair keyPair, X500Principal subject, X500Principal issuer) throws Exception {
+        // Make the certificate valid for two days.
+        long millisPerDay = 24 * 60 * 60 * 1000;
+        long now = System.currentTimeMillis();
+        Date start = new Date(now - millisPerDay);
+        Date end = new Date(now + millisPerDay);
+
+        // Assign a random serial number.
+        byte[] serialBytes = new byte[16];
+        new SecureRandom().nextBytes(serialBytes);
+        BigInteger serialNumber = new BigInteger(1, serialBytes);
+
+        // Create the certificate builder
+        X509v3CertificateBuilder x509cg =
+                new X509v3CertificateBuilder(
+                        X500Name.getInstance(issuer.getEncoded()),
+                        serialNumber,
+                        start,
+                        end,
+                        X500Name.getInstance(subject.getEncoded()),
+                        SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
+
+        // Choose a signature algorithm matching the key format.
+        String keyAlgorithm = keyPair.getPrivate().getAlgorithm();
+        String signatureAlgorithm;
+        if (keyAlgorithm.equals("RSA")) {
+            signatureAlgorithm = "SHA256withRSA";
+        } else if (keyAlgorithm.equals("EC")) {
+            signatureAlgorithm = "SHA256withECDSA";
+        } else {
+            throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+        }
+
+        // Sign the certificate and generate it.
+        X509CertificateHolder x509holder =
+                x509cg.build(
+                        new JcaContentSignerBuilder(signatureAlgorithm)
+                                .build(keyPair.getPrivate()));
+        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        X509Certificate x509c =
+                (X509Certificate)
+                        certFactory.generateCertificate(
+                                new ByteArrayInputStream(x509holder.getEncoded()));
+        return x509c;
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RootOfTrust.java b/tests/security/src/android/keystore/cts/RootOfTrust.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/RootOfTrust.java
rename to tests/security/src/android/keystore/cts/RootOfTrust.java
diff --git a/tests/sensor/Android.mk b/tests/sensor/Android.mk
index dcf9015..e636af8 100644
--- a/tests/sensor/Android.mk
+++ b/tests/sensor/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
 
-LOCAL_JAVA_LIBRARIES := platform-test-annotations
+LOCAL_JAVA_LIBRARIES := platform-test-annotations android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
@@ -89,7 +89,7 @@
 
 LOCAL_SDK_VERSION := current
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_NDK_STL_VARIANT := c++_shared
 
diff --git a/tests/sensor/AndroidTest.xml b/tests/sensor/AndroidTest.xml
index 3eff64c..7137323 100644
--- a/tests/sensor/AndroidTest.xml
+++ b/tests/sensor/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Sensor test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="location" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/sensor/src/android/hardware/cts/SensorTest.java b/tests/sensor/src/android/hardware/cts/SensorTest.java
index 11ead36..1751a8b 100644
--- a/tests/sensor/src/android/hardware/cts/SensorTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorTest.java
@@ -16,6 +16,9 @@
 
 package android.hardware.cts;
 
+import android.hardware.cts.helpers.sensorverification.ContinuousEventSanitizedVerification;
+import android.support.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
 import junit.framework.Assert;
 
 import android.content.Context;
@@ -45,9 +48,9 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -307,14 +310,60 @@
         assertFalse(result);
     }
 
-    // TODO: remove when parametized tests are supported and EventTimestampSynchronization
+    /**
+     * Verifies that if the UID is idle the continuous events are being reported
+     * but sanitized - all events are the same as the first one delivered except
+     * for their timestamps. From the point of view of an idle app these events are
+     * being properly generated but the sensor reading does not change - privacy.
+     */
+    // TODO: remove when parametrized tests are supported and EventTimestampSynchronization
+    public void testSanitizedContinuousEventsUidIdle() throws Exception {
+        ArrayList<Throwable> errorsFound = new ArrayList<>();
+        for (Sensor sensor : mAndroidSensorList) {
+            // If the UID is active no sanitization should be performed
+            verifyLongActivation(sensor, 0 /* maxReportLatencyUs */,
+                    5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                    false /* sanitized */, errorsFound);
+            verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10),
+                    5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                    false /* sanitized */, errorsFound);
+
+            // If the UID is idle sanitization should be performed
+            makeMyPackageIdle();
+            try {
+                verifyLongActivation(sensor, 0 /* maxReportLatencyUs */,
+                        5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                        true /* sanitized */, errorsFound);
+                verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10),
+                        5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                        true /* sanitized */, errorsFound);
+            } finally {
+                makeMyPackageActive();
+            }
+
+            // If the UID is active no sanitization should be performed
+            verifyLongActivation(sensor, 0 /* maxReportLatencyUs */,
+                    5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                    false /* sanitized */, errorsFound);
+            verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10),
+                    5 /* duration */, TimeUnit.SECONDS, "continuous event",
+                    false /* sanitized */, errorsFound);
+        }
+        assertOnErrors(errorsFound);
+    }
+
+    // TODO: remove when parametrized tests are supported and EventTimestampSynchronization
     //       verification is added to default verifications
     public void testSensorTimeStamps() throws Exception {
         ArrayList<Throwable> errorsFound = new ArrayList<>();
         for (Sensor sensor : mAndroidSensorList) {
             // test both continuous and batching mode sensors
-            verifyLongActivation(sensor, 0 /* maxReportLatencyUs */, errorsFound);
-            verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10), errorsFound);
+            verifyLongActivation(sensor, 0 /* maxReportLatencyUs */,
+                    20 /* duration */, TimeUnit.SECONDS, "timestamp", false
+                    /* sanitized */, errorsFound);
+            verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10),
+                    20 /* duration */, TimeUnit.SECONDS, "timestamp",
+                    false /* sanitized */, errorsFound);
         }
         assertOnErrors(errorsFound);
     }
@@ -324,7 +373,24 @@
         SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
         ArrayList<Throwable> errorsFound = new ArrayList<>();
         for (Sensor sensor : mAndroidSensorList) {
-            verifyRegisterListenerCallFlush(sensor, null /* handler */, errorsFound);
+            verifyRegisterListenerCallFlush(sensor, null /* handler */, errorsFound,
+                    false /* flushWhileIdle */);
+        }
+        assertOnErrors(errorsFound);
+    }
+
+    /**
+     * Verifies that if the UID is idle flush events are reported. Since
+     * these events have no payload with private data they are working as
+     * for a non-idle UID.
+     */
+    // TODO: remove when parametized tests are supported and EventTimestampSynchronization
+    public void testBatchAndFlushUidIdle() throws Exception {
+        SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
+        ArrayList<Throwable> errorsFound = new ArrayList<>();
+        for (Sensor sensor : mAndroidSensorList) {
+            verifyRegisterListenerCallFlush(sensor, null /* handler */, errorsFound,
+                    true /* flushWhileIdle */);
         }
         assertOnErrors(errorsFound);
     }
@@ -426,7 +492,8 @@
                     shouldEmulateSensorUnderLoad(),
                     SensorManager.SENSOR_DELAY_FASTEST,
                     maxReportLatencyUs);
-            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */);
+            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */,
+                    false /* flushWhileIdle */);
             parallelSensorOperation.add(new TestSensorOperation(environment, executor));
             builder.append(sensor.getName()).append(", ");
         }
@@ -479,11 +546,15 @@
 
     /**
      * Verifies that a continuous sensor produces events that have timestamps synchronized with
-     * {@link SystemClock#elapsedRealtimeNanos()}.
+     * {@link SystemClock#elapsedRealtimeNanos()} and that the events are sanitized/non-sanitized.
      */
     private void verifyLongActivation(
             Sensor sensor,
             int maxReportLatencyUs,
+            long duration,
+            TimeUnit durationTimeUnit,
+            String testType,
+            boolean sanitized,
             ArrayList<Throwable> errorsFound) throws InterruptedException {
         if (sensor.getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
             return;
@@ -496,14 +567,20 @@
                     shouldEmulateSensorUnderLoad(),
                     SensorManager.SENSOR_DELAY_FASTEST,
                     maxReportLatencyUs);
-            TestSensorOperation operation =
-                    TestSensorOperation.createOperation(environment, 20, TimeUnit.SECONDS);
-            operation.addVerification(EventGapVerification.getDefault(environment));
-            operation.addVerification(EventOrderingVerification.getDefault(environment));
-            operation.addVerification(
-                    EventTimestampSynchronizationVerification.getDefault(environment));
-
-            Log.i(TAG, "Running timestamp test on: " + sensor.getName());
+            TestSensorOperation operation = TestSensorOperation.createOperation(
+                    environment, duration, durationTimeUnit);
+            if (sanitized) {
+                final long verificationDelayNano = TimeUnit.NANOSECONDS.convert(
+                        maxReportLatencyUs, TimeUnit.MICROSECONDS) * 2;
+                operation.addVerification(ContinuousEventSanitizedVerification
+                        .getDefault(environment, verificationDelayNano));
+            } else {
+                operation.addVerification(EventGapVerification.getDefault(environment));
+                operation.addVerification(EventOrderingVerification.getDefault(environment));
+                operation.addVerification(EventTimestampSynchronizationVerification
+                        .getDefault(environment));
+            }
+            Log.i(TAG, "Running " + testType + " test on: " + sensor.getName());
             operation.execute(getCurrentTestNode());
         } catch (InterruptedException e) {
             // propagate so the test can stop
@@ -522,7 +599,8 @@
     private void verifyRegisterListenerCallFlush(
             Sensor sensor,
             Handler handler,
-            ArrayList<Throwable> errorsFound)
+            ArrayList<Throwable> errorsFound,
+            boolean flushWhileIdle)
             throws InterruptedException {
         if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
             return;
@@ -535,7 +613,8 @@
                     shouldEmulateSensorUnderLoad(),
                     SensorManager.SENSOR_DELAY_FASTEST,
                     (int) TimeUnit.SECONDS.toMicros(10));
-            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */);
+            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */,
+                    flushWhileIdle);
             TestSensorOperation operation = new TestSensorOperation(environment, executor, handler);
 
             Log.i(TAG, "Running flush test on: " + sensor.getName());
@@ -559,6 +638,18 @@
         }
     }
 
+    private static void makeMyPackageActive() throws IOException {
+        final String command = "cmd sensorservice reset-uid-state "
+                +  InstrumentationRegistry.getTargetContext().getPackageName();
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private void makeMyPackageIdle() throws IOException {
+        final String command = "cmd sensorservice set-uid-state "
+                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle";
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
     /**
      * A delegate that drives the execution of Batch/Flush tests.
      * It performs several operations in order:
@@ -572,10 +663,13 @@
     private class FlushExecutor implements TestSensorOperation.Executor {
         private final TestSensorEnvironment mEnvironment;
         private final int mEventCount;
+        private final boolean mFlushWhileIdle;
 
-        public FlushExecutor(TestSensorEnvironment environment, int eventCount) {
+        public FlushExecutor(TestSensorEnvironment environment, int eventCount,
+                boolean flushWhileIdle) {
             mEnvironment = environment;
             mEventCount = eventCount;
+            mFlushWhileIdle = flushWhileIdle;
         }
 
         /**
@@ -588,17 +682,23 @@
          */
         @Override
         public void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
-                throws InterruptedException {
+                throws Exception {
             int sensorReportingMode = mEnvironment.getSensor().getReportingMode();
             try {
                 CountDownLatch eventLatch = sensorManager.registerListener(listener, mEventCount);
                 if (sensorReportingMode == Sensor.REPORTING_MODE_CONTINUOUS) {
                     listener.waitForEvents(eventLatch, mEventCount, true);
                 }
+                if (mFlushWhileIdle) {
+                    makeMyPackageIdle();
+                }
                 CountDownLatch flushLatch = sensorManager.requestFlush();
                 listener.waitForFlushComplete(flushLatch, true);
             } finally {
                 sensorManager.unregisterListener();
+                if (mFlushWhileIdle) {
+                    makeMyPackageActive();
+                }
             }
         }
     }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java b/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
index 3892366..1ccf524 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
@@ -56,6 +56,7 @@
             "event_time_wrong_clocksource_positions";
     public static final String EVENT_COUNT_KEY = "event_count";
     public static final String EVENT_COUNT_EXPECTED_KEY = "event_count_expected";
+    public static final String EVENT_NOT_SANITIZED_KEY = "event_not_sanitized";
     public static final String EVENT_LOG_FILENAME = "event_log_filename";
     public static final String WRONG_SENSOR_KEY = "wrong_sensor_observed";
     public static final String FREQUENCY_KEY = "frequency";
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 436a7cf..7c9be9f 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -76,7 +76,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         // Start alarm
         IntentFilter intentFilter = new IntentFilter(ACTION);
         BroadcastReceiver receiver = new BroadcastReceiver() {
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
index 8c52222..741dd63 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
@@ -48,7 +48,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         SensorCtsHelper.sleep(mDelay, mTimeUnit);
         mOperation.execute(asTestNode(parent));
     }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
index 5b333b8..8acfa7e 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
@@ -49,7 +49,7 @@
      * one iterations, it is thrown and all subsequent iterations will not run.
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         ISensorTestNode currentNode = asTestNode(parent);
         for(int i = 0; i < mIterations; ++i) {
             SensorOperation operation = mOperation.clone();
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
index 66604d3..fc12382 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
@@ -62,7 +62,7 @@
      * - cleaning up on {@link InterruptedException}
      * - propagating the exception down the stack
      */
-    public abstract void execute(ISensorTestNode parent) throws InterruptedException;
+    public abstract void execute(ISensorTestNode parent) throws Exception;
 
     /**
      * @return The cloned {@link SensorOperation}.
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
index abfa692..568b015 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
@@ -44,7 +44,7 @@
      * Test that the {@link FakeSensorOperation} functions correctly. Other tests in this class
      * rely on this operation.
      */
-    public void testFakeSensorOperation() throws InterruptedException {
+    public void testFakeSensorOperation() throws Exception {
         final int opDurationMs = 100;
 
         SensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
@@ -69,7 +69,7 @@
     /**
      * Test that the {@link DelaySensorOperation} functions correctly.
      */
-    public void testDelaySensorOperation() throws InterruptedException {
+    public void testDelaySensorOperation() throws Exception {
         final int opDurationMs = 500;
         final int subOpDurationMs = 100;
 
@@ -86,7 +86,7 @@
     /**
      * Test that the {@link ParallelSensorOperation} functions correctly.
      */
-    public void testParallelSensorOperation() throws InterruptedException {
+    public void testParallelSensorOperation() throws Exception {
         final int subOpCount = 100;
         final int subOpDurationMs = 500;
 
@@ -127,7 +127,7 @@
      * Test that the {@link ParallelSensorOperation} functions correctly if there is a failure in
      * a child operation.
      */
-    public void testParallelSensorOperation_fail() throws InterruptedException {
+    public void testParallelSensorOperation_fail() throws Exception {
         final int subOpCount = 100;
 
         ParallelSensorOperation op = new ParallelSensorOperation();
@@ -167,7 +167,7 @@
      * Test that the {@link ParallelSensorOperation} functions correctly if a child exceeds the
      * timeout.
      */
-    public void testParallelSensorOperation_timeout() throws InterruptedException {
+    public void testParallelSensorOperation_timeout() throws Exception {
         final int subOpCount = 100;
 
         ParallelSensorOperation op = new ParallelSensorOperation(1, TimeUnit.SECONDS);
@@ -201,7 +201,7 @@
     /**
      * Test that the {@link RepeatingSensorOperation} functions correctly.
      */
-    public void testRepeatingSensorOperation() throws InterruptedException {
+    public void testRepeatingSensorOperation() throws Exception {
         final int iterations = 10;
         final int subOpDurationMs = 100;
 
@@ -228,7 +228,7 @@
      * Test that the {@link RepeatingSensorOperation} functions correctly if there is a failure in
      * a child operation.
      */
-    public void testRepeatingSensorOperation_fail() throws InterruptedException {
+    public void testRepeatingSensorOperation_fail() throws Exception {
         final int iterations = 100;
         final int failCount = 75;
 
@@ -286,7 +286,7 @@
     /**
      * Test that the {@link SequentialSensorOperation} functions correctly.
      */
-    public void testSequentialSensorOperation() throws InterruptedException {
+    public void testSequentialSensorOperation() throws Exception {
         final int subOpCount = 10;
         final int subOpDurationMs = 100;
 
@@ -317,7 +317,7 @@
      * Test that the {@link SequentialSensorOperation} functions correctly if there is a failure in
      * a child operation.
      */
-    public void testSequentialSensorOperation_fail() throws InterruptedException {
+    public void testSequentialSensorOperation_fail() throws Exception {
         final int subOpCount = 100;
         final int failCount = 75;
 
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
index 847c0f2..73ee68c 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
@@ -48,7 +48,7 @@
      * in one operation, it is thrown and all subsequent operations will not run.
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         ISensorTestNode currentNode = asTestNode(parent);
         for (int i = 0; i < mOperations.size(); i++) {
             SensorOperation operation = mOperations.get(i);
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
index ad2084d..878bb42 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
@@ -76,7 +76,7 @@
      */
     public interface Executor {
         void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
-                throws InterruptedException;
+                throws Exception;
     }
 
     /**
@@ -124,7 +124,7 @@
      * Collect the specified number of events from the sensor and run all enabled verifications.
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         getStats().addValue("sensor_name", mEnvironment.getSensor().getName());
         TestSensorEventListener listener = new TestSensorEventListener(mEnvironment, mHandler);
 
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
index 9f03f31..0ae5289 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
@@ -60,7 +60,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute(ISensorTestNode parent) throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws Exception {
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         WakeLock wakeLock = pm.newWakeLock(mWakeLockFlags, TAG);
         wakeLock.acquire();
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/ContinuousEventSanitizedVerification.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/ContinuousEventSanitizedVerification.java
new file mode 100644
index 0000000..4469c97
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/ContinuousEventSanitizedVerification.java
@@ -0,0 +1,116 @@
+/*
+ * 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.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.os.SystemClock;
+import android.util.Log;
+import junit.framework.Assert;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A {@link ISensorVerification} which verifies that continuous events are sanitized
+ * when the UID is idle. In this case no events should be fired. We expect to receive
+ * events and stop receiving since some events may have been cached already.
+ */
+public class ContinuousEventSanitizedVerification extends AbstractSensorVerification {
+    public static final String PASSED_KEY = "continuous_event_sanitization_passed";
+
+    // Number of indices to print in assert message before truncating
+    private static final int TRUNCATE_MESSAGE_LENGTH = 3;
+
+    private final List<TestSensorEvent> mNonSanitizedEvents = new LinkedList<>();
+
+    private final long mVerificationStartTimeNano;
+
+    /**
+     * Get the default {@link ContinuousEventSanitizedVerification} for a sensor.
+     *
+     * @param environment the test environment
+     * @param verificationDelayNano After what delay to expect no events (nanoseconds).
+     * @return the verification or null if the verification does not apply to the sensor.
+     */
+    public static ContinuousEventSanitizedVerification getDefault(
+            TestSensorEnvironment environment, long verificationDelayNano) {
+        final int reportingMode = environment.getSensor().getReportingMode();
+        if (reportingMode == Sensor.REPORTING_MODE_CONTINUOUS) {
+            return new ContinuousEventSanitizedVerification(
+                    SystemClock.elapsedRealtimeNanos() + verificationDelayNano);
+        }
+        return null;
+    }
+
+    private ContinuousEventSanitizedVerification(long verificationDelayNano) {
+        mVerificationStartTimeNano = verificationDelayNano;
+    }
+
+    /**
+     * Verify that the events are sanitized. Add {@value #PASSED_KEY} and
+     * {@value SensorStats#EVENT_NOT_SANITIZED_KEY} keys to {@link SensorStats}.
+     *
+     * @throws AssertionError if the verification failed.
+     */
+    @Override
+    public void verify(TestSensorEnvironment environment, SensorStats stats) {
+        final int count = mNonSanitizedEvents.size();
+        if (count > 0) {
+            stats.addValue(PASSED_KEY, false);
+            stats.addValue(SensorStats.EVENT_NOT_SANITIZED_KEY, mNonSanitizedEvents);
+
+            final StringBuilder sb = new StringBuilder();
+            sb.append(mNonSanitizedEvents).append(" non-sanitized events: ");
+
+            for (int i = 0; i < Math.min(count, TRUNCATE_MESSAGE_LENGTH); i++) {
+                final TestSensorEvent event = mNonSanitizedEvents.get(i);
+                sb.append(String.format("sensor:%s", event.sensor.getName()));
+                sb.append(String.format("accuracy:%d", event.accuracy));
+                sb.append(String.format("values:%s", Arrays.toString(event.values)));
+
+                if (count > TRUNCATE_MESSAGE_LENGTH) {
+                    sb.append(count - TRUNCATE_MESSAGE_LENGTH).append(" more");
+                } else {
+                    // Delete the trailing "; "
+                    sb.delete(sb.length() - 2, sb.length());
+                    break;
+                }
+            }
+
+            Assert.fail(sb.toString());
+        } else {
+            stats.addValue(PASSED_KEY, true);
+        }
+    }
+
+    @Override
+    public ContinuousEventSanitizedVerification clone() {
+        return new ContinuousEventSanitizedVerification(mVerificationStartTimeNano);
+    }
+
+    @Override
+    protected void addSensorEventInternal(TestSensorEvent event) {
+        Log.i("OPALA", "event time: " + event.timestamp + " verif start:" + mVerificationStartTimeNano);
+        if (event.receivedTimestamp > mVerificationStartTimeNano) {
+            mNonSanitizedEvents.add(event);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/signature/api-check/Android.mk b/tests/signature/api-check/Android.mk
index 15fa178..4462393 100644
--- a/tests/signature/api-check/Android.mk
+++ b/tests/signature/api-check/Android.mk
@@ -22,7 +22,7 @@
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src/java)
 
 LOCAL_MODULE := cts-api-signature-test
 
@@ -32,8 +32,16 @@
     cts-signature-common \
     repackaged.android.test.base \
     repackaged.android.test.runner \
-    repackaged.android.test.mock \
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+include $(CLEAR_VARS)
+LOCAL_MODULE := libclassdescriptors
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := src/jni/classdescriptors.cpp
+LOCAL_HEADER_LIBRARIES := jni_headers libopenjdkjvmti_headers
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+include $(BUILD_SHARED_LIBRARY)
+
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
index c65bfdf..877e911 100644
--- a/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
@@ -16,13 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.signature.cts.api.android_test_base_26">
+          package="android.signature.cts.api.android_test_base_27">
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
     <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="27"/>
 
-    <application/>
+    <application android:debuggable="true"/>
 
     <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
                      android:targetPackage="android.signature.cts.api.android_test_base_27"
diff --git a/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml
index c9cbd9a..bf07983 100644
--- a/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Android Test Base 27 API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
@@ -29,6 +30,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_base_27" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
         <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api" />
         <option name="runtime-hint" value="5s" />
     </test>
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
index 5c88521..f0fc8e1 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
-    <application>
+    <application android:debuggable="true">
         <uses-library android:name="android.test.mock"/>
     </application>
 
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
index 16b29a8..c5ecdbd 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Android Test Mock Current API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
@@ -29,6 +30,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_mock_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
         <option name="instrumentation-arg" key="expected-api-files" value="android-test-mock-current.api" />
         <option name="runtime-hint" value="5s" />
     </test>
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
index 61de501..f81665e 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
-    <application>
+    <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
     </application>
 
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
index ecf7055..53bed33 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Android Test Runner Current API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
@@ -32,6 +33,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_runner_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
         <option name="instrumentation-arg" key="expected-api-files" value="android-test-mock-current.api,android-test-runner-current.api" />
         <option name="runtime-hint" value="5s" />
     </test>
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
new file mode 100644
index 0000000..0f2161a
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-27-api/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsApacheHttpLegacy27ApiSignatureTestCases
+
+LOCAL_SIGNATURE_API_FILES := \
+    current.api \
+    apache-http-legacy-minus-current.api \
+
+include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml
new file mode 100644
index 0000000..ae69f52
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-27-api/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.signature.cts.api.apache_http_legacy_27">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="27"/>
+
+    <application/>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.apache_http_legacy_27"
+                     android:label="Apache Http Legacy 27 API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
new file mode 100644
index 0000000..3bbc90e
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-27-api/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 Apache Http Legacy 27 API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="current.api->/data/local/tmp/signature-test/current.api" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="apache-http-legacy-minus-current.api->/data/local/tmp/signature-test/apache-http-legacy-minus-current.api" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsApacheHttpLegacy27ApiSignatureTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.apache_http_legacy_27" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api" />
+        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-minus-current.api" />
+        <option name="runtime-hint" value="5s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/Android.mk b/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
index df69004..b382698 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
+++ b/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
@@ -19,6 +19,6 @@
 LOCAL_PACKAGE_NAME := CtsApacheHttpLegacyCurrentApiSignatureTestCases
 
 LOCAL_SIGNATURE_API_FILES := \
-    apache-http-legacy-current.api \
+    apache-http-legacy-minus-current.api \
 
 include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
index eaf118b..f0fb7d8 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
-    <application/>
+    <application android:debuggable="true"/>
 
     <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
                      android:targetPackage="android.signature.cts.api.apache_http_legacy_current"
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
index a5e69a9..418c881 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
@@ -14,13 +14,14 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Apache Http Legacy Current API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
         <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="push" value="apache-http-legacy-current.api->/data/local/tmp/signature-test/apache-http-legacy-current.api" />
+        <option name="push" value="apache-http-legacy-minus-current.api->/data/local/tmp/signature-test/apache-http-legacy-minus-current.api" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -29,7 +30,8 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.apache_http_legacy_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
-        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
+        <option name="instrumentation-arg" key="unexpected-api-files" value="apache-http-legacy-minus-current.api" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.mk b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.mk
new file mode 100644
index 0000000..1d801c5
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsApacheHttpLegacyUsesLibraryApiSignatureTestCases
+
+LOCAL_SIGNATURE_API_FILES := \
+    current.api \
+    apache-http-legacy-minus-current.api \
+
+include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml
new file mode 100644
index 0000000..f759c54
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/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.signature.cts.api.apache_http_legacy_uses_library">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application>
+        <uses-library android:name="org.apache.http.legacy"/>
+    </application>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.apache_http_legacy_uses_library"
+                     android:label="Apache Http Legacy UsesLibrary API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
new file mode 100644
index 0000000..bd2c566
--- /dev/null
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/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 Apache Http Legacy UsesLibrary API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="current.api->/data/local/tmp/signature-test/current.api" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="apache-http-legacy-minus-current.api->/data/local/tmp/signature-test/apache-http-legacy-minus-current.api" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsApacheHttpLegacyUsesLibraryApiSignatureTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.apache_http_legacy_uses_library" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api" />
+        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-minus-current.api" />
+        <option name="runtime-hint" value="5s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/build_signature_apk.mk b/tests/signature/api-check/build_signature_apk.mk
index 3b0afd2..a5ebcf8 100644
--- a/tests/signature/api-check/build_signature_apk.mk
+++ b/tests/signature/api-check/build_signature_apk.mk
@@ -21,18 +21,24 @@
 #         the list of api files needed
 
 # don't include this package in any target
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := current
 
-LOCAL_STATIC_JAVA_LIBRARIES := cts-api-signature-test
+LOCAL_STATIC_JAVA_LIBRARIES += cts-api-signature-test
+
+LOCAL_JNI_SHARED_LIBRARIES += libclassdescriptors
+LOCAL_MULTILIB := both
 
 LOCAL_ADDITIONAL_DEPENDENCIES += \
     $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(LOCAL_SIGNATURE_API_FILES))
 
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
 include $(BUILD_CTS_PACKAGE)
 
 LOCAL_SIGNATURE_API_FILES :=
diff --git a/tests/signature/api-check/current-api/AndroidManifest.xml b/tests/signature/api-check/current-api/AndroidManifest.xml
index 7dc5730..d6f474b 100644
--- a/tests/signature/api-check/current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
-    <application/>
+    <application android:debuggable="true"/>
 
     <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
                      android:targetPackage="android.signature.cts.api.current"
diff --git a/tests/signature/api-check/current-api/AndroidTest.xml b/tests/signature/api-check/current-api/AndroidTest.xml
index 29dfe6d..59b7bd6 100644
--- a/tests/signature/api-check/current-api/AndroidTest.xml
+++ b/tests/signature/api-check/current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Current API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -36,6 +37,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
         <option name="instrumentation-arg" key="expected-api-files" value="current.api" />
         <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.api,android-test-runner-current.api" />
         <option name="runtime-hint" value="30s" />
diff --git a/tests/signature/api-check/hidden-api/Android.mk b/tests/signature/api-check/hidden-api/Android.mk
new file mode 100644
index 0000000..a5bc737
--- /dev/null
+++ b/tests/signature/api-check/hidden-api/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)
+LOCAL_MODULE := cts-hidden-api-blacklist
+LOCAL_MODULE_STEM := blacklist.api
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+include $(BUILD_SYSTEM)/base_rules.mk
+$(eval $(call copy-one-file,$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST),$(LOCAL_BUILT_MODULE)))
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libcts_hiddenapi
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := hidden-api.cpp
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsHiddenApiDiscoveryTestCases
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SIGNATURE_API_FILES := blacklist.api
+LOCAL_JNI_SHARED_LIBRARIES := libcts_hiddenapi
+include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/hidden-api/AndroidManifest.xml b/tests/signature/api-check/hidden-api/AndroidManifest.xml
new file mode 100644
index 0000000..a68e11a
--- /dev/null
+++ b/tests/signature/api-check/hidden-api/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.signature.cts.api.hidden">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application/>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.hidden"
+                     android:label="Hidden API Signature Test"/>
+</manifest>
diff --git a/tests/signature/api-check/hidden-api/AndroidTest.xml b/tests/signature/api-check/hidden-api/AndroidTest.xml
new file mode 100644
index 0000000..df6d7ce
--- /dev/null
+++ b/tests/signature/api-check/hidden-api/AndroidTest.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.
+-->
+<configuration description="Config for CTS Hidden API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="blacklist.api->/data/local/tmp/signature-test/blacklist.api" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsHiddenApiDiscoveryTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.hidden" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.HiddenApiTest" />
+        <option name="instrumentation-arg" key="hidden-api-files" value="blacklist.api" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/hidden-api/hidden-api.cpp b/tests/signature/api-check/hidden-api/hidden-api.cpp
new file mode 100644
index 0000000..fd2a94d
--- /dev/null
+++ b/tests/signature/api-check/hidden-api/hidden-api.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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"
+
+class ScopedUtfChars {
+ public:
+  ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+    if (s == NULL) {
+      utf_chars_ = NULL;
+    } else {
+      utf_chars_ = env->GetStringUTFChars(s, NULL);
+    }
+  }
+
+  ~ScopedUtfChars() {
+    if (utf_chars_) {
+      env_->ReleaseStringUTFChars(string_, utf_chars_);
+    }
+  }
+
+  const char* c_str() const {
+    return utf_chars_;
+  }
+
+ private:
+  JNIEnv* env_;
+  jstring string_;
+  const char* utf_chars_;
+};
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_signature_cts_api_HiddenApiTest_getField_1JNI(
+    JNIEnv* env, jclass, jclass klass, jstring name, jstring type) {
+  ScopedUtfChars utf_name(env, name);
+  ScopedUtfChars utf_type(env, type);
+  // Attempt to access the given instance field. It will succeed if it exists,
+  // and throw NoSuchFieldError if not.
+  env->GetFieldID(klass, utf_name.c_str(), utf_type.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_signature_cts_api_HiddenApiTest_getStaticField_1JNI(
+    JNIEnv* env, jclass, jclass klass, jstring name, jstring type) {
+  ScopedUtfChars utf_name(env, name);
+  ScopedUtfChars utf_type(env, type);
+  // Attempt to access the given static field. It will succeed if it exists,
+  // and throw NoSuchFieldError if not.
+  env->GetStaticFieldID(klass, utf_name.c_str(), utf_type.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_signature_cts_api_HiddenApiTest_getMethod_1JNI(
+    JNIEnv* env, jclass, jclass klass, jstring name, jstring signature) {
+  ScopedUtfChars utf_name(env, name);
+  ScopedUtfChars utf_signature(env, signature);
+  // Attempt to access the given instance method. It will succeed if it exists,
+  // and throw NoSuchMethodError if not.
+  env->GetMethodID(klass, utf_name.c_str(), utf_signature.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_signature_cts_api_HiddenApiTest_getStaticMethod_1JNI(
+    JNIEnv* env, jclass, jclass klass, jstring name, jstring signature) {
+  ScopedUtfChars utf_name(env, name);
+  ScopedUtfChars utf_signature(env, signature);
+  // Attempt to access the given static method. It will succeed if it exists,
+  // and throw NoSuchMethodError if not.
+  env->GetStaticMethodID(klass, utf_name.c_str(), utf_signature.c_str());
+}
diff --git a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
deleted file mode 100644
index 3473cfd..0000000
--- a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.signature.cts.api;
-
-import android.os.Bundle;
-import android.signature.cts.ApiDocumentParser;
-import android.signature.cts.ApiComplianceChecker;
-import android.signature.cts.FailureType;
-import android.signature.cts.JDiffClassDescription;
-import android.signature.cts.ReflectionHelper;
-import android.signature.cts.ResultObserver;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.TreeSet;
-import org.xmlpull.v1.XmlPullParserException;
-import repackaged.android.test.InstrumentationTestCase;
-import repackaged.android.test.InstrumentationTestRunner;
-
-import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY;
-
-/**
- * Performs the signature check via a JUnit test.
- */
-public class SignatureTest extends InstrumentationTestCase {
-
-    private static final String TAG = SignatureTest.class.getSimpleName();
-
-    /**
-     * A set of class names that are inaccessible for some reason.
-     */
-    private static final Set<String> KNOWN_INACCESSIBLE_CLASSES = new HashSet<>();
-
-    static {
-        // TODO(b/63383787) - These classes, which are nested annotations with @Retention(SOURCE)
-        // are removed from framework.dex for an as yet unknown reason.
-        KNOWN_INACCESSIBLE_CLASSES.add("android.content.pm.PackageManager.PermissionFlags");
-        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.IdentifierType");
-        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.ProgramType");
-        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.RadioManager.Band");
-        KNOWN_INACCESSIBLE_CLASSES.add("android.os.UserManager.UserRestrictionSource");
-        KNOWN_INACCESSIBLE_CLASSES.add(
-                "android.service.persistentdata.PersistentDataBlockManager.FlashLockState");
-    }
-
-    private TestResultObserver mResultObserver;
-
-    private String[] expectedApiFiles;
-    private String[] unexpectedApiFiles;
-
-    private class TestResultObserver implements ResultObserver {
-
-        boolean mDidFail = false;
-
-        StringBuilder mErrorString = new StringBuilder();
-
-        @Override
-        public void notifyFailure(FailureType type, String name, String errorMessage) {
-            mDidFail = true;
-            mErrorString.append("\n");
-            mErrorString.append(type.toString().toLowerCase());
-            mErrorString.append(":\t");
-            mErrorString.append(name);
-            mErrorString.append("\tError: ");
-            mErrorString.append(errorMessage);
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResultObserver = new TestResultObserver();
-
-        // Get the arguments passed to the instrumentation.
-        Bundle instrumentationArgs =
-                ((InstrumentationTestRunner) getInstrumentation()).getArguments();
-
-        expectedApiFiles = getCommaSeparatedList(instrumentationArgs, "expected-api-files");
-        unexpectedApiFiles = getCommaSeparatedList(instrumentationArgs, "unexpected-api-files");
-    }
-
-    private String[] getCommaSeparatedList(Bundle instrumentationArgs, String key) {
-        String argument = instrumentationArgs.getString(key);
-        if (argument == null) {
-            return new String[0];
-        }
-        return argument.split(",");
-    }
-
-    /**
-     * Tests that the device's API matches the expected set defined in xml.
-     * <p/>
-     * Will check the entire API, and then report the complete list of failures
-     */
-    public void testSignature() {
-        try {
-            Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
-            for (JDiffClassDescription classDescription : unexpectedClasses) {
-                Class<?> unexpectedClass = findUnexpectedClass(classDescription);
-                if (unexpectedClass != null) {
-                    mResultObserver.notifyFailure(
-                            FailureType.UNEXPECTED_CLASS,
-                            classDescription.getAbsoluteClassName(),
-                            "Class should not be accessible to this APK");
-                }
-            }
-
-            ApiComplianceChecker complianceChecker = new ApiComplianceChecker(mResultObserver);
-            ApiDocumentParser apiDocumentParser = new ApiDocumentParser(
-                    TAG, new ApiDocumentParser.Listener() {
-                @Override
-                public void completedClass(JDiffClassDescription classDescription) {
-                    // Ignore classes that are known to be inaccessible.
-                    if (KNOWN_INACCESSIBLE_CLASSES.contains(classDescription.getAbsoluteClassName())) {
-                        return;
-                    }
-
-                    // Ignore unexpected classes that are in the API definition.
-                    if (!unexpectedClasses.contains(classDescription)) {
-                        complianceChecker.checkSignatureCompliance(classDescription);
-                    }
-                }
-            });
-
-            for (String expectedApiFile : expectedApiFiles) {
-                File file = new File(API_FILE_DIRECTORY + "/" + expectedApiFile);
-                apiDocumentParser.parse(new FileInputStream(file));
-            }
-        } catch (Exception e) {
-            mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getMessage(),
-                    e.getMessage());
-        }
-        if (mResultObserver.mDidFail) {
-            StringBuilder errorString = mResultObserver.mErrorString;
-            ClassLoader classLoader = getClass().getClassLoader();
-            errorString.append("\nClassLoader hierarchy\n");
-            while (classLoader != null) {
-                errorString.append("    ").append(classLoader).append("\n");
-                classLoader = classLoader.getParent();
-            }
-            fail(errorString.toString());
-        }
-    }
-
-    private Class<?> findUnexpectedClass(JDiffClassDescription classDescription) {
-        try {
-            return ReflectionHelper.findMatchingClass(classDescription);
-        } catch (ClassNotFoundException e) {
-            return null;
-        }
-    }
-
-    private Set<JDiffClassDescription> loadUnexpectedClasses()
-            throws IOException, XmlPullParserException {
-
-        Set<JDiffClassDescription> unexpectedClasses = new TreeSet<>(
-                Comparator.comparing(JDiffClassDescription::getAbsoluteClassName));
-        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG,
-                new ApiDocumentParser.Listener() {
-                    @Override
-                    public void completedClass(JDiffClassDescription classDescription) {
-                        unexpectedClasses.add(classDescription);
-                    }
-                });
-        for (String expectedApiFile : unexpectedApiFiles) {
-            File file = new File(API_FILE_DIRECTORY + "/" + expectedApiFile);
-            apiDocumentParser.parse(new FileInputStream(file));
-        }
-        return unexpectedClasses;
-    }
-}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
new file mode 100644
index 0000000..4e4ef80
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.api;
+
+import android.os.Bundle;
+import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.ExcludingClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+import org.xmlpull.v1.XmlPullParserException;
+import repackaged.android.test.InstrumentationTestCase;
+import repackaged.android.test.InstrumentationTestRunner;
+
+import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY;
+
+/**
+ */
+public class AbstractApiTest extends InstrumentationTestCase {
+
+    /**
+     * A set of class names that are inaccessible for some reason.
+     */
+    private static final Set<String> KNOWN_INACCESSIBLE_CLASSES = new HashSet<>();
+
+    static {
+        // TODO(b/63383787) - These classes, which are nested annotations with @Retention(SOURCE)
+        // are removed from framework.dex for an as yet unknown reason.
+        KNOWN_INACCESSIBLE_CLASSES.add("android.content.pm.PackageManager.PermissionFlags");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.IdentifierType");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.ProgramType");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.RadioManager.Band");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.os.UserManager.UserRestrictionSource");
+        KNOWN_INACCESSIBLE_CLASSES.add(
+                "android.service.persistentdata.PersistentDataBlockManager.FlashLockState");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.IdentifierType");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.ProgramType");
+        KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.RadioManager.Band");
+    }
+
+    private TestResultObserver mResultObserver;
+
+    ClassProvider classProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResultObserver = new TestResultObserver();
+
+        // Get the arguments passed to the instrumentation.
+        Bundle instrumentationArgs =
+                ((InstrumentationTestRunner) getInstrumentation()).getArguments();
+
+        // Prepare for a class provider that loads classes from bootclasspath but filters
+        // out known inaccessible classes.
+        // Note that com.android.internal.R.* inner classes are also excluded as they are
+        // not part of API though exist in the runtime.
+        classProvider = new ExcludingClassProvider(
+                new BootClassPathClassesProvider(),
+                name -> KNOWN_INACCESSIBLE_CLASSES.contains(name)
+                        || (name != null && name.startsWith("com.android.internal.R.")));
+
+
+        initializeFromArgs(instrumentationArgs);
+    }
+
+    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
+
+    }
+
+    private static boolean isAccessibleClass(JDiffClassDescription classDescription) {
+        // Ignore classes that are known to be inaccessible.
+        return !KNOWN_INACCESSIBLE_CLASSES.contains(classDescription.getAbsoluteClassName());
+    }
+
+    protected interface RunnableWithTestResultObserver {
+        void run(TestResultObserver observer) throws Exception;
+    }
+
+    void runWithTestResultObserver(RunnableWithTestResultObserver runnable) {
+        try {
+            runnable.run(mResultObserver);
+        } catch (Exception e) {
+            StringWriter writer = new StringWriter();
+            writer.write(e.toString());
+            writer.write("\n");
+            e.printStackTrace(new PrintWriter(writer));
+            mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getClass().getName(),
+                    writer.toString());
+        }
+        if (mResultObserver.mDidFail) {
+            StringBuilder errorString = mResultObserver.mErrorString;
+            ClassLoader classLoader = getClass().getClassLoader();
+            errorString.append("\nClassLoader hierarchy\n");
+            while (classLoader != null) {
+                errorString.append("    ").append(classLoader).append("\n");
+                classLoader = classLoader.getParent();
+            }
+            fail(errorString.toString());
+        }
+    }
+
+    static String[] getCommaSeparatedList(Bundle instrumentationArgs, String key) {
+        String argument = instrumentationArgs.getString(key);
+        if (argument == null) {
+            return new String[0];
+        }
+        return argument.split(",");
+    }
+
+    static Stream<InputStream> readFile(File file) {
+        try {
+            if (file.getName().endsWith(".zip")) {
+                ZipFile zip = new ZipFile(file);
+                return zip.stream().map(entry -> {
+                    try {
+                        return zip.getInputStream(entry);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }});
+            } else {
+                return Stream.of(new FileInputStream(file));
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static Stream<JDiffClassDescription> parseApiFilesAsStream(
+            ApiDocumentParser apiDocumentParser, String[] apiFiles)
+            throws XmlPullParserException, IOException {
+        return Stream.of(apiFiles)
+                .map(name -> new File(API_FILE_DIRECTORY + "/" + name))
+                .flatMap(file -> readFile(file))
+                .flatMap(stream -> {
+                    try {
+                        return apiDocumentParser.parseAsStream(stream)
+                              .filter(AbstractApiTest::isAccessibleClass);
+                    } catch (IOException | XmlPullParserException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+    }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java b/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java
new file mode 100644
index 0000000..95f46df
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.os.Debug;
+import android.signature.cts.ClassProvider;
+import dalvik.system.BaseDexClassLoader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+@SuppressWarnings("deprecation")
+public class BootClassPathClassesProvider extends ClassProvider {
+    private static boolean sJvmtiAttached = false;
+
+    @Override
+    public Stream<Class<?>> getAllClasses() {
+        if (!sJvmtiAttached) {
+            try {
+                Debug.attachJvmtiAgent(copyAgentToFile("classdescriptors").getAbsolutePath(), null,
+                        BootClassPathClassesProvider.class.getClassLoader());
+                sJvmtiAttached = true;
+                initialize();
+            } catch (Exception e) {
+                throw new RuntimeException("Error while attaching JVMTI agent", e);
+            }
+        }
+        return Arrays.stream(getClassloaderDescriptors(Object.class.getClassLoader()))
+                .map(descriptor -> {
+                    System.err.println("Class name = " + descriptor);
+                    String classname = descriptor.replace('/', '.');
+                    // omit L and ; at the front and at the end
+                    return classname.substring(1, classname.length() - 1);
+                })
+                .map(classname -> {
+                    try {
+                        return getClass(classname);
+                    } catch (ClassNotFoundException e) {
+                        throw new RuntimeException("Cannot load " + classname, e);
+                    }
+                });
+    }
+
+    private static File copyAgentToFile(String lib) throws Exception {
+        ClassLoader cl = BootClassPathClassesProvider.class.getClassLoader();
+
+        File copiedAgent = File.createTempFile("agent", ".so");
+        try (InputStream is = new FileInputStream(
+                ((BaseDexClassLoader) cl).findLibrary(lib))) {
+            try (OutputStream os = new FileOutputStream(copiedAgent)) {
+                byte[] buffer = new byte[64 * 1024];
+
+                while (true) {
+                    int numRead = is.read(buffer);
+                    if (numRead == -1) {
+                        break;
+                    }
+                    os.write(buffer, 0, numRead);
+                }
+            }
+        }
+        return copiedAgent;
+    }
+
+    private static native void initialize();
+
+    private static native String[] getClassloaderDescriptors(ClassLoader loader);
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
new file mode 100644
index 0000000..e1da5ac
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.os.Bundle;
+import android.signature.cts.DexApiDocumentParser;
+import android.signature.cts.DexApiDocumentParser.DexField;
+import android.signature.cts.DexApiDocumentParser.DexMember;
+import android.signature.cts.DexApiDocumentParser.DexMethod;
+import android.signature.cts.FailureType;
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Stream;
+import java.text.ParseException;
+
+import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY;
+
+/**
+ * Checks that it is not possible to access hidden APIs.
+ */
+public class HiddenApiTest extends AbstractApiTest {
+
+    private String[] hiddenApiFiles;
+
+    @Override
+    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
+        hiddenApiFiles = getCommaSeparatedList(instrumentationArgs, "hidden-api-files");
+    }
+
+    /**
+     * Tests that the device does not expose APIs on the provided lists of
+     * DEX signatures.
+     *
+     * Will check the entire API, and then report the complete list of failures
+     */
+    public void testSignature() {
+        System.loadLibrary("cts_hiddenapi");
+        runWithTestResultObserver(resultObserver -> {
+            parseDexApiFilesAsStream(hiddenApiFiles).forEach(dexMember -> {
+                checkSingleMember(dexMember, resultObserver);
+            });
+        });
+    }
+
+    /**
+     * Check that a DexMember cannot be discovered with reflection or JNI, and
+     * record results in the result
+     */
+    private void checkSingleMember(DexMember dexMember, TestResultObserver resultObserver) {
+        Class<?> klass = findClass(dexMember);
+        if (klass == null) {
+            // Class not found. Therefore its members are not visible.
+            return;
+        }
+
+        if (dexMember instanceof DexField) {
+            if (hasMatchingField_Reflection(klass, (DexField) dexMember)) {
+                resultObserver.notifyFailure(
+                        FailureType.EXTRA_FIELD,
+                        dexMember.toString(),
+                        "Hidden field accessible through reflection");
+            }
+            if (hasMatchingField_JNI(klass, (DexField) dexMember)) {
+                resultObserver.notifyFailure(
+                        FailureType.EXTRA_FIELD,
+                        dexMember.toString(),
+                        "Hidden field accessible through JNI");
+            }
+        } else if (dexMember instanceof DexMethod) {
+            if (hasMatchingMethod_Reflection(klass, (DexMethod) dexMember)) {
+                resultObserver.notifyFailure(
+                        FailureType.EXTRA_METHOD,
+                        dexMember.toString(),
+                        "Hidden method accessible through reflection");
+            }
+            if (hasMatchingMethod_JNI(klass, (DexMethod) dexMember)) {
+                resultObserver.notifyFailure(
+                        FailureType.EXTRA_METHOD,
+                        dexMember.toString(),
+                        "Hidden method accessible through JNI");
+            }
+        } else {
+            throw new IllegalStateException("Unexpected type of dex member");
+        }
+    }
+
+    private boolean typesMatch(Class<?>[] classes, List<String> typeNames) {
+        if (classes.length != typeNames.size()) {
+            return false;
+        }
+        for (int i = 0; i < classes.length; ++i) {
+            if (!classes[i].getTypeName().equals(typeNames.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Class<?> findClass(DexMember dexMember) {
+        Class<?> klass = null;
+        try {
+            return Class.forName(dexMember.getJavaClassName());
+        } catch (ClassNotFoundException ex) {
+            return null;
+        }
+    }
+
+    private static boolean hasMatchingField_Reflection(Class<?> klass, DexField dexField) {
+        try {
+            klass.getDeclaredField(dexField.getName());
+            return true;
+        } catch (NoSuchFieldException ex) {
+            return false;
+        }
+    }
+
+    private static boolean hasMatchingField_JNI(Class<?> klass, DexField dexField) {
+        try {
+            getField_JNI(klass, dexField.getName(), dexField.getDexType());
+            return true;
+        } catch (NoSuchFieldError ex) {
+        }
+        try {
+            getStaticField_JNI(klass, dexField.getName(), dexField.getDexType());
+            return true;
+        } catch (NoSuchFieldError ex) {
+        }
+        return false;
+    }
+
+    private boolean hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod) {
+        List<String> methodParams = dexMethod.getJavaParameterTypes();
+
+        if (dexMethod.isConstructor()) {
+            for (Constructor constructor : klass.getDeclaredConstructors()) {
+                if (typesMatch(constructor.getParameterTypes(), methodParams)) {
+                    return true;
+                }
+            }
+        } else {
+            for (Method method : klass.getDeclaredMethods()) {
+                if (method.getName().equals(dexMethod.getName())
+                        && typesMatch(method.getParameterTypes(), methodParams)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod) {
+        try {
+            getMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
+            return true;
+        } catch (NoSuchMethodError ex) {
+        }
+        if (!dexMethod.isConstructor()) {
+            try {
+                getStaticMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
+                return true;
+            } catch (NoSuchMethodError ex) {
+            }
+        }
+        return false;
+    }
+
+    private static Stream<DexMember> parseDexApiFilesAsStream(String[] apiFiles) {
+        DexApiDocumentParser dexApiDocumentParser = new DexApiDocumentParser();
+        return Stream.of(apiFiles)
+                .map(name -> new File(API_FILE_DIRECTORY + "/" + name))
+                .flatMap(file -> readFile(file))
+                .flatMap(stream -> dexApiDocumentParser.parseAsStream(stream));
+    }
+
+    private static native boolean getField_JNI(Class<?> klass, String name, String type);
+    private static native boolean getStaticField_JNI(Class<?> klass, String name, String type);
+    private static native boolean getMethod_JNI(Class<?> klass, String name, String signature);
+    private static native boolean getStaticMethod_JNI(Class<?> klass, String name,
+            String signature);
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
new file mode 100644
index 0000000..fadf748
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.os.Bundle;
+import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ReflectionHelper;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Performs the signature check via a JUnit test.
+ */
+public class SignatureTest extends AbstractApiTest {
+
+    private static final String TAG = SignatureTest.class.getSimpleName();
+
+    private String[] expectedApiFiles;
+    private String[] baseApiFiles;
+    private String[] unexpectedApiFiles;
+
+    @Override
+    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
+        expectedApiFiles = getCommaSeparatedList(instrumentationArgs, "expected-api-files");
+        baseApiFiles = getCommaSeparatedList(instrumentationArgs, "base-api-files");
+        unexpectedApiFiles = getCommaSeparatedList(instrumentationArgs, "unexpected-api-files");
+    }
+
+    /**
+     * Tests that the device's API matches the expected set defined in xml.
+     * <p/>
+     * Will check the entire API, and then report the complete list of failures
+     */
+    public void testSignature() {
+        runWithTestResultObserver(mResultObserver -> {
+            Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
+            for (JDiffClassDescription classDescription : unexpectedClasses) {
+                Class<?> unexpectedClass = findUnexpectedClass(classDescription, classProvider);
+                if (unexpectedClass != null) {
+                    mResultObserver.notifyFailure(
+                            FailureType.UNEXPECTED_CLASS,
+                            classDescription.getAbsoluteClassName(),
+                            "Class should not be accessible to this APK");
+                }
+            }
+
+            ApiComplianceChecker complianceChecker =
+                    new ApiComplianceChecker(mResultObserver, classProvider);
+
+            // Load classes from any API files that form the base which the expected APIs extend.
+            loadBaseClasses(complianceChecker);
+
+            ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+
+            parseApiFilesAsStream(apiDocumentParser, expectedApiFiles)
+                    .filter(not(unexpectedClasses::contains))
+                    .forEach(complianceChecker::checkSignatureCompliance);
+
+            // After done parsing all expected API files, perform any deferred checks.
+            complianceChecker.checkDeferred();
+        });
+    }
+
+    private static <T> Predicate<T> not(Predicate<T> predicate) {
+        return predicate.negate();
+    }
+
+    private Class<?> findUnexpectedClass(JDiffClassDescription classDescription,
+            ClassProvider classProvider) {
+        try {
+            return ReflectionHelper.findMatchingClass(classDescription, classProvider);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+
+    private Set<JDiffClassDescription> loadUnexpectedClasses()
+            throws IOException, XmlPullParserException {
+
+        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+        return parseApiFilesAsStream(apiDocumentParser, unexpectedApiFiles)
+                .collect(Collectors.toCollection(SignatureTest::newSetOfClassDescriptions));
+    }
+
+    private static TreeSet<JDiffClassDescription> newSetOfClassDescriptions() {
+        return new TreeSet<>(Comparator.comparing(JDiffClassDescription::getAbsoluteClassName));
+    }
+
+    private void loadBaseClasses(ApiComplianceChecker complianceChecker)
+            throws IOException, XmlPullParserException {
+
+        ApiDocumentParser apiDocumentParser =
+                new ApiDocumentParser(TAG);
+        parseApiFilesAsStream(apiDocumentParser, baseApiFiles)
+                .forEach(complianceChecker::addBaseClass);
+    }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
new file mode 100644
index 0000000..a3ea203
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.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.signature.cts.api;
+
+import android.signature.cts.FailureType;
+import android.signature.cts.ResultObserver;
+
+/**
+ * Keeps track of any reported failures.
+ */
+class TestResultObserver implements ResultObserver {
+
+    boolean mDidFail = false;
+
+    StringBuilder mErrorString = new StringBuilder();
+
+    @Override
+    public void notifyFailure(FailureType type, String name, String errorMessage) {
+        mDidFail = true;
+        mErrorString.append("\n");
+        mErrorString.append(type.toString().toLowerCase());
+        mErrorString.append(":\t");
+        mErrorString.append(name);
+        mErrorString.append("\tError: ");
+        mErrorString.append(errorMessage);
+    }
+}
diff --git a/tests/signature/api-check/src/jni/classdescriptors.cpp b/tests/signature/api-check/src/jni/classdescriptors.cpp
new file mode 100644
index 0000000..3da66f1
--- /dev/null
+++ b/tests/signature/api-check/src/jni/classdescriptors.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <jvmti.h>
+
+#include <string.h>
+
+namespace android {
+namespace signature {
+namespace cts {
+namespace api {
+
+static jvmtiEnv* jvmti_env;
+static jvmtiError (*get_descriptor_list)(jvmtiEnv* env, jobject loader, jint* cnt, char*** descs);
+
+template <typename T>
+static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename ...Rest>
+static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+static void Cleanup(char** data, jint cnt) {
+  for (jint i = 0; i < cnt; i++) {
+    Dealloc(data[i]);
+  }
+  Dealloc(data);
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm,
+                                                 __attribute__((unused)) char* options,
+                                                 __attribute__((unused)) void* reserved) {
+  jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_2);
+  if (jvmError != JNI_OK) {
+    return jvmError;
+  }
+  return JVMTI_ERROR_NONE;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_android_signature_cts_api_BootClassPathClassesProvider_getClassloaderDescriptors(
+    JNIEnv* env, jclass, jobject loader) {
+  if (get_descriptor_list == nullptr) {
+    jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+    env->ThrowNew(rt_exception, "get_class_loader_class_descriptor extension is not ready.");
+    return nullptr;
+  }
+  char** classes = nullptr;
+  jint cnt = -1;
+  jvmtiError error = get_descriptor_list(jvmti_env, loader, &cnt, &classes);
+  if (error != JVMTI_ERROR_NONE) {
+    jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+    env->ThrowNew(rt_exception, "Error while executing get_class_loader_class_descriptor.");
+    return nullptr;
+  }
+
+  jobjectArray arr = env->NewObjectArray(cnt, env->FindClass("java/lang/String"), nullptr);
+  if (env->ExceptionCheck()) {
+    Cleanup(classes, cnt);
+    return nullptr;
+  }
+
+  for (jint i = 0; i < cnt; i++) {
+    env->SetObjectArrayElement(arr, i, env->NewStringUTF(classes[i]));
+    if (env->ExceptionCheck()) {
+      Cleanup(classes, cnt);
+      return nullptr;
+    }
+  }
+  Cleanup(classes, cnt);
+  return arr;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_android_signature_cts_api_BootClassPathClassesProvider_initialize(JNIEnv* env, jclass) {
+  jint functionInfosCount = 0;
+  jvmtiExtensionFunctionInfo* functionInfos = nullptr;
+
+  jvmtiError err = jvmti_env->GetExtensionFunctions(&functionInfosCount, &functionInfos);
+  if (err != JVMTI_ERROR_NONE) {
+    jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+    env->ThrowNew(rt_exception, "Failed to get JVMTI extension APIs");
+    return;
+  }
+
+  for (jint i = 0; i < functionInfosCount; i++) {
+    jvmtiExtensionFunctionInfo* curInfo = &functionInfos[i];
+    if (strcmp("com.android.art.class.get_class_loader_class_descriptors", curInfo->id) == 0) {
+      get_descriptor_list = reinterpret_cast<jvmtiError (*)(jvmtiEnv*, jobject, jint*, char***)>(curInfo->func);
+    }
+    DeallocParams(curInfo->params, curInfo->param_count);
+    Dealloc(curInfo->id, curInfo->short_description, curInfo->params, curInfo->errors);
+  }
+  Dealloc(functionInfos);
+
+  if (get_descriptor_list == nullptr) {
+    jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+    env->ThrowNew(rt_exception, "Failed to find get_class_loader_class_descriptors extension");
+    return;
+  }
+}
+
+}  // namespace api
+}  // namespace cts
+}  // namespace signature
+}  // namespace android
diff --git a/tests/signature/api-check/system-annotation/Android.mk b/tests/signature/api-check/system-annotation/Android.mk
new file mode 100644
index 0000000..d740d0f
--- /dev/null
+++ b/tests/signature/api-check/system-annotation/Android.mk
@@ -0,0 +1,26 @@
+# 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_PACKAGE_NAME := CtsSystemApiAnnotationTestCases
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
+LOCAL_SIGNATURE_API_FILES := \
+    system-current.api \
+    system-removed.api \
+
+include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/system-annotation/AndroidManifest.xml b/tests/signature/api-check/system-annotation/AndroidManifest.xml
new file mode 100644
index 0000000..7753fb9
--- /dev/null
+++ b/tests/signature/api-check/system-annotation/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.signature.cts.api.system_annotation">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application android:debuggable="true"/>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.system_annotation"
+                     android:label="System API Annotation Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
new file mode 100644
index 0000000..56a4cca
--- /dev/null
+++ b/tests/signature/api-check/system-annotation/AndroidTest.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.
+-->
+<configuration description="Config for CTS System Current API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="system-current.api->/data/local/tmp/signature-test/system-current.api" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="system-removed.api->/data/local/tmp/signature-test/system-removed.api" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSystemApiAnnotationTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.system_annotation" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.AnnotationTest" />
+        <option name="instrumentation-arg" key="expected-api-files" value="system-current.api,system-removed.api" />
+        <option name="instrumentation-arg" key="annotation-for-exact-match" value="android.annotation.SystemApi" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
new file mode 100644
index 0000000..b94300f
--- /dev/null
+++ b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.signature.cts.AnnotationChecker;
+import android.signature.cts.ApiDocumentParser;
+
+import com.android.compatibility.common.util.PropertyUtil;
+
+/**
+ * Checks that parts of the device's API that are annotated (e.g. with android.annotation.SystemApi)
+ * match the API definition.
+ */
+public class AnnotationTest extends AbstractApiTest {
+
+    private static final String TAG = AnnotationTest.class.getSimpleName();
+
+    private String[] expectedApiFiles;
+    private String annotationForExactMatch;
+
+    @Override
+    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
+        expectedApiFiles = getCommaSeparatedList(instrumentationArgs, "expected-api-files");
+        annotationForExactMatch = instrumentationArgs.getString("annotation-for-exact-match");
+    }
+
+    /**
+     * Tests that the parts of the device's API that are annotated (e.g. with
+     * android.annotation.SystemApi) match the API definition.
+     */
+    public void testAnnotation() {
+        if ("true".equals(PropertyUtil.getProperty("ro.treble.enabled")) &&
+                PropertyUtil.getFirstApiLevel() > Build.VERSION_CODES.O_MR1) {
+            runWithTestResultObserver(resultObserver -> {
+                AnnotationChecker complianceChecker = new AnnotationChecker(resultObserver,
+                        classProvider, annotationForExactMatch);
+
+                ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+
+                parseApiFilesAsStream(apiDocumentParser, expectedApiFiles)
+                        .forEach(complianceChecker::checkSignatureCompliance);
+
+                // After done parsing all expected API files, perform any deferred checks.
+                complianceChecker.checkDeferred();
+            });
+        }
+    }
+}
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
new file mode 100644
index 0000000..e795a6d
--- /dev/null
+++ b/tests/signature/api-check/system-api/Android.mk
@@ -0,0 +1,50 @@
+# 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)
+
+all_system_api_files := system-current.api system-removed.api
+$(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
+  $(if $(call math_is_number,$(ver)),\
+    $(eval all_system_api_files += system-$(ver).api)\
+  )\
+)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := cts-system-all.api
+LOCAL_MODULE_STEM := system-all.api.zip
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(all_system_api_files))
+	@echo "Zip API files $^ -> $@"
+	@mkdir -p $(dir $@)
+	$(hide) rm -f $@
+	$(hide) zip -q $@ $^
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSystemApiSignatureTestCases
+
+LOCAL_SIGNATURE_API_FILES := \
+    current.api \
+    android-test-mock-current.api \
+    android-test-runner-current.api \
+    $(all_sytem_api_files) \
+    system-all.api.zip
+
+include $(LOCAL_PATH)/../build_signature_apk.mk
+
+all_system_api_files :=
diff --git a/tests/signature/api-check/system-api/AndroidManifest.xml b/tests/signature/api-check/system-api/AndroidManifest.xml
new file mode 100644
index 0000000..1f7bed8
--- /dev/null
+++ b/tests/signature/api-check/system-api/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.signature.cts.api.system">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application/>
+
+    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.signature.cts.api.system"
+                     android:label="System API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/system-api/AndroidTest.xml b/tests/signature/api-check/system-api/AndroidTest.xml
new file mode 100644
index 0000000..5f70478
--- /dev/null
+++ b/tests/signature/api-check/system-api/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?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 System API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="current.api->/data/local/tmp/signature-test/current.api" />
+        <option name="push" value="android-test-mock-current.api->/data/local/tmp/signature-test/android-test-mock-current.api" />
+        <option name="push" value="android-test-runner-current.api->/data/local/tmp/signature-test/android-test-runner-current.api" />
+        <!-- This zip file contains all versions of system APIs that the platform is supposed to implement -->
+        <option name="push" value="system-all.api.zip->/data/local/tmp/signature-test/system-all.api.zip" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSystemApiSignatureTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.system" />
+        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="class" value="android.signature.cts.api.SignatureTest" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api" />
+        <option name="instrumentation-arg" key="expected-api-files" value="system-all.api.zip" />
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.api,android-test-runner-current.api" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/system-current-api/Android.mk b/tests/signature/api-check/system-current-api/Android.mk
deleted file mode 100644
index 1c10d2c..0000000
--- a/tests/signature/api-check/system-current-api/Android.mk
+++ /dev/null
@@ -1,26 +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_PACKAGE_NAME := CtsSystemCurrentApiSignatureTestCases
-
-LOCAL_SIGNATURE_API_FILES := \
-    system-current.api \
-    android-test-mock-current.api \
-    android-test-runner-current.api \
-
-include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/system-current-api/AndroidManifest.xml b/tests/signature/api-check/system-current-api/AndroidManifest.xml
deleted file mode 100644
index d678c4b..0000000
--- a/tests/signature/api-check/system-current-api/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="android.signature.cts.api.system_current">
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-
-    <application/>
-
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
-                     android:targetPackage="android.signature.cts.api.system_current"
-                     android:label="System Current API Signature Test"/>
-
-</manifest>
diff --git a/tests/signature/api-check/system-current-api/AndroidTest.xml b/tests/signature/api-check/system-current-api/AndroidTest.xml
deleted file mode 100644
index 6bf641a..0000000
--- a/tests/signature/api-check/system-current-api/AndroidTest.xml
+++ /dev/null
@@ -1,42 +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 System Current API Signature test cases">
-    <option name="config-descriptor:metadata" key="component" value="systems" />
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
-        <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="push" value="system-current.api->/data/local/tmp/signature-test/system-current.api" />
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="push" value="android-test-mock-current.api->/data/local/tmp/signature-test/android-test-mock-current.api" />
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="push" value="android-test-runner-current.api->/data/local/tmp/signature-test/android-test-runner-current.api" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsSystemCurrentApiSignatureTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.signature.cts.api.system_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
-        <option name="instrumentation-arg" key="expected-api-files" value="system-current.api" />
-        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.api,android-test-runner-current.api" />
-        <option name="runtime-hint" value="30s" />
-    </test>
-</configuration>
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index f84ca36..b257c5e 100644
--- a/tests/signature/api/Android.mk
+++ b/tests/signature/api/Android.mk
@@ -15,71 +15,61 @@
 # We define this in a subdir so that it won't pick up the parent's Android.xml by default.
 
 LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
 
-# current api, in XML format.
+# $(1) name of the xml file to be created
+# $(2) path to the api text file
+define build_xml_api_file
+include $(CLEAR_VARS)
+LOCAL_MODULE := cts-$(subst .,-,$(1))
+LOCAL_MODULE_STEM := $(1)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
+include $(BUILD_SYSTEM)/base_rules.mk
+$$(LOCAL_BUILT_MODULE): $(2) | $(APICHECK)
+	@echo "Convert API file $$< -> $$@"
+	@mkdir -p $$(dir $$@)
+	$(hide) $(APICHECK_COMMAND) -convert2xmlnostrip $$< $$@
+endef
+
 # NOTE: the output XML file is also used
 # in //cts/hostsidetests/devicepolicy/AndroidTest.xml
 # by com.android.cts.managedprofile.CurrentApiHelper
 # ============================================================
+$(eval $(call build_xml_api_file,current.api,frameworks/base/api/current.txt))
+$(eval $(call build_xml_api_file,system-current.api,frameworks/base/api/system-current.txt))
+$(eval $(call build_xml_api_file,system-removed.api,frameworks/base/api/system-removed.txt))
+$(eval $(call build_xml_api_file,android-test-base-current.api,frameworks/base/test-base/api/android-test-base-current.txt))
+$(eval $(call build_xml_api_file,android-test-mock-current.api,frameworks/base/test-mock/api/android-test-mock-current.txt))
+$(eval $(call build_xml_api_file,android-test-runner-current.api,frameworks/base/test-runner/api/android-test-runner-current.txt))
+$(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
+  $(if $(call math_is_number,$(ver)),\
+    $(eval $(call build_xml_api_file,system-$(ver).api,prebuilts/sdk/system-api/$(ver).txt))\
+  )\
+)
+
+# current apache-http-legacy minus current api, in text format.
+# =============================================================
+# Removes any classes from the org.apache.http.legacy API description that are
+# also part of the Android API description.
 include $(CLEAR_VARS)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_ETC)
 
-LOCAL_MODULE := cts-current-api
-LOCAL_MODULE_STEM := current.api
-LOCAL_SRC_FILES := frameworks/base/api/current.txt
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-include $(LOCAL_PATH)/build_xml_api_file.mk
+LOCAL_MODULE := cts-apache-http-legacy-minus-current-api
+LOCAL_MODULE_STEM := apache-http-legacy-minus-current.api
 
-# current system api, in XML format.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-system-current-api
-LOCAL_MODULE_STEM := system-current.api
-LOCAL_SRC_FILES := frameworks/base/api/system-current.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
-
-# removed system api, in XML format.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-system-removed-api
-LOCAL_MODULE_STEM := system-removed.api
-LOCAL_SRC_FILES := frameworks/base/api/system-removed.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
-
-# current android-test-base api, in XML format.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-android-test-base-current-api
-LOCAL_MODULE_STEM := android-test-base-current.api
-LOCAL_SRC_FILES := frameworks/base/test-base/api/android-test-base-current.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
-
-# current android-test-mock api, in XML format.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-android-test-mock-current-api
-LOCAL_MODULE_STEM := android-test-mock-current.api
-LOCAL_SRC_FILES := frameworks/base/test-mock/api/android-test-mock-current.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
-
-# current android-test-runner api, in XML format.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-android-test-runner-current-api
-LOCAL_MODULE_STEM := android-test-runner-current.api
-LOCAL_SRC_FILES := frameworks/base/test-runner/api/android-test-runner-current.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
-
-# current apache-http-legacy api, in XML format.
-# ==============================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-apache-http-legacy-current-api
-LOCAL_MODULE_STEM := apache-http-legacy-current.api
-LOCAL_SRC_FILES := external/apache-http/api/apache-http-legacy-current.txt
-
-include $(LOCAL_PATH)/build_xml_api_file.mk
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE) : \
+        frameworks/base/api/current.txt \
+        external/apache-http/api/apache-http-legacy-current.txt \
+        | $(APICHECK)
+	@echo "Generate unique Apache Http Legacy API file -> $@"
+	@mkdir -p $(dir $@)
+	$(hide) $(APICHECK_COMMAND) -new_api_no_strip \
+	        frameworks/base/api/current.txt \
+            external/apache-http/api/apache-http-legacy-current.txt \
+            $@
diff --git a/tests/signature/api/build_xml_api_file.mk b/tests/signature/api/build_xml_api_file.mk
deleted file mode 100644
index f5468bb..0000000
--- a/tests/signature/api/build_xml_api_file.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Specify the following 3 variables before including:
-#
-#     LOCAL_MODULE_STEM
-#         the name of the file to generate, e.g. current.api
-#
-#     LOCAL_MODULE
-#         the name of the module - must be unique
-#
-#     LOCAL_SRC_FILES
-#         the name of the source api txt file - only one file allowed
-
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_ETC)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE) : ${LOCAL_SRC_FILES} | $(APICHECK)
-	@echo "Convert API file $< -> $@"
-	@mkdir -p $(dir $@)
-	$(hide) $(APICHECK_COMMAND) -convert2xmlnostrip $< $@
diff --git a/tests/signature/intent-check/AndroidTest.xml b/tests/signature/intent-check/AndroidTest.xml
index da6895b..134a175 100644
--- a/tests/signature/intent-check/AndroidTest.xml
+++ b/tests/signature/intent-check/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Intent Signature test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
diff --git a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
index 2f551dc..477c781 100644
--- a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
+++ b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
@@ -127,25 +127,21 @@
 
         Set<String> androidIntents = new HashSet<>();
 
-        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG,
-                new ApiDocumentParser.Listener() {
-                    @Override
-                    public void completedClass(JDiffClassDescription classDescription) {
-                        for (JDiffField diffField : classDescription.getFieldList()) {
-                            String fieldValue = diffField.getValueString();
-                            if (fieldValue != null) {
-                                fieldValue = fieldValue.replace("\"", "");
-                                if (fieldValue.startsWith(ANDROID_INTENT_PREFIX)) {
-                                    androidIntents.add(fieldValue);
-                                }
+        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+
+        apiDocumentParser.parseAsStream(new FileInputStream(new File(apiFileName))).forEach(
+                classDescription -> {
+                    for (JDiffField diffField : classDescription.getFieldList()) {
+                        String fieldValue = diffField.getValueString();
+                        if (fieldValue != null) {
+                            fieldValue = fieldValue.replace("\"", "");
+                            if (fieldValue.startsWith(ANDROID_INTENT_PREFIX)) {
+                                androidIntents.add(fieldValue);
                             }
                         }
-
                     }
                 });
 
-        apiDocumentParser.parse(new FileInputStream(new File(apiFileName)));
-
         return androidIntents;
     }
 
diff --git a/tests/signature/runSignatureTests.sh b/tests/signature/runSignatureTests.sh
index e23d4fa..1350297 100755
--- a/tests/signature/runSignatureTests.sh
+++ b/tests/signature/runSignatureTests.sh
@@ -12,11 +12,18 @@
 if [ $# -eq 0 ]; then
     PACKAGES="
 CtsCurrentApiSignatureTestCases
-CtsSystemCurrentApiSignatureTestCases
+CtsSystemApiSignatureTestCases
 CtsAndroidTestMockCurrentApiSignatureTestCases
 CtsAndroidTestRunnerCurrentApiSignatureTestCases
 CtsAndroidTestBase27ApiSignatureTestCases
+
+CtsApacheHttpLegacy27ApiSignatureTestCases
 CtsApacheHttpLegacyCurrentApiSignatureTestCases
+CtsApacheHttpLegacyUsesLibraryApiSignatureTestCases
+
+CtsSystemApiAnnotationTestCases
+
+CtsHiddenApiDiscoveryTestCases
 "
 else
     PACKAGES=${1+"$@"}
diff --git a/tests/signature/src/android/signature/cts/AbstractApiChecker.java b/tests/signature/src/android/signature/cts/AbstractApiChecker.java
new file mode 100644
index 0000000..3ea16e1
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/AbstractApiChecker.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for those that process a set of API definition files and perform some checking on
+ * them.
+ */
+public abstract class AbstractApiChecker {
+
+    final ResultObserver resultObserver;
+
+    final ClassProvider classProvider;
+
+    AbstractApiChecker(ClassProvider classProvider, ResultObserver resultObserver) {
+        this.classProvider = classProvider;
+        this.resultObserver = resultObserver;
+    }
+
+    /**
+     * Checks test class's name, modifier, fields, constructors, and
+     * methods.
+     */
+    public void checkSignatureCompliance(JDiffClassDescription classDescription) {
+        Class<?> runtimeClass = checkClassCompliance(classDescription);
+        if (runtimeClass != null) {
+            checkFieldsCompliance(classDescription, runtimeClass);
+            checkConstructorCompliance(classDescription, runtimeClass);
+            checkMethodCompliance(classDescription, runtimeClass);
+        }
+    }
+
+    /**
+     * Checks that the class found through reflection matches the
+     * specification from the API xml file.
+     *
+     * @param classDescription a description of a class in an API.
+     */
+    @SuppressWarnings("unchecked")
+    private Class<?> checkClassCompliance(JDiffClassDescription classDescription) {
+        try {
+            Class<?> runtimeClass = ReflectionHelper
+                    .findRequiredClass(classDescription, classProvider);
+
+            if (runtimeClass == null) {
+                // No class found, notify the observer according to the class type
+                resultObserver.notifyFailure(FailureType.missing(classDescription),
+                        classDescription.getAbsoluteClassName(),
+                        "Classloader is unable to find " + classDescription
+                                .getAbsoluteClassName());
+
+                return null;
+            }
+
+            if (!checkClass(classDescription, runtimeClass)) {
+                return null;
+            }
+
+            return runtimeClass;
+        } catch (Exception e) {
+            LogHelper.loge("Got exception when checking class compliance", e);
+            resultObserver.notifyFailure(
+                    FailureType.CAUGHT_EXCEPTION,
+                    classDescription.getAbsoluteClassName(),
+                    "Exception while checking class compliance!");
+            return null;
+        }
+    }
+
+    /**
+     * Perform any additional checks that can only be done after all api files have been processed.
+     */
+    public abstract void checkDeferred();
+
+    /**
+     * Implement to provide custom check of the supplied class description.
+     *
+     * <p>This should not peform checks on the members, those will be done separately depending
+     * on the result of this method.
+     *
+     * @param classDescription the class description to check
+     * @param runtimeClass the runtime class corresponding to the class description.
+     * @return true if the checks passed and the members should now be checked.
+     */
+    protected abstract boolean checkClass(JDiffClassDescription classDescription,
+            Class<?> runtimeClass);
+
+
+    /**
+     * Checks all fields in test class for compliance with the API xml.
+     *
+     * @param classDescription a description of a class in an API.
+     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
+     */
+    @SuppressWarnings("unchecked")
+    private void checkFieldsCompliance(JDiffClassDescription classDescription,
+            Class<?> runtimeClass) {
+        // A map of field name to field of the fields contained in runtimeClass.
+        Map<String, Field> classFieldMap = buildFieldMap(runtimeClass);
+        for (JDiffClassDescription.JDiffField field : classDescription.getFields()) {
+            try {
+                Field f = classFieldMap.get(field.mName);
+                if (f == null) {
+                    resultObserver.notifyFailure(FailureType.MISSING_FIELD,
+                            field.toReadableString(classDescription.getAbsoluteClassName()),
+                            "No field with correct signature found:" +
+                                    field.toSignatureString());
+                } else {
+                    checkField(classDescription, runtimeClass, field, f);
+                }
+            } catch (Exception e) {
+                LogHelper.loge("Got exception when checking field compliance", e);
+                resultObserver.notifyFailure(
+                        FailureType.CAUGHT_EXCEPTION,
+                        field.toReadableString(classDescription.getAbsoluteClassName()),
+                        "Exception while checking field compliance");
+            }
+        }
+    }
+
+    /**
+     * Scan a class (an its entire inheritance chain) for fields.
+     *
+     * @return a {@link Map} of fieldName to {@link Field}
+     */
+    private static Map<String, Field> buildFieldMap(Class testClass) {
+        Map<String, Field> fieldMap = new HashMap<>();
+        // Scan the superclass
+        if (testClass.getSuperclass() != null) {
+            fieldMap.putAll(buildFieldMap(testClass.getSuperclass()));
+        }
+
+        // Scan the interfaces
+        for (Class interfaceClass : testClass.getInterfaces()) {
+            fieldMap.putAll(buildFieldMap(interfaceClass));
+        }
+
+        // Check the fields in the test class
+        for (Field field : testClass.getDeclaredFields()) {
+            fieldMap.put(field.getName(), field);
+        }
+
+        return fieldMap;
+    }
+
+    protected abstract void checkField(JDiffClassDescription classDescription,
+            Class<?> runtimeClass,
+            JDiffClassDescription.JDiffField fieldDescription, Field field);
+
+
+    /**
+     * Checks whether the constructor parsed from API xml file and
+     * Java reflection are compliant.
+     *
+     * @param classDescription a description of a class in an API.
+     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
+     */
+    @SuppressWarnings("unchecked")
+    private void checkConstructorCompliance(JDiffClassDescription classDescription,
+            Class<?> runtimeClass) {
+        for (JDiffClassDescription.JDiffConstructor con : classDescription.getConstructors()) {
+            try {
+                Constructor<?> c = ReflectionHelper.findMatchingConstructor(runtimeClass, con);
+                if (c == null) {
+                    resultObserver.notifyFailure(FailureType.MISSING_CONSTRUCTOR,
+                            con.toReadableString(classDescription.getAbsoluteClassName()),
+                            "No constructor with correct signature found:" +
+                                    con.toSignatureString());
+                } else {
+                    checkConstructor(classDescription, runtimeClass, con, c);
+                }
+            } catch (Exception e) {
+                LogHelper.loge("Got exception when checking constructor compliance", e);
+                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
+                        con.toReadableString(classDescription.getAbsoluteClassName()),
+                        "Exception while checking constructor compliance!");
+            }
+        }
+    }
+
+    protected abstract void checkConstructor(JDiffClassDescription classDescription,
+            Class<?> runtimeClass,
+            JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor);
+
+    /**
+     * Checks that the method found through reflection matches the
+     * specification from the API xml file.
+     *
+     * @param classDescription a description of a class in an API.
+     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
+     */
+    private void checkMethodCompliance(JDiffClassDescription classDescription,
+            Class<?> runtimeClass) {
+        for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
+            try {
+
+                Method m = ReflectionHelper.findMatchingMethod(runtimeClass, method);
+                if (m == null) {
+                    resultObserver.notifyFailure(FailureType.MISSING_METHOD,
+                            method.toReadableString(classDescription.getAbsoluteClassName()),
+                            "No method with correct signature found:" +
+                                    method.toSignatureString());
+                } else {
+                    checkMethod(classDescription, runtimeClass, method, m);
+                }
+            } catch (Exception e) {
+                LogHelper.loge("Got exception when checking method compliance", e);
+                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
+                        method.toReadableString(classDescription.getAbsoluteClassName()),
+                        "Exception while checking method compliance!");
+            }
+        }
+    }
+
+    protected abstract void checkMethod(JDiffClassDescription classDescription,
+            Class<?> runtimeClass,
+            JDiffClassDescription.JDiffMethod methodDescription, Method method);
+}
diff --git a/tests/signature/src/android/signature/cts/AnnotationChecker.java b/tests/signature/src/android/signature/cts/AnnotationChecker.java
new file mode 100644
index 0000000..9419df1
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/AnnotationChecker.java
@@ -0,0 +1,161 @@
+/*
+ * 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.signature.cts;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Checks that the runtime representation of a class matches the API representation of a class.
+ */
+public class AnnotationChecker extends AbstractApiChecker {
+
+    private final Class<? extends Annotation> annotationClass;
+
+    private final Map<String, Class<?>> annotatedClassesMap = new HashMap<>();
+    private final Map<String, Set<Constructor<?>>> annotatedConstructorsMap = new HashMap<>();
+    private final Map<String, Set<Method>> annotatedMethodsMap = new HashMap<>();
+    private final Map<String, Set<Field>> annotatedFieldsMap = new HashMap<>();
+
+    /**
+     * @param annotationName name of the annotation class for the API type (e.g.
+     *      android.annotation.SystemApi)
+     */
+    public AnnotationChecker(
+            ResultObserver resultObserver, ClassProvider classProvider, String annotationName) {
+        super(classProvider, resultObserver);
+
+        annotationClass = ReflectionHelper.getAnnotationClass(annotationName);
+        classProvider.getAllClasses().forEach(clazz -> {
+            if (clazz.isAnnotationPresent(annotationClass)) {
+                annotatedClassesMap.put(clazz.getName(), clazz);
+            }
+            Set<Constructor<?>> constructors = ReflectionHelper.getAnnotatedConstructors(clazz,
+                    annotationClass);
+            if (!constructors.isEmpty()) {
+                annotatedConstructorsMap.put(clazz.getName(), constructors);
+            }
+            Set<Method> methods = ReflectionHelper.getAnnotatedMethods(clazz, annotationClass);
+            if (!methods.isEmpty()) {
+                annotatedMethodsMap.put(clazz.getName(), methods);
+            }
+            Set<Field> fields = ReflectionHelper.getAnnotatedFields(clazz, annotationClass);
+            if (!fields.isEmpty()) {
+                annotatedFieldsMap.put(clazz.getName(), fields);
+            }
+        });
+    }
+
+    @Override
+    public void checkDeferred() {
+        for (Class<?> clazz : annotatedClassesMap.values()) {
+            resultObserver.notifyFailure(FailureType.EXTRA_CLASS, clazz.getName(),
+                    "Class annotated with " + annotationClass.getName()
+                            + " does not exist in the documented API");
+        }
+        for (Set<Constructor<?>> set : annotatedConstructorsMap.values()) {
+            for (Constructor<?> c : set) {
+                resultObserver.notifyFailure(FailureType.EXTRA_METHOD, c.toString(),
+                        "Constructor annotated with " + annotationClass.getName()
+                                + " does not exist in the API");
+            }
+        }
+        for (Set<Method> set : annotatedMethodsMap.values()) {
+            for (Method m : set) {
+                resultObserver.notifyFailure(FailureType.EXTRA_METHOD, m.toString(),
+                        "Method annotated with " + annotationClass.getName()
+                                + " does not exist in the API");
+            }
+        }
+        for (Set<Field> set : annotatedFieldsMap.values()) {
+            for (Field f : set) {
+                resultObserver.notifyFailure(FailureType.EXTRA_FIELD, f.toString(),
+                        "Field annotated with " + annotationClass.getName()
+                                + " does not exist in the API");
+            }
+        }
+    }
+
+    @Override
+    protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) {
+        // remove the class from the set if found
+        annotatedClassesMap.remove(runtimeClass.getName());
+        return true;
+    }
+
+    @Override
+    protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffField fieldDescription, Field field) {
+        // make sure that the field (or its declaring class) is annotated
+        if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(field, annotationClass)) {
+            resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+                    field.toString(),
+                    "Annotation " + annotationClass.getName() + " is missing");
+        }
+
+        // remove it from the set if found in the API doc
+        Set<Field> annotatedFields = annotatedFieldsMap.get(runtimeClass.getName());
+        if (annotatedFields != null) {
+            annotatedFields.remove(field);
+        }
+    }
+
+    @Override
+    protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) {
+        Set<Constructor<?>> annotatedConstructors = annotatedConstructorsMap
+                .get(runtimeClass.getName());
+
+        // make sure that the constructor (or its declaring class) is annotated
+        if (annotationClass != null) {
+            if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(ctor, annotationClass)) {
+                resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+                        ctor.toString(),
+                        "Annotation " + annotationClass.getName() + " is missing");
+            }
+        }
+
+        // remove it from the set if found in the API doc
+        if (annotatedConstructors != null) {
+            annotatedConstructors.remove(ctor);
+        }
+    }
+
+    @Override
+    protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffMethod methodDescription, Method method) {
+        // make sure that the method (or its declaring class) is annotated or overriding
+        // annotated method.
+        if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(method, annotationClass)
+                && !ReflectionHelper.isOverridingAnnotatedMethod(method,
+                        annotationClass)) {
+            resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+                    method.toString(),
+                    "Annotation " + annotationClass.getName() + " is missing");
+        }
+
+        // remove it from the set if found in the API doc
+        Set<Method> annotatedMethods = annotatedMethodsMap.get(runtimeClass.getName());
+        if (annotatedMethods != null) {
+            annotatedMethods.remove(method);
+        }
+    }
+}
diff --git a/tests/signature/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
index 8dbf5e7..a72059f 100644
--- a/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
@@ -20,18 +20,14 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
 /**
  * Checks that the runtime representation of a class matches the API representation of a class.
  */
-public class ApiComplianceChecker {
+public class ApiComplianceChecker extends AbstractApiChecker {
 
     /** Indicates that the class is an annotation. */
     private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000;
@@ -48,189 +44,56 @@
     /** Indicates that the method is a synthetic method. */
     private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000;
 
-    private static final Set<String> HIDDEN_INTERFACE_WHITELIST = new HashSet<>();
+    private final InterfaceChecker interfaceChecker;
 
-    static {
-        // Interfaces that define @hide or @SystemApi or @TestApi methods will by definition contain
-        // methods that do not appear in current.txt. Interfaces added to this
-        // list are probably not meant to be implemented in an application.
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
-        HIDDEN_INTERFACE_WHITELIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract java.lang.String android.companion.DeviceFilter.getDeviceDisplayName(D)");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract int android.companion.DeviceFilter.getMediumType()");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
-        HIDDEN_INTERFACE_WHITELIST.add("public abstract android.graphics.Region android.view.WindowManager.getCurrentImeTouchRegion()");
+    public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
+        super(classProvider, resultObserver);
+        interfaceChecker = new InterfaceChecker(resultObserver, classProvider);
     }
 
-
-    private final ResultObserver resultObserver;
-
-    public ApiComplianceChecker(ResultObserver resultObserver) {
-        this.resultObserver = resultObserver;
+    @Override
+    public void checkDeferred() {
+        interfaceChecker.checkQueued();
     }
 
-    private static void loge(String message, Exception exception) {
-        System.err.println(String.format("%s: %s", message, exception));
-    }
-
-    private void logMismatchInterfaceSignature(JDiffClassDescription.JDiffType mClassType,
-            String classFullName, String errorMessage) {
-        if (JDiffClassDescription.JDiffType.INTERFACE.equals(mClassType)) {
-            resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE,
-                    classFullName,
-                    errorMessage);
-        } else {
-            resultObserver.notifyFailure(FailureType.MISMATCH_CLASS,
-                    classFullName,
-                    errorMessage);
+    @Override
+    protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) {
+        if (JDiffClassDescription.JDiffType.INTERFACE.equals(classDescription.getClassType())) {
+            // Queue the interface for deferred checking.
+            interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass);
         }
-    }
 
-    /**
-     * Checks test class's name, modifier, fields, constructors, and
-     * methods.
-     */
-    public void checkSignatureCompliance(JDiffClassDescription classDescription) {
-        Class<?> runtimeClass = checkClassCompliance(classDescription);
-        if (runtimeClass != null) {
-            checkFieldsCompliance(classDescription, runtimeClass);
-            checkConstructorCompliance(classDescription, runtimeClass);
-            checkMethodCompliance(classDescription, runtimeClass);
-        }
-    }
-
-    /**
-     * Checks that the class found through reflection matches the
-     * specification from the API xml file.
-     *
-     * @param classDescription a description of a class in an API.
-     */
-    @SuppressWarnings("unchecked")
-    private Class<?> checkClassCompliance(JDiffClassDescription classDescription) {
-        try {
-            Class<?> runtimeClass = findRequiredClass(classDescription);
-
-            if (runtimeClass == null) {
-                // No class found, notify the observer according to the class type
-                if (JDiffClassDescription.JDiffType.INTERFACE.equals(
-                        classDescription.getClassType())) {
-                    resultObserver.notifyFailure(FailureType.MISSING_INTERFACE,
-                            classDescription.getAbsoluteClassName(),
-                            "Classloader is unable to find " + classDescription
-                                    .getAbsoluteClassName());
-                } else {
-                    resultObserver.notifyFailure(FailureType.MISSING_CLASS,
-                            classDescription.getAbsoluteClassName(),
-                            "Classloader is unable to find " + classDescription
-                                    .getAbsoluteClassName());
-                }
-
-                return null;
-            }
-
-            List<String> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
-            if (JDiffClassDescription.JDiffType.INTERFACE.equals(classDescription.getClassType()) && methods.size() > 0) {
-                resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
-                        classDescription.getAbsoluteClassName(), "Interfaces cannot be modified: "
-                                + classDescription.getAbsoluteClassName() + ": " + methods);
-                return null;
-            }
-
-            if (!checkClassModifiersCompliance(classDescription, runtimeClass)) {
-                logMismatchInterfaceSignature(classDescription.getClassType(),
-                        classDescription.getAbsoluteClassName(),
-                                "Non-compatible class found when looking for " +
-                                        classDescription.toSignatureString());
-                return null;
-            }
-
-            if (!checkClassAnnotationCompliance(classDescription, runtimeClass)) {
-                logMismatchInterfaceSignature(classDescription.getClassType(),
-                        classDescription.getAbsoluteClassName(),
-                                "Annotation mismatch");
-                return null;
-            }
-
-            if (!runtimeClass.isAnnotation()) {
-                // check father class
-                if (!checkClassExtendsCompliance(classDescription, runtimeClass)) {
-                    logMismatchInterfaceSignature(classDescription.getClassType(),
-                            classDescription.getAbsoluteClassName(),
-                                    "Extends mismatch");
-                    return null;
-                }
-
-                // check implements interface
-                if (!checkClassImplementsCompliance(classDescription, runtimeClass)) {
-                    logMismatchInterfaceSignature(classDescription.getClassType(),
-                            classDescription.getAbsoluteClassName(),
-                                    "Implements mismatch");
-                    return null;
-                }
-            }
-            return runtimeClass;
-        } catch (Exception e) {
-            loge("Got exception when checking field compliance", e);
-            resultObserver.notifyFailure(
-                    FailureType.CAUGHT_EXCEPTION,
+        String reason;
+        if ((reason = checkClassModifiersCompliance(classDescription, runtimeClass)) != null) {
+            resultObserver.notifyFailure(FailureType.mismatch(classDescription),
                     classDescription.getAbsoluteClassName(),
-                    "Exception!");
-            return null;
-        }
-    }
-
-    private Class<?> findRequiredClass(JDiffClassDescription classDescription) {
-        try {
-            return ReflectionHelper.findMatchingClass(classDescription);
-        } catch (ClassNotFoundException e) {
-            loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
-            return null;
-        }
-    }
-
-    /**
-     * Validate that an interfaces method count is as expected.
-     *
-     * @param classDescription the class's API description.
-     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     */
-    private static List<String> checkInterfaceMethodCompliance(
-            JDiffClassDescription classDescription, Class<?> runtimeClass) {
-        List<String> unexpectedMethods = new ArrayList<>();
-        for (Method method : runtimeClass.getDeclaredMethods()) {
-            if (method.isDefault()) {
-                continue;
-            }
-            if (method.isSynthetic()) {
-                continue;
-            }
-            if (method.isBridge()) {
-                continue;
-            }
-            if (HIDDEN_INTERFACE_WHITELIST.contains(method.toGenericString())) {
-                continue;
-            }
-
-            boolean foundMatch = false;
-            for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
-                if (ReflectionHelper.matches(jdiffMethod, method)) {
-                    foundMatch = true;
-                }
-            }
-            if (!foundMatch) {
-                unexpectedMethods.add(method.toGenericString());
-            }
+                    String.format("Non-compatible class found when looking for %s - because %s",
+                            classDescription.toSignatureString(), reason));
+            return false;
         }
 
-        return unexpectedMethods;
+        if (!checkClassAnnotationCompliance(classDescription, runtimeClass)) {
+            resultObserver.notifyFailure(FailureType.mismatch(classDescription),
+                    classDescription.getAbsoluteClassName(), "Annotation mismatch");
+            return false;
+        }
 
+        if (!runtimeClass.isAnnotation()) {
+            // check father class
+            if (!checkClassExtendsCompliance(classDescription, runtimeClass)) {
+                resultObserver.notifyFailure(FailureType.mismatch(classDescription),
+                        classDescription.getAbsoluteClassName(), "Extends mismatch");
+                return false;
+            }
+
+            // check implements interface
+            if (!checkClassImplementsCompliance(classDescription, runtimeClass)) {
+                resultObserver.notifyFailure(FailureType.mismatch(classDescription),
+                        classDescription.getAbsoluteClassName(), "Implements mismatch");
+                return false;
+            }
+        }
+        return true;
     }
 
     /**
@@ -238,38 +101,43 @@
      *
      * @param classDescription a description of a class in an API.
      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     * @return true if modifiers are compliant.
+     * @return null if modifiers are compliant otherwise a reason why they are not.
      */
-    private static boolean checkClassModifiersCompliance(JDiffClassDescription classDescription,
+    private static String checkClassModifiersCompliance(JDiffClassDescription classDescription,
             Class<?> runtimeClass) {
-        int reflectionModifier = runtimeClass.getModifiers();
-        int apiModifier = classDescription.getModifier();
+        int reflectionModifiers = runtimeClass.getModifiers();
+        int apiModifiers = classDescription.getModifier();
 
         // If the api class isn't abstract
-        if (((apiModifier & Modifier.ABSTRACT) == 0) &&
+        if (((apiModifiers & Modifier.ABSTRACT) == 0) &&
                 // but the reflected class is
-                ((reflectionModifier & Modifier.ABSTRACT) != 0) &&
+                ((reflectionModifiers & Modifier.ABSTRACT) != 0) &&
                 // and it isn't an enum
                 !classDescription.isEnumType()) {
             // that is a problem
-            return false;
+            return "description is abstract but class is not and is not an enum";
         }
         // ABSTRACT check passed, so mask off ABSTRACT
-        reflectionModifier &= ~Modifier.ABSTRACT;
-        apiModifier &= ~Modifier.ABSTRACT;
+        reflectionModifiers &= ~Modifier.ABSTRACT;
+        apiModifiers &= ~Modifier.ABSTRACT;
 
         if (classDescription.isAnnotation()) {
-            reflectionModifier &= ~CLASS_MODIFIER_ANNOTATION;
+            reflectionModifiers &= ~CLASS_MODIFIER_ANNOTATION;
         }
         if (runtimeClass.isInterface()) {
-            reflectionModifier &= ~(Modifier.INTERFACE);
+            reflectionModifiers &= ~(Modifier.INTERFACE);
         }
         if (classDescription.isEnumType() && runtimeClass.isEnum()) {
-            reflectionModifier &= ~CLASS_MODIFIER_ENUM;
+            reflectionModifiers &= ~CLASS_MODIFIER_ENUM;
         }
 
-        return ((reflectionModifier == apiModifier) &&
-                (classDescription.isEnumType() == runtimeClass.isEnum()));
+        if ((reflectionModifiers == apiModifiers)
+                && (classDescription.isEnumType() == runtimeClass.isEnum())) {
+            return null;
+        } else {
+            return String.format("modifier mismatch - description (%s), class (%s)",
+                    Modifier.toString(apiModifiers), Modifier.toString(reflectionModifiers));
+        }
     }
 
     /**
@@ -330,12 +198,9 @@
      */
     private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription,
             Class<?> runtimeClass) {
-        Class<?>[] interfaces = runtimeClass.getInterfaces();
         Set<String> interFaceSet = new HashSet<>();
 
-        for (Class<?> c : interfaces) {
-            interFaceSet.add(c.getCanonicalName());
-        }
+        addInterfacesToSetByName(runtimeClass, interFaceSet);
 
         for (String inter : classDescription.getImplInterfaces()) {
             if (!interFaceSet.contains(inter)) {
@@ -345,59 +210,47 @@
         return true;
     }
 
+    private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) {
+        Class<?>[] interfaces = runtimeClass.getInterfaces();
+        for (Class<?> c : interfaces) {
+            interFaceSet.add(c.getCanonicalName());
+        }
 
-    /**
-     * Checks all fields in test class for compliance with the API xml.
-     *
-     * @param classDescription a description of a class in an API.
-     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     */
-    @SuppressWarnings("unchecked")
-    private void checkFieldsCompliance(JDiffClassDescription classDescription,
-            Class<?> runtimeClass) {
-        // A map of field name to field of the fields contained in runtimeClass.
-        Map<String, Field> classFieldMap = buildFieldMap(runtimeClass);
-        for (JDiffClassDescription.JDiffField field : classDescription.getFields()) {
-            try {
-                Field f = classFieldMap.get(field.mName);
-                if (f == null) {
-                    resultObserver.notifyFailure(FailureType.MISSING_FIELD,
-                            field.toReadableString(classDescription.getAbsoluteClassName()),
-                            "No field with correct signature found:" +
-                                    field.toSignatureString());
-                } else if (f.getModifiers() != field.mModifier) {
-                    resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
-                            field.toReadableString(classDescription.getAbsoluteClassName()),
-                            "Non-compatible field modifiers found when looking for " +
-                                    field.toSignatureString());
-                } else if (!checkFieldValueCompliance(field, f)) {
-                    resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
-                            field.toReadableString(classDescription.getAbsoluteClassName()),
-                            "Incorrect field value found when looking for " +
-                                    field.toSignatureString());
-                } else if (!f.getType().getCanonicalName().equals(field.mFieldType)) {
-                    // type name does not match, but this might be a generic
-                    String genericTypeName = null;
-                    Type type = f.getGenericType();
-                    if (type != null) {
-                        genericTypeName = type instanceof Class ? ((Class) type).getName() :
-                                type.toString().replace('$', '.');
-                    }
-                    if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) {
-                        resultObserver.notifyFailure(
-                                FailureType.MISMATCH_FIELD,
-                                field.toReadableString(classDescription.getAbsoluteClassName()),
-                                "Non-compatible field type found when looking for " +
-                                        field.toSignatureString());
-                    }
-                }
+        // Add the interfaces that the super class implements as well just in case the super class
+        // is hidden.
+        Class<?> superClass = runtimeClass.getSuperclass();
+        if (superClass != null) {
+            addInterfacesToSetByName(superClass, interFaceSet);
+        }
+    }
 
-            } catch (Exception e) {
-                loge("Got exception when checking field compliance", e);
+    @Override
+    protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffField fieldDescription, Field field) {
+        if (field.getModifiers() != fieldDescription.mModifier) {
+            resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
+                    fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
+                    "Non-compatible field modifiers found when looking for " +
+                            fieldDescription.toSignatureString());
+        } else if (!checkFieldValueCompliance(fieldDescription, field)) {
+            resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
+                    fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
+                    "Incorrect field value found when looking for " +
+                            fieldDescription.toSignatureString());
+        } else if (!field.getType().getCanonicalName().equals(fieldDescription.mFieldType)) {
+            // type name does not match, but this might be a generic
+            String genericTypeName = null;
+            Type type = field.getGenericType();
+            if (type != null) {
+                genericTypeName = type instanceof Class ? ((Class) type).getName() :
+                        type.toString().replace('$', '.');
+            }
+            if (genericTypeName == null || !genericTypeName.equals(fieldDescription.mFieldType)) {
                 resultObserver.notifyFailure(
-                        FailureType.CAUGHT_EXCEPTION,
-                        field.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception!");
+                        FailureType.MISMATCH_FIELD,
+                        fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
+                        "Non-compatible field type found when looking for " +
+                                fieldDescription.toSignatureString());
             }
         }
     }
@@ -408,8 +261,7 @@
      * @param apiField The field as defined by the platform API.
      * @param deviceField The field as defined by the device under test.
      */
-    private static boolean checkFieldValueCompliance(JDiffClassDescription.JDiffField apiField, Field deviceField)
-            throws IllegalAccessException {
+    private static boolean checkFieldValueCompliance(JDiffClassDescription.JDiffField apiField, Field deviceField) {
         if ((apiField.mModifier & Modifier.FINAL) == 0 ||
                 (apiField.mModifier & Modifier.STATIC) == 0) {
             // Only final static fields can have fixed values.
@@ -421,40 +273,44 @@
         }
         // Some fields may be protected or package-private
         deviceField.setAccessible(true);
-        switch (apiField.mFieldType) {
-            case "byte":
-                return Objects.equals(apiField.getValueString(),
-                        Byte.toString(deviceField.getByte(null)));
-            case "char":
-                return Objects.equals(apiField.getValueString(),
-                        Integer.toString(deviceField.getChar(null)));
-            case "short":
-                return Objects.equals(apiField.getValueString(),
-                        Short.toString(deviceField.getShort(null)));
-            case "int":
-                return Objects.equals(apiField.getValueString(),
-                        Integer.toString(deviceField.getInt(null)));
-            case "long":
-                return Objects.equals(apiField.getValueString(),
-                        Long.toString(deviceField.getLong(null)) + "L");
-            case "float":
-                return Objects.equals(apiField.getValueString(),
-                        canonicalizeFloatingPoint(
-                                Float.toString(deviceField.getFloat(null)), "f"));
-            case "double":
-                return Objects.equals(apiField.getValueString(),
-                        canonicalizeFloatingPoint(
-                                Double.toString(deviceField.getDouble(null)), ""));
-            case "boolean":
-                return Objects.equals(apiField.getValueString(),
-                        Boolean.toString(deviceField.getBoolean(null)));
-            case "java.lang.String":
-                String value = apiField.getValueString();
-                // Remove the quotes the value string is wrapped in
-                value = unescapeFieldStringValue(value.substring(1, value.length() - 1));
-                return Objects.equals(value, deviceField.get(null));
-            default:
-                return true;
+        try {
+            switch (apiField.mFieldType) {
+                case "byte":
+                    return Objects.equals(apiField.getValueString(),
+                            Byte.toString(deviceField.getByte(null)));
+                case "char":
+                    return Objects.equals(apiField.getValueString(),
+                            Integer.toString(deviceField.getChar(null)));
+                case "short":
+                    return Objects.equals(apiField.getValueString(),
+                            Short.toString(deviceField.getShort(null)));
+                case "int":
+                    return Objects.equals(apiField.getValueString(),
+                            Integer.toString(deviceField.getInt(null)));
+                case "long":
+                    return Objects.equals(apiField.getValueString(),
+                            Long.toString(deviceField.getLong(null)) + "L");
+                case "float":
+                    return Objects.equals(apiField.getValueString(),
+                            canonicalizeFloatingPoint(
+                                    Float.toString(deviceField.getFloat(null)), "f"));
+                case "double":
+                    return Objects.equals(apiField.getValueString(),
+                            canonicalizeFloatingPoint(
+                                    Double.toString(deviceField.getDouble(null)), ""));
+                case "boolean":
+                    return Objects.equals(apiField.getValueString(),
+                            Boolean.toString(deviceField.getBoolean(null)));
+                case "java.lang.String":
+                    String value = apiField.getValueString();
+                    // Remove the quotes the value string is wrapped in
+                    value = unescapeFieldStringValue(value.substring(1, value.length() - 1));
+                    return Objects.equals(value, deviceField.get(null));
+                default:
+                    return true;
+            }
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
         }
     }
 
@@ -588,117 +444,46 @@
         return buf.toString();
     }
 
-    /**
-     * Scan a class (an its entire inheritance chain) for fields.
-     *
-     * @return a {@link Map} of fieldName to {@link Field}
-     */
-    private static Map<String, Field> buildFieldMap(Class testClass) {
-        Map<String, Field> fieldMap = new HashMap<>();
-        // Scan the superclass
-        if (testClass.getSuperclass() != null) {
-            fieldMap.putAll(buildFieldMap(testClass.getSuperclass()));
+    @Override
+    protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) {
+        if (ctor.isVarArgs()) {// some method's parameter are variable args
+            ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS;
         }
-
-        // Scan the interfaces
-        for (Class interfaceClass : testClass.getInterfaces()) {
-            fieldMap.putAll(buildFieldMap(interfaceClass));
-        }
-
-        // Check the fields in the test class
-        for (Field field : testClass.getDeclaredFields()) {
-            fieldMap.put(field.getName(), field);
-        }
-
-        return fieldMap;
-    }
-
-    /**
-     * Checks whether the constructor parsed from API xml file and
-     * Java reflection are compliant.
-     *
-     * @param classDescription a description of a class in an API.
-     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     */
-    @SuppressWarnings("unchecked")
-    private void checkConstructorCompliance(JDiffClassDescription classDescription,
-            Class<?> runtimeClass) {
-        for (JDiffClassDescription.JDiffConstructor con : classDescription.getConstructors()) {
-            try {
-                Constructor<?> c = ReflectionHelper.findMatchingConstructor(runtimeClass, con);
-                if (c == null) {
-                    resultObserver.notifyFailure(FailureType.MISSING_METHOD,
-                            con.toReadableString(classDescription.getAbsoluteClassName()),
-                            "No method with correct signature found:" +
-                                    con.toSignatureString());
-                } else {
-                    if (c.isVarArgs()) {// some method's parameter are variable args
-                        con.mModifier |= METHOD_MODIFIER_VAR_ARGS;
-                    }
-                    if (c.getModifiers() != con.mModifier) {
-                        resultObserver.notifyFailure(
-                                FailureType.MISMATCH_METHOD,
-                                con.toReadableString(classDescription.getAbsoluteClassName()),
-                                "Non-compatible method found when looking for " +
-                                        con.toSignatureString());
-                    }
-                }
-            } catch (Exception e) {
-                loge("Got exception when checking constructor compliance", e);
-                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
-                        con.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception!");
-            }
+        if (ctor.getModifiers() != ctorDescription.mModifier) {
+            resultObserver.notifyFailure(
+                    FailureType.MISMATCH_METHOD,
+                    ctorDescription.toReadableString(classDescription.getAbsoluteClassName()),
+                    "Non-compatible method found when looking for " +
+                            ctorDescription.toSignatureString());
         }
     }
 
-    /**
-     * Checks that the method found through reflection matches the
-     * specification from the API xml file.
-     *
-     * @param classDescription a description of a class in an API.
-     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     */
-    private void checkMethodCompliance(JDiffClassDescription classDescription,
-            Class<?> runtimeClass) {
-        for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
-            try {
+    @Override
+    protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass,
+            JDiffClassDescription.JDiffMethod methodDescription, Method method) {
+        if (method.isVarArgs()) {
+            methodDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS;
+        }
+        if (method.isBridge()) {
+            methodDescription.mModifier |= METHOD_MODIFIER_BRIDGE;
+        }
+        if (method.isSynthetic()) {
+            methodDescription.mModifier |= METHOD_MODIFIER_SYNTHETIC;
+        }
 
-                Method m = ReflectionHelper.findMatchingMethod(runtimeClass, method);
-                if (m == null) {
-                    resultObserver.notifyFailure(FailureType.MISSING_METHOD,
-                            method.toReadableString(classDescription.getAbsoluteClassName()),
-                            "No method with correct signature found:" +
-                                    method.toSignatureString());
-                } else {
-                    if (m.isVarArgs()) {
-                        method.mModifier |= METHOD_MODIFIER_VAR_ARGS;
-                    }
-                    if (m.isBridge()) {
-                        method.mModifier |= METHOD_MODIFIER_BRIDGE;
-                    }
-                    if (m.isSynthetic()) {
-                        method.mModifier |= METHOD_MODIFIER_SYNTHETIC;
-                    }
+        // FIXME: A workaround to fix the final mismatch on enumeration
+        if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) {
+            return;
+        }
 
-                    // FIXME: A workaround to fix the final mismatch on enumeration
-                    if (runtimeClass.isEnum() && method.mName.equals("values")) {
-                        return;
-                    }
-
-                    if (!areMethodsModifiedCompatible(classDescription, method, m)) {
-                        resultObserver.notifyFailure(FailureType.MISMATCH_METHOD,
-                                method.toReadableString(classDescription.getAbsoluteClassName()),
-                                "Non-compatible method found when looking for " +
-                                        method.toSignatureString());
-                    }
-                }
-            } catch (Exception e) {
-                loge("Got exception when checking method compliance", e);
-                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
-                        method.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception!");
-            }
+        String reason;
+        if ((reason = areMethodsModifiedCompatible(
+                classDescription, methodDescription, method)) != null) {
+            resultObserver.notifyFailure(FailureType.MISMATCH_METHOD,
+                    methodDescription.toReadableString(classDescription.getAbsoluteClassName()),
+                    String.format("Non-compatible method found when looking for %s - because %s",
+                            methodDescription.toSignatureString(), reason));
         }
     }
 
@@ -713,8 +498,9 @@
      * @param classDescription a description of a class in an API.
      * @param apiMethod the method read from the api file.
      * @param reflectedMethod the method found via reflection.
+     * @return null if the method modifiers are compatible otherwise the reason why not.
      */
-    private static boolean areMethodsModifiedCompatible(
+    private static String areMethodsModifiedCompatible(
             JDiffClassDescription classDescription,
             JDiffClassDescription.JDiffMethod apiMethod,
             Method reflectedMethod) {
@@ -724,21 +510,39 @@
                 // but the reflected method is
                 ((reflectedMethod.getModifiers() & Modifier.SYNCHRONIZED) != 0)) {
             // that is a problem
-            return false;
+            return "description is synchronize but class is not";
         }
 
         // Mask off NATIVE since it is a don't care.  Also mask off
         // SYNCHRONIZED since we've already handled that check.
         int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT);
-        int mod1 = reflectedMethod.getModifiers() & ~ignoredMods;
-        int mod2 = apiMethod.mModifier & ~ignoredMods;
+        int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods;
+        int apiModifiers = apiMethod.mModifier & ~ignoredMods;
 
         // We can ignore FINAL for classes
         if ((classDescription.getModifier() & Modifier.FINAL) != 0) {
-            mod1 &= ~Modifier.FINAL;
-            mod2 &= ~Modifier.FINAL;
+            reflectionModifiers &= ~Modifier.FINAL;
+            apiModifiers &= ~Modifier.FINAL;
         }
 
-        return mod1 == mod2;
+        if (reflectionModifiers == apiModifiers) {
+            return null;
+        } else {
+            return String.format("modifier mismatch - description (%s), method (%s)",
+                    Modifier.toString(apiModifiers), Modifier.toString(reflectionModifiers));
+        }
+    }
+
+    public void addBaseClass(JDiffClassDescription classDescription) {
+        // Keep track of all the base interfaces that may by extended.
+        if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) {
+            try {
+                Class<?> runtimeClass =
+                        ReflectionHelper.findMatchingClass(classDescription, classProvider);
+                interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass);
+            } catch (ClassNotFoundException e) {
+                // Do nothing.
+            }
+        }
     }
 }
diff --git a/tests/signature/src/android/signature/cts/ApiDocumentParser.java b/tests/signature/src/android/signature/cts/ApiDocumentParser.java
index 6c8b172..18afa98 100644
--- a/tests/signature/src/android/signature/cts/ApiDocumentParser.java
+++ b/tests/signature/src/android/signature/cts/ApiDocumentParser.java
@@ -34,6 +34,10 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlPullParserFactory;
@@ -42,10 +46,7 @@
  * Parses an XML api definition file and constructs and populates an {@link JDiffClassDescription}
  * for every class.
  *
- * <p>Once it has completely populated the members (so does not include nested/inner classes) of a
- * {@link JDiffClassDescription} it notifies the {@link #listener} by calling
- * {@link Listener#completedClass(JDiffClassDescription)} with the completed
- * {@link JDiffClassDescription}.
+ * <p>The definition file is converted into a {@link Stream} of {@link JDiffClassDescription}.
  */
 public class ApiDocumentParser {
 
@@ -66,132 +67,156 @@
 
     private final String tag;
 
-    private final Listener listener;
+    private final XmlPullParserFactory factory;
 
-    private final XmlPullParser parser;
-
-    public ApiDocumentParser(String tag, Listener listener) throws XmlPullParserException {
+    public ApiDocumentParser(String tag) throws XmlPullParserException {
         this.tag = tag;
-        this.listener = listener;
-
-        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
-        parser = factory.newPullParser();
+        factory = XmlPullParserFactory.newInstance();
     }
 
-    public void parse(InputStream inputStream) throws XmlPullParserException, IOException {
-        parser.setInput(inputStream, null);
-        start(parser);
-    }
-
-    public interface Listener {
-
-        /**
-         * Invoked when a {@link JDiffClassDescription} has been completely populated.
-         *
-         * @param classDescription the description of the class as read from the XML API file.
-         */
-        void completedClass(JDiffClassDescription classDescription);
-    }
-
-
-    private void beginDocument(XmlPullParser parser, String firstElementName)
+    public Stream<JDiffClassDescription> parseAsStream(InputStream inputStream)
             throws XmlPullParserException, IOException {
-        int type;
-        do {
-            type = parser.next();
-        } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
-
-        if (type != XmlPullParser.START_TAG) {
-            throw new XmlPullParserException("No start tag found");
-        }
-
-        if (!parser.getName().equals(firstElementName)) {
-            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
-                    ", expected " + firstElementName);
-        }
+        XmlPullParser parser = factory.newPullParser();
+        parser.setInput(inputStream, null);
+        return StreamSupport.stream(new ClassDescriptionSpliterator(parser), false);
     }
 
-    /**
-     * Signature test entry point.
-     */
-    private void start(XmlPullParser parser) throws XmlPullParserException, IOException {
-        logd(String.format("Name: %s", parser.getName()));
-        logd(String.format("Text: %s", parser.getText()));
-        logd(String.format("Namespace: %s", parser.getNamespace()));
-        logd(String.format("Line Number: %s", parser.getLineNumber()));
-        logd(String.format("Column Number: %s", parser.getColumnNumber()));
-        logd(String.format("Position Description: %s", parser.getPositionDescription()));
+    private class ClassDescriptionSpliterator implements Spliterator<JDiffClassDescription> {
+
+        private final XmlPullParser parser;
+
         JDiffClassDescription currentClass = null;
         String currentPackage = "";
         JDiffClassDescription.JDiffMethod currentMethod = null;
 
-        beginDocument(parser, TAG_ROOT);
-        int type;
-        while (true) {
+        ClassDescriptionSpliterator(XmlPullParser parser) throws IOException, XmlPullParserException {
+            this.parser = parser;
+            logd(String.format("Name: %s", parser.getName()));
+            logd(String.format("Text: %s", parser.getText()));
+            logd(String.format("Namespace: %s", parser.getNamespace()));
+            logd(String.format("Line Number: %s", parser.getLineNumber()));
+            logd(String.format("Column Number: %s", parser.getColumnNumber()));
+            logd(String.format("Position Description: %s", parser.getPositionDescription()));
+            beginDocument(parser, TAG_ROOT);
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super JDiffClassDescription> action) {
+            JDiffClassDescription classDescription;
+            try {
+                classDescription = next();
+            } catch (IOException|XmlPullParserException e) {
+                throw new RuntimeException(e);
+            }
+
+            if (classDescription == null) {
+                return false;
+            }
+            action.accept(classDescription);
+            return true;
+        }
+
+        @Override
+        public Spliterator<JDiffClassDescription> trySplit() {
+            return null;
+        }
+
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public int characteristics() {
+            return ORDERED | DISTINCT | NONNULL | IMMUTABLE;
+        }
+
+        private void beginDocument(XmlPullParser parser, String firstElementName)
+                throws XmlPullParserException, IOException {
+            int type;
             do {
                 type = parser.next();
-            } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT
-                    && type != XmlPullParser.END_TAG);
+            } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
 
-            if (type == XmlPullParser.END_TAG) {
-                if (TAG_CLASS.equals(parser.getName())
-                        || TAG_INTERFACE.equals(parser.getName())) {
-                    if (listener != null) {
-                        listener.completedClass(currentClass);
-                    }
-                } else if (TAG_PACKAGE.equals(parser.getName())) {
-                    currentPackage = "";
+            if (type != XmlPullParser.START_TAG) {
+                throw new XmlPullParserException("No start tag found");
+            }
+
+            if (!parser.getName().equals(firstElementName)) {
+                throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+                        ", expected " + firstElementName);
+            }
+        }
+
+        private JDiffClassDescription next() throws IOException, XmlPullParserException {
+            int type;
+            while (true) {
+                do {
+                    type = parser.next();
+                } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT
+                        && type != XmlPullParser.END_TAG);
+
+                if (type == XmlPullParser.END_DOCUMENT) {
+                    logd("Reached end of document");
+                    break;
                 }
-                continue;
+
+                String tagname = parser.getName();
+                if (type == XmlPullParser.END_TAG) {
+                    if (TAG_CLASS.equals(tagname) || TAG_INTERFACE.equals(tagname)) {
+                        logd("Reached end of class: " + currentClass);
+                        return currentClass;
+                    } else if (TAG_PACKAGE.equals(tagname)) {
+                        currentPackage = "";
+                    }
+                    continue;
+                }
+
+                if (!KEY_TAG_SET.contains(tagname)) {
+                    continue;
+                }
+
+                if (tagname.equals(TAG_PACKAGE)) {
+                    currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                } else if (tagname.equals(TAG_CLASS)) {
+                    currentClass = CurrentApi.loadClassInfo(
+                            parser, false, currentPackage);
+                } else if (tagname.equals(TAG_INTERFACE)) {
+                    currentClass = CurrentApi.loadClassInfo(
+                            parser, true, currentPackage);
+                } else if (tagname.equals(TAG_IMPLEMENTS)) {
+                    currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
+                } else if (tagname.equals(TAG_CONSTRUCTOR)) {
+                    JDiffClassDescription.JDiffConstructor constructor =
+                            CurrentApi.loadConstructorInfo(parser, currentClass);
+                    currentClass.addConstructor(constructor);
+                    currentMethod = constructor;
+                } else if (tagname.equals(TAG_METHOD)) {
+                    currentMethod = CurrentApi.loadMethodInfo(currentClass.getClassName(), parser);
+                    currentClass.addMethod(currentMethod);
+                } else if (tagname.equals(TAG_PARAM)) {
+                    currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
+                } else if (tagname.equals(TAG_EXCEPTION)) {
+                    currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
+                } else if (tagname.equals(TAG_FIELD)) {
+                    JDiffClassDescription.JDiffField field = CurrentApi.loadFieldInfo(currentClass.getClassName(), parser);
+                    currentClass.addField(field);
+                } else {
+                    throw new RuntimeException(
+                            "unknown tag exception:" + tagname);
+                }
+                if (currentPackage != null) {
+                    logd(String.format("currentPackage: %s", currentPackage));
+                }
+                if (currentClass != null) {
+                    logd(String.format("currentClass: %s", currentClass.toSignatureString()));
+                }
+                if (currentMethod != null) {
+                    logd(String.format("currentMethod: %s", currentMethod.toSignatureString()));
+                }
             }
 
-            if (type == XmlPullParser.END_DOCUMENT) {
-                break;
-            }
-
-            String tagname = parser.getName();
-            if (!KEY_TAG_SET.contains(tagname)) {
-                continue;
-            }
-
-            if (type == XmlPullParser.START_TAG && tagname.equals(TAG_PACKAGE)) {
-                currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-            } else if (tagname.equals(TAG_CLASS)) {
-                currentClass = CurrentApi.loadClassInfo(
-                        parser, false, currentPackage);
-            } else if (tagname.equals(TAG_INTERFACE)) {
-                currentClass = CurrentApi.loadClassInfo(
-                        parser, true, currentPackage);
-            } else if (tagname.equals(TAG_IMPLEMENTS)) {
-                currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
-            } else if (tagname.equals(TAG_CONSTRUCTOR)) {
-                JDiffClassDescription.JDiffConstructor constructor =
-                        CurrentApi.loadConstructorInfo(parser, currentClass);
-                currentClass.addConstructor(constructor);
-                currentMethod = constructor;
-            } else if (tagname.equals(TAG_METHOD)) {
-                currentMethod = CurrentApi.loadMethodInfo(currentClass.getClassName(), parser);
-                currentClass.addMethod(currentMethod);
-            } else if (tagname.equals(TAG_PARAM)) {
-                currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
-            } else if (tagname.equals(TAG_EXCEPTION)) {
-                currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
-            } else if (tagname.equals(TAG_FIELD)) {
-                JDiffClassDescription.JDiffField field = CurrentApi.loadFieldInfo(currentClass.getClassName(), parser);
-                currentClass.addField(field);
-            } else {
-                throw new RuntimeException(
-                        "unknown tag exception:" + tagname);
-            }
-            if (currentPackage != null) {
-                logd(String.format("currentPackage: %s", currentPackage));
-            }
-            if (currentClass != null) {
-                logd(String.format("currentClass: %s", currentClass.toSignatureString()));
-            }
-            if (currentMethod != null) {
-                logd(String.format("currentMethod: %s", currentMethod.toSignatureString()));
-            }
+            return null;
         }
     }
 
diff --git a/tests/signature/src/android/signature/cts/ClassProvider.java b/tests/signature/src/android/signature/cts/ClassProvider.java
new file mode 100644
index 0000000..2461f26
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/ClassProvider.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.signature.cts;
+
+import java.util.stream.Stream;
+
+/**
+ * Generic class for getting runtime classes that will be matched against
+ * {@link JDiffClassDescription}. {@link ApiComplianceChecker} is using this
+ * when it needs runtime classes.
+ */
+public abstract class ClassProvider {
+    /**
+     * Get a specific class with the given name.
+     *
+     * @throws ClassNotFoundException
+     */
+    public Class<?> getClass(String name) throws ClassNotFoundException {
+        return Class.forName(name, false, this.getClass().getClassLoader());
+    }
+
+    /**
+     * Gets all classes available to this provider.
+     */
+    public abstract Stream<Class<?>> getAllClasses();
+}
diff --git a/tests/signature/src/android/signature/cts/DexApiDocumentParser.java b/tests/signature/src/android/signature/cts/DexApiDocumentParser.java
new file mode 100644
index 0000000..7cb60fb
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/DexApiDocumentParser.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import java.text.ParseException;
+
+/**
+ * Parses an API definition given as a text file with DEX signatures of class
+ * members. Constructs a {@link DexApiDocumentParser.DexMember} for every class
+ * member.
+ *
+ * <p>The definition file is converted into a {@link Stream} of
+ * {@link DexApiDocumentParser.DexMember}.
+ */
+public class DexApiDocumentParser {
+
+    public Stream<DexMember> parseAsStream(InputStream inputStream) {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        return StreamSupport.stream(new DexApiSpliterator(reader), false);
+    }
+
+    private static class DexApiSpliterator implements Spliterator<DexMember> {
+        private final BufferedReader mReader;
+        private int mLineNum;
+
+        private static final Pattern REGEX_CLASS = Pattern.compile("^L[^->]*;$");
+        private static final Pattern REGEX_FIELD = Pattern.compile("^(L[^->]*;)->(.*):(.*)$");
+        private static final Pattern REGEX_METHOD =
+                Pattern.compile("^(L[^->]*;)->(.*)\\((.*)\\)(.*)$");
+
+        DexApiSpliterator(BufferedReader reader) {
+            mReader = reader;
+            mLineNum = 0;
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super DexMember> action) {
+            DexMember nextMember = null;
+            try {
+                nextMember = next();
+            } catch (IOException | ParseException ex) {
+                throw new RuntimeException(ex);
+            }
+            if (nextMember == null) {
+                return false;
+            }
+            action.accept(nextMember);
+            return true;
+        }
+
+        @Override
+        public Spliterator<DexMember> trySplit() {
+            return null;
+        }
+
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public int characteristics() {
+            return ORDERED | DISTINCT | NONNULL | IMMUTABLE;
+        }
+
+        /**
+         * Parses lines of DEX signatures from `mReader`. The following three
+         * line formats are supported:
+         * 1) [class descriptor]
+         *      - e.g. Lcom/example/MyClass;
+         *      - these lines are ignored
+         * 2) [class descriptor]->[field name]:[field type]
+         *      - e.g. Lcom/example/MyClass;->myField:I
+         *      - these lines are parsed as field signatures
+         * 3) [class descriptor]->[method name]([method parameter types])[method return type]
+         *      - e.g. Lcom/example/MyClass;->myMethod(Lfoo;Lbar;)J
+         *      - these lines are parsed as method signatures
+         */
+        private DexMember next() throws IOException, ParseException {
+            while (true) {
+                // Read the next line from the input.
+                String line = mReader.readLine();
+                if (line == null) {
+                    // End of stream.
+                    return null;
+                }
+
+                // Increment the line number.
+                mLineNum = mLineNum + 1;
+
+                // Match line against regex patterns.
+                Matcher matchClass = REGEX_CLASS.matcher(line);
+                Matcher matchField = REGEX_FIELD.matcher(line);
+                Matcher matchMethod = REGEX_METHOD.matcher(line);
+
+                // Check that *exactly* one pattern matches.
+                int matchCount = (matchClass.matches() ? 1 : 0) + (matchField.matches() ? 1 : 0) +
+                        (matchMethod.matches() ? 1 : 0);
+                if (matchCount == 0) {
+                    throw new ParseException("Could not parse: \"" + line + "\"", mLineNum);
+                } else if (matchCount > 1) {
+                    throw new ParseException("Ambiguous parse: \"" + line + "\"", mLineNum);
+                }
+
+                // Extract information from the line.
+                if (matchClass.matches()) {
+                    // We ignore lines describing a class because classes are
+                    // not being hidden.
+                } else if (matchField.matches()) {
+                    return new DexField(
+                            matchField.group(1), matchField.group(2), matchField.group(3));
+                } else if (matchMethod.matches()) {
+                    return new DexMethod(
+                            matchMethod.group(1),matchMethod.group(2),
+                            parseDexTypeList(matchMethod.group(3)), matchMethod.group(4));
+                } else {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+
+        private List<String> parseDexTypeList(String typeSequence) throws ParseException {
+            List<String> list = new ArrayList<String>();
+            while (!typeSequence.isEmpty()) {
+                String type = firstDexTypeFromList(typeSequence);
+                list.add(type);
+                typeSequence = typeSequence.substring(type.length());
+            }
+            return list;
+        }
+
+        /**
+         * Returns the first dex type in `typeList` or throws a ParserException
+         * if a dex type is not recognized. The input is not changed.
+         */
+        private String firstDexTypeFromList(String typeList) throws ParseException {
+            String dexDimension = "";
+            while (typeList.startsWith("[")) {
+                dexDimension += "[";
+                typeList = typeList.substring(1);
+            }
+
+            String type = null;
+            if (typeList.startsWith("V")
+                    || typeList.startsWith("Z")
+                    || typeList.startsWith("B")
+                    || typeList.startsWith("C")
+                    || typeList.startsWith("S")
+                    || typeList.startsWith("I")
+                    || typeList.startsWith("J")
+                    || typeList.startsWith("F")
+                    || typeList.startsWith("D")) {
+                type = typeList.substring(0, 1);
+            } else if (typeList.startsWith("L") && typeList.indexOf(";") > 0) {
+                type = typeList.substring(0, typeList.indexOf(";") + 1);
+            } else {
+                throw new ParseException(
+                        "Unexpected dex type in \"" + typeList + "\"", mLineNum);
+            }
+
+            return dexDimension + type;
+        }
+    }
+
+    /**
+     * Represents one class member parsed from the reader of dex signatures.
+     */
+    public static abstract class DexMember {
+        private final String mName;
+        private final String mClassDescriptor;
+        private final String mType;
+
+        protected DexMember(String className, String name, String type) {
+            mName = name;
+            mClassDescriptor = className;
+            mType = type;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String getDexClassName() {
+            return mClassDescriptor;
+        }
+
+        public String getJavaClassName() {
+            return dexToJavaType(mClassDescriptor);
+        }
+
+        public String getDexType() {
+            return mType;
+        }
+
+        public String getJavaType() {
+            return dexToJavaType(mType);
+        }
+
+        /**
+         * Converts `type` to a Java type.
+         */
+        protected static String dexToJavaType(String type) {
+            String javaDimension = "";
+            while (type.startsWith("[")) {
+                javaDimension += "[]";
+                type = type.substring(1);
+            }
+
+            String javaType = null;
+            if ("V".equals(type)) {
+                javaType = "void";
+            } else if ("Z".equals(type)) {
+                javaType = "boolean";
+            } else if ("B".equals(type)) {
+                javaType = "byte";
+            } else if ("C".equals(type)) {
+                javaType = "char";
+            } else if ("S".equals(type)) {
+                javaType = "short";
+            } else if ("I".equals(type)) {
+                javaType = "int";
+            } else if ("J".equals(type)) {
+                javaType = "long";
+            } else if ("F".equals(type)) {
+                javaType = "float";
+            } else if ("D".equals(type)) {
+                javaType = "double";
+            } else if (type.startsWith("L") && type.endsWith(";")) {
+                javaType = type.substring(1, type.length() - 1).replace('/', '.');
+            } else {
+                throw new IllegalStateException("Unexpected type " + type);
+            }
+
+            return javaType + javaDimension;
+        }
+    }
+
+    public static class DexField extends DexMember {
+        public DexField(String className, String name, String type) {
+            super(className, name, type);
+        }
+
+        @Override
+        public String toString() {
+            return getJavaType() + " " + getJavaClassName() + "." + getName();
+        }
+    }
+
+    public static class DexMethod extends DexMember {
+        private final List<String> mParamTypeList;
+
+        public DexMethod(String className, String name, List<String> paramTypeList,
+                String dexReturnType) {
+            super(className, name, dexReturnType);
+            mParamTypeList = paramTypeList;
+        }
+
+        public String getDexSignature() {
+            return "(" + String.join("", mParamTypeList) + ")" + getDexType();
+        }
+
+        public List<String> getJavaParameterTypes() {
+            return mParamTypeList
+                    .stream()
+                    .map(DexMember::dexToJavaType)
+                    .collect(Collectors.toList());
+        }
+
+        public boolean isConstructor() {
+            return "<init>".equals(getName()) && "V".equals(getDexType());
+        }
+
+        @Override
+        public String toString() {
+            return getJavaType() + " " + getJavaClassName() + "." + getName()
+                    + "(" + String.join(", ", getJavaParameterTypes()) + ")";
+        }
+    }
+}
diff --git a/tests/signature/src/android/signature/cts/ExcludingClassProvider.java b/tests/signature/src/android/signature/cts/ExcludingClassProvider.java
new file mode 100644
index 0000000..290cbf5
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/ExcludingClassProvider.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.signature.cts;
+
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * A filtered class provider which excludes classes by their canonical names
+ */
+public class ExcludingClassProvider extends ClassProvider {
+    private final ClassProvider base;
+    private final Predicate<String> testForExclusion;
+
+    public ExcludingClassProvider(ClassProvider base,
+            Predicate<String> testForExclusion) {
+        this.base = base;
+        this.testForExclusion = testForExclusion;
+    }
+
+    @Override
+    public Class<?> getClass(String name) throws ClassNotFoundException {
+        if (!testForExclusion.test(name)) {
+            return base.getClass(name);
+        }
+        // a filtered-out class is the same as non-existing class
+        throw new ClassNotFoundException("Cannot find class " + name);
+    }
+
+    @Override
+    public Stream<Class<?>> getAllClasses() {
+        return base.getAllClasses()
+                .filter(clazz -> !testForExclusion.test(clazz.getCanonicalName()));
+    }
+}
diff --git a/tests/signature/src/android/signature/cts/FailureType.java b/tests/signature/src/android/signature/cts/FailureType.java
index 77820eb..25efcab 100644
--- a/tests/signature/src/android/signature/cts/FailureType.java
+++ b/tests/signature/src/android/signature/cts/FailureType.java
@@ -4,8 +4,10 @@
  * Define the type of the signature check failures.
  */
 public enum FailureType {
+    MISSING_ANNOTATION,
     MISSING_CLASS,
     MISSING_INTERFACE,
+    MISSING_CONSTRUCTOR,
     MISSING_METHOD,
     MISSING_FIELD,
     MISMATCH_CLASS,
@@ -14,5 +16,20 @@
     MISMATCH_METHOD,
     MISMATCH_FIELD,
     UNEXPECTED_CLASS,
-    CAUGHT_EXCEPTION,
+    EXTRA_CLASS,
+    EXTRA_INTERFACE,
+    EXTRA_METHOD,
+    EXTRA_FIELD,
+    CAUGHT_EXCEPTION;
+
+    static FailureType mismatch(JDiffClassDescription description) {
+        return JDiffClassDescription.JDiffType.INTERFACE.equals(description.getClassType())
+                ? FailureType.MISMATCH_INTERFACE : FailureType.MISMATCH_CLASS;
+    }
+
+    static FailureType missing(JDiffClassDescription description) {
+        return JDiffClassDescription.JDiffType.INTERFACE.equals(description.getClassType())
+                ? FailureType.MISSING_INTERFACE : FailureType.MISSING_CLASS;
+    }
+
 }
diff --git a/tests/signature/src/android/signature/cts/InterfaceChecker.java b/tests/signature/src/android/signature/cts/InterfaceChecker.java
new file mode 100644
index 0000000..6be5f96
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/InterfaceChecker.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.signature.cts;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Checks that the runtime representation of the interfaces match the API definition.
+ *
+ * <p>Interfaces are treated differently to other classes. Whereas other classes are checked by
+ * making sure that every member in the API is accessible through reflection. Interfaces are
+ * checked to make sure that every method visible through reflection is defined in the API. The
+ * reason for this difference is to ensure that no additional methods have been added to interfaces
+ * that are expected to be implemented by Android developers because that would break backwards
+ * compatibility.
+ *
+ * TODO(b/71886491): This also potentially applies to abstract classes that the App developers are
+ * expected to extend.
+ */
+class InterfaceChecker {
+
+    private static final Set<String> HIDDEN_INTERFACE_METHOD_WHITELIST = new HashSet<>();
+    static {
+        // Interfaces that define @hide or @SystemApi or @TestApi methods will by definition contain
+        // methods that do not appear in current.txt. Interfaces added to this
+        // list are probably not meant to be implemented in an application.
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract java.lang.String android.companion.DeviceFilter.getDeviceDisplayName(D)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract int android.companion.DeviceFilter.getMediumType()");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
+        HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract android.graphics.Region android.view.WindowManager.getCurrentImeTouchRegion()");
+    }
+
+    private final ResultObserver resultObserver;
+
+    private final Map<Class<?>, JDiffClassDescription> class2Description =
+            new TreeMap<>(Comparator.comparing(Class::getName));
+
+    private final ClassProvider classProvider;
+
+    InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
+        this.resultObserver = resultObserver;
+        this.classProvider = classProvider;
+    }
+
+    public void checkQueued() {
+        for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) {
+            Class<?> runtimeClass = entry.getKey();
+            JDiffClassDescription classDescription = entry.getValue();
+            List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
+            if (methods.size() > 0) {
+                resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
+                        classDescription.getAbsoluteClassName(), "Interfaces cannot be modified: "
+                                + classDescription.getAbsoluteClassName() + ": " + methods);
+            }
+        }
+    }
+
+    private static <T> Predicate<T> not(Predicate<T> predicate) {
+        return predicate.negate();
+    }
+
+    /**
+     * Validate that an interfaces method count is as expected.
+     *
+     * @param classDescription the class's API description.
+     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
+     */
+    private List<Method> checkInterfaceMethodCompliance(
+            JDiffClassDescription classDescription, Class<?> runtimeClass) {
+
+        return Stream.of(runtimeClass.getDeclaredMethods())
+                .filter(not(Method::isDefault))
+                .filter(not(Method::isSynthetic))
+                .filter(not(Method::isBridge))
+                .filter(m -> !Modifier.isStatic(m.getModifiers()))
+                .filter(m -> !HIDDEN_INTERFACE_METHOD_WHITELIST.contains(m.toGenericString()))
+                .filter(m -> !findMethod(classDescription, m))
+                .collect(Collectors.toCollection(ArrayList::new));
+    }
+
+    private boolean findMethod(JDiffClassDescription classDescription, Method method) {
+        for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
+            if (ReflectionHelper.matches(jdiffMethod, method)) {
+                return true;
+            }
+        }
+        for (String interfaceName : classDescription.getImplInterfaces()) {
+            Class<?> interfaceClass = null;
+            try {
+                interfaceClass = ReflectionHelper.findMatchingClass(interfaceName, classProvider);
+            } catch (ClassNotFoundException e) {
+                LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
+            }
+
+            JDiffClassDescription implInterface = class2Description.get(interfaceClass);
+            if (implInterface == null) {
+                // Class definition is not in the scope of the API definitions.
+                continue;
+            }
+
+            if (findMethod(implInterface, method)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) {
+
+        JDiffClassDescription existingDescription = class2Description.get(runtimeClass);
+        if (existingDescription != null) {
+            for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
+                existingDescription.addMethod(method);
+            }
+        } else {
+            class2Description.put(runtimeClass, classDescription);
+        }
+    }
+}
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index df91ddb..30584c9 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -42,10 +42,10 @@
     private int mModifier;
 
     private String mExtendedClass;
-    private List<String> implInterfaces = new ArrayList<>();
-    private List<JDiffField> jDiffFields = new ArrayList<>();
-    private List<JDiffMethod> jDiffMethods = new ArrayList<>();
-    private List<JDiffConstructor> jDiffConstructors = new ArrayList<>();
+    private final List<String> implInterfaces = new ArrayList<>();
+    private final List<JDiffField> jDiffFields = new ArrayList<>();
+    private final List<JDiffMethod> jDiffMethods = new ArrayList<>();
+    private final List<JDiffConstructor> jDiffConstructors = new ArrayList<>();
 
     private JDiffType mClassType;
 
@@ -66,7 +66,7 @@
         return mPackageName;
     }
 
-    String getShortClassName() {
+    public String getShortClassName() {
         return mShortClassName;
     }
 
@@ -103,7 +103,7 @@
      *
      * @param iname name of interface
      */
-    void addImplInterface(String iname) {
+    public void addImplInterface(String iname) {
         implInterfaces.add(iname);
     }
 
@@ -134,7 +134,7 @@
         jDiffConstructors.add(tc);
     }
 
-    static String convertModifiersToAccessLevel(int modifiers) {
+    private static String convertModifiersToAccessLevel(int modifiers) {
         if ((modifiers & Modifier.PUBLIC) != 0) {
             return "public";
         } else if ((modifiers & Modifier.PRIVATE) != 0) {
@@ -147,7 +147,7 @@
         }
     }
 
-    static String convertModifersToModifierString(int modifiers) {
+    private static String convertModifersToModifierString(int modifiers) {
         StringBuilder sb = new StringBuilder();
         String separator = "";
 
@@ -201,8 +201,8 @@
      * Represents a  field.
      */
     public static final class JDiffField extends JDiffElement {
-        String mFieldType;
-        private String mFieldValue;
+        final String mFieldType;
+        private final String mFieldValue;
 
         public JDiffField(String name, String fieldType, int modifier, String value) {
             super(name, modifier);
@@ -254,9 +254,9 @@
      * Represents a method.
      */
     public static class JDiffMethod extends JDiffElement {
-        String mReturnType;
-        ArrayList<String> mParamList;
-        ArrayList<String> mExceptionList;
+        final String mReturnType;
+        final ArrayList<String> mParamList;
+        final ArrayList<String> mExceptionList;
 
         public JDiffMethod(String name, int modifier, String returnType) {
             super(name, modifier);
@@ -369,7 +369,7 @@
          *
          * @return the return type of this method.
          */
-        protected String getReturnType() {
+        String getReturnType() {
             return mReturnType;
         }
     }
diff --git a/tests/signature/src/android/signature/cts/LogHelper.java b/tests/signature/src/android/signature/cts/LogHelper.java
new file mode 100644
index 0000000..0cde3eb
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/LogHelper.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.signature.cts;
+
+/**
+ */
+public class LogHelper {
+
+    static void loge(String message, Exception exception) {
+        System.err.println(String.format("%s: %s", message, exception));
+    }
+}
diff --git a/tests/signature/src/android/signature/cts/ReflectionHelper.java b/tests/signature/src/android/signature/cts/ReflectionHelper.java
index 54640a3..8664cd3 100644
--- a/tests/signature/src/android/signature/cts/ReflectionHelper.java
+++ b/tests/signature/src/android/signature/cts/ReflectionHelper.java
@@ -15,8 +15,11 @@
  */
 package android.signature.cts;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
 import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
@@ -24,17 +27,15 @@
 import java.lang.reflect.TypeVariable;
 import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Uses reflection to obtain runtime representations of elements in the API.
  */
 public class ReflectionHelper {
 
-    private static void loge(String message, Exception exception) {
-        System.err.println(String.format("%s: %s", message, exception));
-    }
-
     /**
      * Finds the reflected class for the class under test.
      *
@@ -42,24 +43,34 @@
      * @return the reflected class, or null if not found.
      */
     @SuppressWarnings("unchecked")
-    public static Class<?> findMatchingClass(JDiffClassDescription classDescription)
+    public static Class<?> findMatchingClass(JDiffClassDescription classDescription, ClassProvider classProvider)
             throws ClassNotFoundException {
         // even if there are no . in the string, split will return an
         // array of length 1
         String shortClassName = classDescription.getShortClassName();
         String[] classNameParts = shortClassName.split("\\.");
         String packageName = classDescription.getPackageName();
-        String currentName = packageName + "." + classNameParts[0];
+        String outermostClassName = packageName + "." + classNameParts[0];
+        int firstInnerClassNameIndex = 0;
 
-        Class<?> clz = Class.forName(
-                currentName, false, ReflectionHelper.class.getClassLoader());
-        String absoluteClassName = classDescription.getAbsoluteClassName();
+        return searchForClass(classProvider, classDescription.getAbsoluteClassName(),
+                outermostClassName, classNameParts,
+                firstInnerClassNameIndex);
+    }
+
+    private static Class<?> searchForClass(
+            ClassProvider classProvider,
+            String absoluteClassName,
+            String outermostClassName, String[] classNameParts,
+            int outerClassNameIndex) throws ClassNotFoundException {
+
+        Class<?> clz = classProvider.getClass(outermostClassName);
         if (clz.getCanonicalName().equals(absoluteClassName)) {
             return clz;
         }
 
         // Then it must be an inner class.
-        for (int x = 1; x < classNameParts.length; x++) {
+        for (int x = outerClassNameIndex + 1; x < classNameParts.length; x++) {
             clz = findInnerClassByName(clz, classNameParts[x]);
             if (clz == null) {
                 return null;
@@ -71,6 +82,27 @@
         return null;
     }
 
+    static Class<?> findMatchingClass(String absoluteClassName, ClassProvider classProvider)
+            throws ClassNotFoundException {
+
+        String[] classNameParts = absoluteClassName.split("\\.");
+        StringBuilder builder = new StringBuilder();
+        String separator = "";
+        int start;
+        for (start = 0; start < classNameParts.length; start++) {
+            String classNamePart = classNameParts[start];
+            builder.append(separator).append(classNamePart);
+            separator = ".";
+            if (Character.isUpperCase(classNamePart.charAt(0))) {
+                break;
+            }
+        }
+        String outermostClassName = builder.toString();
+
+        return searchForClass(classProvider, absoluteClassName, outermostClassName, classNameParts,
+                start);
+    }
+
     /**
      * Searches the class for the specified inner class.
      *
@@ -330,4 +362,148 @@
             throw new RuntimeException("Got an unknown java.lang.Type");
         }
     }
+
+    /**
+     * Returns a Class representing an annotation type of the given name.
+     */
+    @SuppressWarnings("unchecked")
+    public static Class<? extends Annotation> getAnnotationClass(String name) {
+        try {
+            Class<?> clazz = Class.forName(
+                    name, false, ReflectionHelper.class.getClassLoader());
+            if (clazz.isAnnotation()) {
+                return (Class<? extends Annotation>) clazz;
+            } else {
+                return null;
+            }
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a list of constructors which are annotated with the given annotation class.
+     */
+    public static Set<Constructor<?>> getAnnotatedConstructors(Class<?> clazz,
+            Class<? extends Annotation> annotation) {
+        Set<Constructor<?>> result = new HashSet<>();
+        if (annotation != null) {
+            for (Constructor<?> c : clazz.getDeclaredConstructors()) {
+                if (c.isAnnotationPresent(annotation)) {
+                    // TODO(b/71630695): currently, some API members are not annotated, because
+                    // a member is automatically added to the API set if it is in a class with
+                    // annotation and it is not @hide. <member>.getDeclaringClass().
+                    // isAnnotationPresent(annotationClass) won't help because it will then
+                    // incorrectly include non-API members which are marked as @hide;
+                    // @hide isn't visible at runtime. Until the issue is fixed, we should
+                    // omit those automatically added API members from the test.
+                    result.add(c);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list of methods which are annotated with the given annotation class.
+     */
+    public static Set<Method> getAnnotatedMethods(Class<?> clazz,
+            Class<? extends Annotation> annotation) {
+        Set<Method> result = new HashSet<>();
+        if (annotation != null) {
+            for (Method m : clazz.getDeclaredMethods()) {
+                if (m.isAnnotationPresent(annotation)) {
+                    // TODO(b/71630695): see getAnnotatedConstructors for details
+                    result.add(m);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list of fields which are annotated with the given annotation class.
+     */
+    public static Set<Field> getAnnotatedFields(Class<?> clazz,
+            Class<? extends Annotation> annotation) {
+        Set<Field> result = new HashSet<>();
+        if (annotation != null) {
+            for (Field f : clazz.getDeclaredFields()) {
+                if (f.isAnnotationPresent(annotation)) {
+                    // TODO(b/71630695): see getAnnotatedConstructors for details
+                    result.add(f);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static boolean isInAnnotatedClass(Member m,
+            Class<? extends Annotation> annotationClass) {
+        Class<?> clazz = m.getDeclaringClass();
+        do {
+            if (clazz.isAnnotationPresent(annotationClass)) {
+                return true;
+            }
+        } while ((clazz = clazz.getDeclaringClass()) != null);
+        return false;
+    }
+
+    public static boolean isAnnotatedOrInAnnotatedClass(Field field,
+            Class<? extends Annotation> annotationClass) {
+        if (annotationClass == null) {
+            return true;
+        }
+        return field.isAnnotationPresent(annotationClass)
+                || isInAnnotatedClass(field, annotationClass);
+    }
+
+    public static boolean isAnnotatedOrInAnnotatedClass(Constructor<?> constructor,
+            Class<? extends Annotation> annotationClass) {
+        if (annotationClass == null) {
+            return true;
+        }
+        return constructor.isAnnotationPresent(annotationClass)
+                || isInAnnotatedClass(constructor, annotationClass);
+    }
+
+    public static boolean isAnnotatedOrInAnnotatedClass(Method method,
+            Class<? extends Annotation> annotationClass) {
+        if (annotationClass == null) {
+            return true;
+        }
+        return method.isAnnotationPresent(annotationClass)
+                || isInAnnotatedClass(method, annotationClass);
+    }
+
+    public static boolean isOverridingAnnotatedMethod(Method method,
+            Class<? extends Annotation> annotationClass) {
+        Class<?> clazz = method.getDeclaringClass();
+        while (!(clazz = clazz.getSuperclass()).equals(Object.class)) {
+            try {
+                Method overriddenMethod;
+                overriddenMethod = clazz.getDeclaredMethod(method.getName(),
+                        method.getParameterTypes());
+                if (overriddenMethod != null) {
+                    return isAnnotatedOrInAnnotatedClass(overriddenMethod, annotationClass);
+                }
+            } catch (NoSuchMethodException e) {
+                continue;
+            } catch (SecurityException e) {
+                throw new RuntimeException(
+                        "Error while searching for overridden method. " + method.toString(), e);
+            }
+        }
+        return false;
+    }
+
+    static Class<?> findRequiredClass(JDiffClassDescription classDescription,
+            ClassProvider classProvider) {
+        try {
+            return findMatchingClass(classDescription, classProvider);
+        } catch (ClassNotFoundException e) {
+            LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
+            return null;
+        }
+    }
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/AbstractApiCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/AbstractApiCheckerTest.java
new file mode 100644
index 0000000..b8fea80
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/AbstractApiCheckerTest.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.signature.cts.tests;
+
+import android.signature.cts.AbstractApiChecker;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.ExcludingClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ResultObserver;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+/**
+ * Base class for tests of implementations of {@link AbstractApiChecker}.
+ */
+public abstract class AbstractApiCheckerTest<T extends AbstractApiChecker> extends TestCase {
+
+    static final String VALUE = "VALUE";
+
+    private static ClassProvider createClassProvider(String[] excludedRuntimeClasses) {
+        ClassProvider provider = new TestClassesProvider();
+        if (excludedRuntimeClasses.length != 0) {
+            provider = new ExcludingClassProvider(provider,
+                    name -> Arrays.stream(excludedRuntimeClasses)
+                            .anyMatch(myname -> myname.equals(name)));
+        }
+        return provider;
+    }
+
+    protected static JDiffClassDescription createClass(String name) {
+        JDiffClassDescription clz = new JDiffClassDescription(
+                "android.signature.cts.tests.data", name);
+        clz.setType(JDiffClassDescription.JDiffType.CLASS);
+        clz.setModifier(Modifier.PUBLIC);
+        return clz;
+    }
+
+    void checkSignatureCompliance(JDiffClassDescription classDescription,
+            String... excludedRuntimeClassNames) {
+        ResultObserver resultObserver = new NoFailures();
+        checkSignatureCompliance(classDescription, resultObserver,
+                excludedRuntimeClassNames);
+    }
+
+    void checkSignatureCompliance(JDiffClassDescription classDescription,
+            ResultObserver resultObserver, String... excludedRuntimeClasses) {
+        runWithApiChecker(resultObserver,
+                checker -> checker.checkSignatureCompliance(classDescription),
+                excludedRuntimeClasses);
+    }
+
+    void runWithApiChecker(
+            ResultObserver resultObserver, Consumer<T> consumer, String... excludedRuntimeClasses) {
+        ClassProvider provider = createClassProvider(excludedRuntimeClasses);
+        T checker = createChecker(resultObserver, provider);
+        consumer.accept(checker);
+        checker.checkDeferred();
+    }
+
+    protected abstract T createChecker(ResultObserver resultObserver, ClassProvider provider);
+
+    protected JDiffClassDescription createInterface(String name) {
+        JDiffClassDescription clz = new JDiffClassDescription(
+                "android.signature.cts.tests.data", name);
+        clz.setType(JDiffClassDescription.JDiffType.INTERFACE);
+        clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
+        return clz;
+    }
+
+    protected static JDiffClassDescription.JDiffMethod method(
+            String name, int modifiers, String returnType) {
+        return new JDiffClassDescription.JDiffMethod(name, modifiers, returnType);
+    }
+
+    protected static class NoFailures implements ResultObserver {
+
+        @Override
+        public void notifyFailure(FailureType type, String name, String errmsg) {
+            Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type
+                    + " error message: " + errmsg);
+        }
+    }
+
+    protected static class ExpectFailure implements ResultObserver {
+
+        private FailureType expectedType;
+
+        private boolean failureSeen;
+
+        ExpectFailure(FailureType expectedType) {
+            this.expectedType = expectedType;
+        }
+
+        @Override
+        public void notifyFailure(FailureType type, String name, String errMsg) {
+            if (type == expectedType) {
+                if (failureSeen) {
+                    Assert.fail("Saw second test failure: " + name + " failure type: " + type);
+                } else {
+                    // We've seen the error, mark it and keep going
+                    failureSeen = true;
+                }
+            } else {
+                Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
+            }
+        }
+
+        void validate() {
+            Assert.assertTrue(failureSeen);
+        }
+    }
+
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
new file mode 100644
index 0000000..ef4bebe
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.tests;
+
+import android.signature.cts.AnnotationChecker;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ResultObserver;
+import android.signature.cts.tests.data.ApiAnnotation;
+import java.lang.reflect.Modifier;
+
+/**
+ * Test class for {@link android.signature.cts.AnnotationChecker}.
+ */
+@SuppressWarnings("deprecation")
+public class AnnotationCheckerTest extends AbstractApiCheckerTest<AnnotationChecker> {
+
+    @Override
+    protected AnnotationChecker createChecker(ResultObserver resultObserver,
+            ClassProvider provider) {
+        return new AnnotationChecker(resultObserver, provider, ApiAnnotation.class.getName());
+    }
+
+    private static void addConstructor(JDiffClassDescription clz, String... paramTypes) {
+        JDiffClassDescription.JDiffConstructor constructor = new JDiffClassDescription.JDiffConstructor(
+                clz.getShortClassName(), Modifier.PUBLIC);
+        if (paramTypes != null) {
+            for (String type : paramTypes) {
+                constructor.addParam(type);
+            }
+        }
+        clz.addConstructor(constructor);
+    }
+
+    private static void addPublicVoidMethod(JDiffClassDescription clz, String name) {
+        clz.addMethod(method(name, Modifier.PUBLIC, "void"));
+    }
+
+    private static void addPublicBooleanField(JDiffClassDescription clz, String name) {
+        JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
+                name, "boolean", Modifier.PUBLIC, VALUE);
+        clz.addField(field);
+    }
+
+    /**
+     * Documented API and runtime classes are exactly matched.
+     */
+    public void testExactApiMatch() {
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+    }
+
+    /**
+     * A constructor is found in the runtime class, but not in the documented API
+     */
+    public void testDetectUnauthorizedConstructorApi() {
+        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        // (omitted) addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+        clz = createClass("PublicApiClass");
+        // (omitted) addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+    }
+
+    /**
+     * A method is found in the runtime class, but not in the documented API
+     */
+    public void testDetectUnauthorizedMethodApi() {
+        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        // (omitted) addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        // (omitted) addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+    }
+
+    /**
+     * A field is found in the runtime class, but not in the documented API
+     */
+    public void testDetectUnauthorizedFieldApi() {
+        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_FIELD);
+
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        // (omitted) addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.EXTRA_FIELD);
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        // (omitted) addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+    }
+
+    /**
+     * A class is found in the runtime classes, but not in the documented API
+     */
+    public void testDetectUnauthorizedClassApi() {
+        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CLASS);
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.PublicApiClass");
+        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.EXTRA_CLASS);
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass");
+        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        observer.validate();
+    }
+
+    /**
+     * A member which is declared in an annotated class is currently recognized as an API.
+     */
+    public void testB71630695() {
+        // TODO(b/71630695): currently, some API members are not annotated, because
+        // a member is automatically added to the API set if it is in a class with
+        // annotation and it is not @hide. This should be fixed, but until then,
+        // CTS should respect the existing behavior.
+        JDiffClassDescription clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addConstructor(clz, "float"); // this is not annotated
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+        clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addPublicVoidMethod(clz, "unannotatedApiMethod"); // this is not annotated
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+        clz = createClass("SystemApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addPublicBooleanField(clz, "unannotatedApiField"); // this is not annotated
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.PublicApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+    }
+
+    /**
+     * An API is documented, but isn't annotated in the runtime class. But, due to b/71630695, this
+     * test can only be done for public API classes.
+     */
+    public void testDetectMissingAnnotation() {
+        ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+        JDiffClassDescription clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addConstructor(clz, "int"); // this is not annotated
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addPublicVoidMethod(clz, "privateMethod"); // this is not annotated
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+
+        observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+        clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addPublicBooleanField(clz, "privateField"); // this is not annotated
+
+        checkSignatureCompliance(clz, observer,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        observer.validate();
+    }
+
+    /**
+     * A <code>@hide</code> method should be recognized as API though it is not annotated, if it is
+     * overriding a method which is already an API.
+     */
+    public void testOverriddenHidenMethodIsApi() {
+        JDiffClassDescription clz = createClass("PublicApiClass");
+        addConstructor(clz);
+        addPublicVoidMethod(clz, "apiMethod");
+        addPublicBooleanField(clz, "apiField");
+        addPublicVoidMethod(clz, "anOverriddenMethod"); // not annotated and @hide, but is API
+
+        checkSignatureCompliance(clz,
+                "android.signature.cts.tests.data.SystemApiClass",
+                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+    }
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
index 71d2e37..7942298 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
@@ -17,82 +17,29 @@
 package android.signature.cts.tests;
 
 import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ClassProvider;
 import android.signature.cts.FailureType;
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ResultObserver;
-
-import junit.framework.Assert;
-import junit.framework.TestCase;
-
+import android.signature.cts.tests.data.ExtendedNormalInterface;
+import android.signature.cts.tests.data.NormalClass;
+import android.signature.cts.tests.data.NormalInterface;
 import java.lang.reflect.Modifier;
 
 /**
  * Test class for JDiffClassDescription.
  */
-public class ApiComplianceCheckerTest extends TestCase {
-
-    private static final String VALUE = "VALUE";
-
-    private class NoFailures implements ResultObserver {
-        @Override
-        public void notifyFailure(FailureType type, String name, String errmsg) {
-            Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
-        }
-    }
-
-    private class ExpectFailure implements ResultObserver {
-        private FailureType expectedType;
-        private boolean failureSeen;
-
-        ExpectFailure(FailureType expectedType) {
-            this.expectedType = expectedType;
-        }
-
-        @Override
-        public void notifyFailure(FailureType type, String name, String errMsg) {
-            if (type == expectedType) {
-                if (failureSeen) {
-                    Assert.fail("Saw second test failure: " + name + " failure type: " + type);
-                } else {
-                    // We've seen the error, mark it and keep going
-                    failureSeen = true;
-                }
-            } else {
-                Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
-            }
-        }
-
-        void validate() {
-            Assert.assertTrue(failureSeen);
-        }
-    }
-
-    private void checkSignatureCompliance(JDiffClassDescription classDescription) {
-        ResultObserver resultObserver = new NoFailures();
-        checkSignatureCompliance(classDescription, resultObserver);
-    }
-
-    private void checkSignatureCompliance(JDiffClassDescription classDescription,
-            ResultObserver resultObserver) {
-        ApiComplianceChecker complianceChecker = new ApiComplianceChecker(resultObserver);
-        complianceChecker.checkSignatureCompliance(classDescription);
-    }
-
-    /**
-     * Create the JDiffClassDescription for "NormalClass".
-     *
-     * @return the new JDiffClassDescription
-     */
-    private JDiffClassDescription createNormalClass() {
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NormalClass");
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        clz.setModifier(Modifier.PUBLIC);
-        return clz;
+@SuppressWarnings("deprecation")
+public class ApiComplianceCheckerTest extends AbstractApiCheckerTest<ApiComplianceChecker> {
+    
+    @Override
+    protected ApiComplianceChecker createChecker(ResultObserver resultObserver,
+            ClassProvider provider) {
+        return new ApiComplianceChecker(resultObserver, provider);
     }
 
     public void testNormalClassCompliance() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         checkSignatureCompliance(clz);
         assertEquals(clz.toSignatureString(), "public class NormalClass");
     }
@@ -107,7 +54,7 @@
     }
 
     public void testSimpleConstructor() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffConstructor constructor =
                 new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PUBLIC);
         clz.addConstructor(constructor);
@@ -116,7 +63,7 @@
     }
 
     public void testOneArgConstructor() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffConstructor constructor =
                 new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PRIVATE);
         constructor.addParam("java.lang.String");
@@ -126,7 +73,7 @@
     }
 
     public void testConstructorThrowsException() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffConstructor constructor =
                 new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PROTECTED);
         constructor.addParam("java.lang.String");
@@ -136,11 +83,11 @@
         checkSignatureCompliance(clz);
         assertEquals(constructor.toSignatureString(),
                 "protected NormalClass(java.lang.String, java.lang.String) " +
-                "throws android.signature.cts.tests.data.NormalException");
+                        "throws android.signature.cts.tests.data.NormalException");
     }
 
     public void testPackageProtectedConstructor() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffConstructor constructor =
                 new JDiffClassDescription.JDiffConstructor("NormalClass", 0);
         constructor.addParam("java.lang.String");
@@ -153,54 +100,52 @@
     }
 
     public void testStaticMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "staticMethod", Modifier.STATIC | Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("staticMethod",
+                Modifier.STATIC | Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "public static void staticMethod()");
     }
 
     public void testSyncMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "syncMethod", Modifier.SYNCHRONIZED | Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("syncMethod",
+                Modifier.SYNCHRONIZED | Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "public synchronized void syncMethod()");
     }
 
     public void testPackageProtectMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "packageProtectedMethod", 0, "boolean");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("packageProtectedMethod", 0, "boolean");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "boolean packageProtectedMethod()");
     }
 
     public void testPrivateMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "privateMethod", Modifier.PRIVATE, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("privateMethod", Modifier.PRIVATE,
+                "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "private void privateMethod()");
     }
 
     public void testProtectedMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "protectedMethod", Modifier.PROTECTED, "java.lang.String");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("protectedMethod", Modifier.PROTECTED,
+                "java.lang.String");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "protected java.lang.String protectedMethod()");
     }
 
     public void testThrowsMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "throwsMethod", Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("throwsMethod", Modifier.PUBLIC, "void");
         method.addException("android.signature.cts.tests.data.NormalException");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
@@ -209,16 +154,16 @@
     }
 
     public void testNativeMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "nativeMethod", Modifier.PUBLIC | Modifier.NATIVE, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("nativeMethod",
+                Modifier.PUBLIC | Modifier.NATIVE, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "public native void nativeMethod()");
     }
 
     public void testFinalField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "FINAL_FIELD", "java.lang.String", Modifier.PUBLIC | Modifier.FINAL, VALUE);
         clz.addField(field);
@@ -227,7 +172,7 @@
     }
 
     public void testStaticField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "STATIC_FIELD", "java.lang.String", Modifier.PUBLIC | Modifier.STATIC, VALUE);
         clz.addField(field);
@@ -236,7 +181,7 @@
     }
 
     public void testVolatileFiled() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "VOLATILE_FIELD", "java.lang.String", Modifier.PUBLIC | Modifier.VOLATILE, VALUE);
         clz.addField(field);
@@ -245,7 +190,7 @@
     }
 
     public void testTransientField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "TRANSIENT_FIELD", "java.lang.String",
                 Modifier.PUBLIC | Modifier.TRANSIENT, VALUE);
@@ -256,7 +201,7 @@
     }
 
     public void testPackageField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "PACAKGE_FIELD", "java.lang.String", 0, VALUE);
         clz.addField(field);
@@ -265,7 +210,7 @@
     }
 
     public void testPrivateField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "PRIVATE_FIELD", "java.lang.String", Modifier.PRIVATE, VALUE);
         clz.addField(field);
@@ -274,7 +219,7 @@
     }
 
     public void testProtectedField() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "PROTECTED_FIELD", "java.lang.String", Modifier.PROTECTED, VALUE);
         clz.addField(field);
@@ -283,10 +228,10 @@
     }
 
     public void testFieldValue() {
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "VALUE_FIELD", "java.lang.String",
-                Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC , "\"\\u2708\"");
+                Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC, "\"\\u2708\"");
         clz.addField(field);
         checkSignatureCompliance(clz);
         assertEquals(field.toSignatureString(),
@@ -295,10 +240,10 @@
 
     public void testFieldValueChanged() {
         ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD);
-        JDiffClassDescription clz = createNormalClass();
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "VALUE_FIELD", "java.lang.String",
-                Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC , "\"&#9992;\"");
+                Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC, "\"&#9992;\"");
         clz.addField(field);
         checkSignatureCompliance(clz, observer);
         assertEquals(field.toSignatureString(),
@@ -307,10 +252,7 @@
     }
 
     public void testInnerClass() {
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NormalClass.InnerClass");
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        clz.setModifier(Modifier.PUBLIC);
+        JDiffClassDescription clz = createClass("NormalClass.InnerClass");
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "innerClassData", "java.lang.String", Modifier.PRIVATE, VALUE);
         clz.addField(field);
@@ -319,11 +261,8 @@
     }
 
     public void testInnerInnerClass() {
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NormalClass.InnerClass.InnerInnerClass"
-        );
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        clz.setModifier(Modifier.PUBLIC);
+        JDiffClassDescription clz = createClass(
+                "NormalClass.InnerClass.InnerInnerClass");
         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
                 "innerInnerClassData", "java.lang.String", Modifier.PRIVATE, VALUE);
         clz.addField(field);
@@ -338,20 +277,15 @@
         clz.setType(JDiffClassDescription.JDiffType.INTERFACE);
         clz.setModifier(Modifier.PUBLIC | Modifier.STATIC | Modifier.ABSTRACT);
         clz.addMethod(
-                new JDiffClassDescription.JDiffMethod("doSomething",
-                    Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                method("doSomething", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
         checkSignatureCompliance(clz);
         assertEquals(clz.toSignatureString(), "public interface NormalClass.InnerInterface");
     }
 
     public void testInterface() {
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NormalInterface");
-        clz.setType(JDiffClassDescription.JDiffType.INTERFACE);
-        clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
+        JDiffClassDescription clz = createInterface("NormalInterface");
         clz.addMethod(
-                new JDiffClassDescription.JDiffMethod("doSomething",
-                    Modifier.ABSTRACT| Modifier.PUBLIC, "void"));
+                method("doSomething", Modifier.ABSTRACT | Modifier.PUBLIC, "void"));
         checkSignatureCompliance(clz);
         assertEquals(clz.toSignatureString(), "public interface NormalInterface");
     }
@@ -371,9 +305,8 @@
      */
     public void testAddingSync() {
         ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD);
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "syncMethod", Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("syncMethod", Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz, observer);
         observer.validate();
@@ -384,9 +317,9 @@
      * actually is not.
      */
     public void testRemovingSync() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "notSyncMethod", Modifier.SYNCHRONIZED | Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("notSyncMethod",
+                Modifier.SYNCHRONIZED | Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
     }
@@ -395,9 +328,8 @@
      * API says method is not native, but it actually is. http://b/1839558
      */
     public void testAddingNative() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "nativeMethod", Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("nativeMethod", Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
     }
@@ -406,9 +338,9 @@
      * API says method is native, but actually isn't. http://b/1839558
      */
     public void testRemovingNative() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "notNativeMethod", Modifier.NATIVE | Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("notNativeMethod",
+                Modifier.NATIVE | Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
     }
@@ -438,18 +370,15 @@
      */
     public void testAddingAbstractToAClass() {
         ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS);
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "AbstractClass");
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        clz.setModifier(Modifier.PUBLIC);
+        JDiffClassDescription clz = createClass("AbstractClass");
         checkSignatureCompliance(clz, observer);
         observer.validate();
     }
 
     public void testFinalMethod() {
-        JDiffClassDescription clz = createNormalClass();
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "finalMethod", Modifier.PUBLIC | Modifier.FINAL, "void");
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("finalMethod",
+                Modifier.PUBLIC | Modifier.FINAL, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
         assertEquals(method.toSignatureString(), "public final void finalMethod()");
@@ -464,8 +393,7 @@
                 "android.signature.cts.tests.data", "FinalClass");
         clz.setType(JDiffClassDescription.JDiffType.CLASS);
         clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "finalMethod", Modifier.PUBLIC, "void");
+        JDiffClassDescription.JDiffMethod method = method("finalMethod", Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
     }
@@ -479,8 +407,8 @@
                 "android.signature.cts.tests.data", "FinalClass");
         clz.setType(JDiffClassDescription.JDiffType.CLASS);
         clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "nonFinalMethod", Modifier.PUBLIC | Modifier.FINAL, "void");
+        JDiffClassDescription.JDiffMethod method = method("nonFinalMethod",
+                Modifier.PUBLIC | Modifier.FINAL, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz);
     }
@@ -491,14 +419,25 @@
      */
     public void testAddingFinalToAMethodInANonFinalClass() {
         ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD);
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NormalClass");
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        clz.setModifier(Modifier.PUBLIC);
-        JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
-                "finalMethod", Modifier.PUBLIC, "void");
+        JDiffClassDescription clz = createClass("NormalClass");
+        JDiffClassDescription.JDiffMethod method = method("finalMethod", Modifier.PUBLIC, "void");
         clz.addMethod(method);
         checkSignatureCompliance(clz, observer);
         observer.validate();
     }
-}
+
+    public void testExtendedNormalInterface() {
+        NoFailures observer = new NoFailures();
+        runWithApiChecker(observer, checker -> {
+            JDiffClassDescription iface = createInterface(NormalInterface.class.getSimpleName());
+            iface.addMethod(method("doSomething", Modifier.PUBLIC, "void"));
+            checker.addBaseClass(iface);
+
+            JDiffClassDescription clz =
+                    createInterface(ExtendedNormalInterface.class.getSimpleName());
+            clz.addMethod(method("doSomethingElse", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+            clz.addImplInterface(iface.getAbsoluteClassName());
+            checker.checkSignatureCompliance(clz);
+        });
+    }
+}
\ No newline at end of file
diff --git a/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.java b/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.java
new file mode 100644
index 0000000..80176de
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.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.signature.cts.tests;
+
+import java.util.stream.Stream;
+
+import android.signature.cts.ClassProvider;
+import android.signature.cts.tests.data.AbstractClass;
+import android.signature.cts.tests.data.SystemApiClass;
+import android.signature.cts.tests.data.FinalClass;
+import android.signature.cts.tests.data.PrivateClass;
+import android.signature.cts.tests.data.PublicApiClass;
+import android.signature.cts.tests.data.ForciblyPublicizedPrivateClass;
+import android.signature.cts.tests.data.NormalClass;
+import android.signature.cts.tests.data.NormalException;
+import android.signature.cts.tests.data.NormalInterface;
+import android.signature.cts.tests.data.ApiAnnotation;
+
+public class TestClassesProvider extends ClassProvider {
+    @Override
+    public Stream<Class<?>> getAllClasses() {
+        Stream.Builder<Class<?>> builder = Stream.builder();
+        builder.add(AbstractClass.class);
+        builder.add(FinalClass.class);
+        builder.add(NormalClass.class);
+        builder.add(NormalException.class);
+        builder.add(NormalInterface.class);
+        builder.add(ApiAnnotation.class);
+        builder.add(PublicApiClass.class);
+        builder.add(SystemApiClass.class);
+        builder.add(PrivateClass.class);
+        builder.add(ForciblyPublicizedPrivateClass.class);
+        return builder.build();
+    }
+
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.java b/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.java
new file mode 100644
index 0000000..17a49b2
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.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.signature.cts.tests.data;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({
+    TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE
+})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiAnnotation {
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ExtendedNormalInterface.java b/tests/signature/tests/src/android/signature/cts/tests/data/ExtendedNormalInterface.java
new file mode 100644
index 0000000..b677f93
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ExtendedNormalInterface.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.tests.data;
+
+/**
+ */
+public interface ExtendedNormalInterface extends NormalInterface {
+
+    @Override
+    void doSomething();
+
+    void doSomethingElse();
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.java
new file mode 100644
index 0000000..1533544
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.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.signature.cts.tests.data;
+
+/**
+ * This mocks a private class, but someone has forcibly added the annotation to make it as an API.
+ */
+@ApiAnnotation
+public class ForciblyPublicizedPrivateClass {
+
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java
new file mode 100644
index 0000000..8f0884c
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.tests.data;
+
+/**
+ * This mocks an internal private class
+ */
+public class PrivateClass {
+    public void privateMethod() {
+    }
+
+    public boolean privateField;
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.java
new file mode 100644
index 0000000..e27d070
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.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 android.signature.cts.tests.data;
+
+/**
+ * A public API class, with some members are marked as system API
+ */
+public class PublicApiClass extends PublicApiClassParent {
+    public PublicApiClass(boolean foo) {
+    }
+
+    public void publicApiMethod() {
+    }
+
+    public boolean publicApiField;
+
+    /** @hide */
+    @ApiAnnotation
+    public PublicApiClass() {
+    }
+
+    /** @hide */
+    @ApiAnnotation
+    public void apiMethod() {
+    }
+
+    /** @hide */
+    @ApiAnnotation
+    public boolean apiField;
+
+    /** @hide */
+    public PublicApiClass(int foo) {
+    }
+
+    /** @hide */
+    public void privateMethod() {
+    }
+
+    /** @hide */
+    public boolean privateField;
+
+    /** @hide */
+    @Override
+    public void anOverriddenMethod() {
+        // This is @hide but should be recognized as an API because the exact same method is defined
+        // as API in one of the ancestor classes.
+    }
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.java b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.java
new file mode 100644
index 0000000..7e29d03
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.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.signature.cts.tests.data;
+
+/**
+ * Parent class of PublicApiClass.
+ */
+public abstract class PublicApiClassParent {
+    public void publicMethod() {
+    }
+
+    /** @hide */
+    @ApiAnnotation
+    public abstract void anOverriddenMethod();
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.java
new file mode 100644
index 0000000..2636d4d
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.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 android.signature.cts.tests.data;
+
+/**
+ * Not a public API, but a system API.
+ *
+ * @hide
+ */
+@ApiAnnotation
+public class SystemApiClass {
+    @ApiAnnotation
+    public SystemApiClass() {
+    }
+
+    @ApiAnnotation
+    public void apiMethod() {
+    }
+
+    @ApiAnnotation
+    public boolean apiField;
+
+    // TODO(b/71630695) this shouldn't be recognized
+    // as an API, as it is not annotated.
+    public SystemApiClass(float foo) {
+
+    }
+
+    // TODO(b/71630695) this shouldn't be recognized
+    // as an API, as it is not annotated.
+    public void unannotatedApiMethod() {
+    }
+
+    // TODO(b/71630695) this shouldn't be recognized
+    // as an API, as it is not annotated.
+    public boolean unannotatedApiField;
+
+    /** @hide */
+    public SystemApiClass(boolean foo) {
+    }
+
+    /** @hide */
+    public void privateMethod() {
+    }
+
+    /** @hide */
+    public boolean privateField;
+}
diff --git a/tests/simplecpu/AndroidTest.xml b/tests/simplecpu/AndroidTest.xml
index 9192e8b..916da50 100644
--- a/tests/simplecpu/AndroidTest.xml
+++ b/tests/simplecpu/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CPU test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/accounts/Android.mk b/tests/tests/accounts/Android.mk
index f5ac4fa..18ab37f 100644
--- a/tests/tests/accounts/Android.mk
+++ b/tests/tests/accounts/Android.mk
@@ -22,9 +22,9 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    CtsAccountTestsCommon ctstestrunner legacy-android-test
+    CtsAccountTestsCommon ctstestrunner
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/accounts/AndroidTest.xml b/tests/tests/accounts/AndroidTest.xml
index b6ad758..77118d7 100644
--- a/tests/tests/accounts/AndroidTest.xml
+++ b/tests/tests/accounts/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Accounts 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" />
diff --git a/tests/tests/accounts/src/android/accounts/cts/AccountTest.java b/tests/tests/accounts/src/android/accounts/cts/AccountTest.java
index 64a1415..bd3fcba 100644
--- a/tests/tests/accounts/src/android/accounts/cts/AccountTest.java
+++ b/tests/tests/accounts/src/android/accounts/cts/AccountTest.java
@@ -44,6 +44,8 @@
         // and verify it is equivalent to the original account.
         Account newAccount = new Account(parcel);
         assertEquals(account, newAccount);
+
+        parcel.recycle();
     }
 
 }
diff --git a/tests/tests/alarmclock/Android.mk b/tests/tests/alarmclock/Android.mk
index adfd51f..5d5c4e4 100644
--- a/tests/tests/alarmclock/Android.mk
+++ b/tests/tests/alarmclock/Android.mk
@@ -23,6 +23,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsAlarmClockTestCases
diff --git a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
index d469592..8f04d2a 100644
--- a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
+++ b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
@@ -24,8 +24,10 @@
 
     public enum TestcaseType {
         DISMISS_ALARM,
+        DISMISS_TIMER,
         SET_ALARM,
         SET_ALARM_FOR_DISMISSAL,
+        SET_TIMER_FOR_DISMISSAL,
         SNOOZE_ALARM,
     }
     public static final String TESTCASE_TYPE = "Testcase_type";
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
index ede762a..99c0b6f 100644
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
@@ -78,12 +78,21 @@
                         AlarmClock.ALARM_SEARCH_MODE_NEXT);
                 break;
 
+            case DISMISS_TIMER:
+                intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
+                break;
+
             case SET_ALARM_FOR_DISMISSAL:
             case SET_ALARM:
                 intent = new Intent(AlarmClock.ACTION_SET_ALARM);
                 intent.putExtra(AlarmClock.EXTRA_HOUR, 14);
                 break;
 
+            case SET_TIMER_FOR_DISMISSAL:
+                intent = new Intent(AlarmClock.ACTION_SET_TIMER);
+                intent.putExtra(AlarmClock.EXTRA_LENGTH, 1);
+                break;
+
             case SNOOZE_ALARM:
                 intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
                 break;
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
index 224c8ab..69f19b9 100644
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
@@ -89,11 +89,19 @@
               intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
               break;
 
+          case DISMISS_TIMER:
+              intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
+              break;
+
           case SET_ALARM:
           case SET_ALARM_FOR_DISMISSAL:
               intent = new Intent(AlarmClock.ACTION_SET_ALARM);
               break;
 
+          case SET_TIMER_FOR_DISMISSAL:
+              intent = new Intent(AlarmClock.ACTION_SET_TIMER);
+              break;
+
           case SNOOZE_ALARM:
               intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
               break;
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
new file mode 100644
index 0000000..5951e65
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
@@ -0,0 +1,38 @@
+package android.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.content.Context;
+import android.media.AudioManager;
+
+public class DismissTimerTest extends AlarmClockTestBase {
+
+    private int mSavedVolume;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // The timer may ring between expiration and dismissal; silence this.
+        final AudioManager audioManager = (AudioManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        mSavedVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+        audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        final AudioManager audioManager = (AudioManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        audioManager.setStreamVolume(AudioManager.STREAM_ALARM, mSavedVolume, 0);
+    }
+
+    public void testAll() throws Exception {
+        assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.SET_TIMER_FOR_DISMISSAL));
+        try {
+            Thread.sleep(1500);
+        } catch (InterruptedException ignored) {
+        }
+        assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.DISMISS_TIMER));
+    }
+}
diff --git a/tests/tests/animation/Android.mk b/tests/tests/animation/Android.mk
index 3261563..4bb9628 100644
--- a/tests/tests/animation/Android.mk
+++ b/tests/tests/animation/Android.mk
@@ -17,7 +17,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsAnimationTestCases
-LOCAL_SDK_VERSION := test_current
 
 # Don't include this package in any target.
 LOCAL_MODULE_TAGS := optional
@@ -40,4 +39,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+# Enforce public / test api only
+LOCAL_SDK_VERSION := test_current
+
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/animation/src/android/animation/cts/InterpolatorTest.java b/tests/tests/animation/src/android/animation/cts/InterpolatorTest.java
new file mode 100644
index 0000000..bcb9bec
--- /dev/null
+++ b/tests/tests/animation/src/android/animation/cts/InterpolatorTest.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.animation.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InterpolatorTest {
+    private static final float EPSILON = 0.00001f;
+    private AnimationActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AnimationActivity> mActivityRule =
+            new ActivityTestRule<>(AnimationActivity.class);
+
+    @Before
+    public void setup() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testFastOutExtraSlowIn() {
+        Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
+                android.R.interpolator.fast_out_extra_slow_in);
+        float turningPointX = 0.166666f;
+        float turningPointY = 0.4f;
+
+        // Test that the interpolator starts at (0, 0) and ends at (1, 1)
+        assertEquals(0f, interpolator.getInterpolation(0), EPSILON);
+        assertEquals(1f, interpolator.getInterpolation(1), EPSILON);
+
+        // Test that when duration is at 1/6 of the total duration, the interpolation is 0.4, per
+        // interpolator spec.
+        assertEquals(turningPointY, interpolator.getInterpolation(turningPointX), EPSILON);
+
+        // Test that the first segment of the curve (i.e. fraction in (0, 0.166666)) is below the
+        // line formed with point (0, 0) and (0.166666, 0.4)
+        for (float fraction = EPSILON; fraction < turningPointX; fraction += 0.05f) {
+            assertTrue(interpolator.getInterpolation(fraction)
+                    < fraction / turningPointX * turningPointY);
+        }
+
+        // Test that the second segment of the curve (i.e. fraction in (0.166666, 1)) is above
+        // the line formed with point (0.166666, 0.4) and (1, 1)
+        for (float fraction = turningPointX + EPSILON; fraction < 1f; fraction += 0.05f) {
+            float value = interpolator.getInterpolation(fraction);
+            assertTrue((value - turningPointY) / (fraction - turningPointX)
+                    > (1f - turningPointY) / (1f - turningPointX));
+        }
+        // The curve needs to pass the turning point.
+        assertEquals(turningPointY, interpolator.getInterpolation(turningPointX), EPSILON);
+    }
+
+}
diff --git a/tests/tests/app.usage/Android.mk b/tests/tests/app.usage/Android.mk
index 7056a0a..3da4dd0 100644
--- a/tests/tests/app.usage/Android.mk
+++ b/tests/tests/app.usage/Android.mk
@@ -29,9 +29,10 @@
     android-support-test \
     ctstestrunner \
     junit \
-    legacy-android-test \
     ub-uiautomator
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 # Tag this module as a cts test artifact
diff --git a/tests/tests/app.usage/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 462736a..e02e19d 100644
--- a/tests/tests/app.usage/AndroidManifest.xml
+++ b/tests/tests/app.usage/AndroidManifest.xml
@@ -26,7 +26,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
-    <application>
+    <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
 
         <activity android:name=".Activities$ActivityOne" />
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 8868c17..cd48c16 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for app.usage 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" />
diff --git a/tests/tests/app.usage/res/drawable/ic_notification.png b/tests/tests/app.usage/res/drawable/ic_notification.png
new file mode 100644
index 0000000..6ae570b
--- /dev/null
+++ b/tests/tests/app.usage/res/drawable/ic_notification.png
Binary files differ
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 ddc59b3..44beedb 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
@@ -16,24 +16,36 @@
 
 package android.app.usage.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import android.app.Activity;
 import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.SystemClock;
+import android.provider.Settings;
+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.test.InstrumentationTestCase;
 import android.util.SparseLongArray;
 
-import junit.framework.AssertionFailedError;
-
+import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -55,7 +67,8 @@
  *   along with the new time.
  * - Proper eviction of old data.
  */
-public class UsageStatsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class UsageStatsTest {
     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
             AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
 
@@ -66,43 +79,39 @@
     private static final long MONTH = 30 * DAY;
     private static final long YEAR = 365 * DAY;
     private static final long TIME_DIFF_THRESHOLD = 200;
+    private static final String CHANNEL_ID = "my_channel";
+
 
     private UiDevice mUiDevice;
     private UsageStatsManager mUsageStatsManager;
     private String mTargetPackage;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mUiDevice = UiDevice.getInstance(getInstrumentation());
-        mUsageStatsManager = (UsageStatsManager) getInstrumentation().getContext()
-                .getSystemService(Context.USAGE_STATS_SERVICE);
-        mTargetPackage = getInstrumentation().getContext().getPackageName();
+    @Before
+    public void setUp() throws Exception {
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUsageStatsManager = (UsageStatsManager) InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(Context.USAGE_STATS_SERVICE);
+        mTargetPackage = InstrumentationRegistry.getContext().getPackageName();
 
         setAppOpsMode("allow");
     }
 
     private static void assertLessThan(long left, long right) {
-        if (left >= right) {
-            throw new AssertionFailedError("Expected " + left + " to be less than " + right);
-        }
+        assertTrue("Expected " + left + " to be less than " + right, left < right);
     }
 
     private static void assertLessThanOrEqual(long left, long right) {
-        if (left > right) {
-            throw new AssertionFailedError("Expected " + left + " to be less than or equal to "
-                    + right);
-        }
+        assertTrue("Expected " + left + " to be less than " + right, left <= right);
     }
 
     private void setAppOpsMode(String mode) throws Exception {
         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND,
-                getInstrumentation().getContext().getPackageName(), mode);
+                InstrumentationRegistry.getContext().getPackageName(), mode);
         mUiDevice.executeShellCommand(command);
     }
 
     private void launchSubActivity(Class<? extends Activity> clazz) {
-        final Context context = getInstrumentation().getContext();
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClassName(mTargetPackage, clazz.getName());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -116,6 +125,7 @@
         }
     }
 
+    @Test
     public void testOrderedActivityLaunchSequenceInEventLog() throws Exception {
         @SuppressWarnings("unchecked")
         Class<? extends Activity>[] activitySequence = new Class[] {
@@ -178,12 +188,45 @@
         }
     }
 
+    @Test
+    public void testStandbyBucketChangeLog() throws Exception {
+        final long startTime = System.currentTimeMillis();
+        mUiDevice.executeShellCommand("am set-standby-bucket " + mTargetPackage + " rare");
+
+        final long endTime = System.currentTimeMillis();
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
+
+        boolean found = false;
+        // Check all the events.
+        ArrayList<UsageEvents.Event> eventList = new ArrayList<>();
+        while (events.hasNextEvent()) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            assertTrue(events.getNextEvent(event));
+            if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
+                found |= event.getStandbyBucket() == UsageStatsManager.STANDBY_BUCKET_RARE;
+            }
+        }
+
+        assertTrue(found);
+    }
+
+    @Test
+    public void testGetAppStandbyBuckets() throws Exception {
+        mUiDevice.executeShellCommand("am set-standby-bucket " + mTargetPackage + " rare");
+        Map<String, Integer> bucketMap = mUsageStatsManager.getAppStandbyBuckets();
+        assertTrue("No bucket data returned", bucketMap.size() > 0);
+        final int bucket = bucketMap.getOrDefault(mTargetPackage, -1);
+        assertEquals("Incorrect bucket returned for " + mTargetPackage, bucket,
+                UsageStatsManager.STANDBY_BUCKET_RARE);
+    }
+
     /**
      * We can't run this test because we are unable to change the system time.
      * It would be nice to add a shell command or other to allow the shell user
      * to set the time, thereby allowing this test to set the time using the UIAutomator.
      */
     @Ignore
+    @Test
     public void ignore_testStatsAreShiftedInTimeWhenSystemTimeChanges() throws Exception {
         launchSubActivity(Activities.ActivityOne.class);
         launchSubActivity(Activities.ActivityThree.class);
@@ -219,6 +262,7 @@
         }
     }
 
+    @Test
     public void testUsageEventsParceling() throws Exception {
         final long startTime = System.currentTimeMillis() - MINUTE;
 
@@ -257,6 +301,7 @@
         assertEquals(events.hasNextEvent(), reparceledEvents.hasNextEvent());
     }
 
+    @Test
     public void testPackageUsageStatsIntervals() throws Exception {
         final long beforeTime = System.currentTimeMillis();
 
@@ -297,11 +342,12 @@
         }
     }
 
+    @Test
     public void testNoAccessSilentlyFails() throws Exception {
         final long startTime = System.currentTimeMillis() - MINUTE;
 
-        launchSubActivity(Activities.ActivityOne.class);
-        launchSubActivity(Activities.ActivityThree.class);
+        launchSubActivity(android.app.usage.cts.Activities.ActivityOne.class);
+        launchSubActivity(android.app.usage.cts.Activities.ActivityThree.class);
 
         final long endTime = System.currentTimeMillis();
         List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
@@ -316,4 +362,57 @@
                 startTime, endTime);
         assertTrue(stats.isEmpty());
     }
+
+    @Test
+    public void testNotificationSeen() throws Exception {
+        final long startTime = System.currentTimeMillis();
+        Context context = InstrumentationRegistry.getContext();
+        NotificationManager mNotificationManager =
+            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        int importance = NotificationManager.IMPORTANCE_DEFAULT;
+        NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, "Channel",
+            importance);
+        // Configure the notification channel.
+        mChannel.setDescription("Test channel");
+        mNotificationManager.createNotificationChannel(mChannel);
+        Notification.Builder mBuilder =
+                new Notification.Builder(context, CHANNEL_ID)
+                    .setSmallIcon(R.drawable.ic_notification)
+                    .setContentTitle("My notification")
+                    .setContentText("Hello World!");
+        PendingIntent pi = PendingIntent.getActivity(context, 1,
+                new Intent(Settings.ACTION_SETTINGS), 0);
+        mBuilder.setContentIntent(pi);
+        mNotificationManager.notify(1, mBuilder.build());
+        Thread.sleep(500);
+        long endTime = System.currentTimeMillis();
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
+        boolean found = false;
+        Event event = new Event();
+        while (events.hasNextEvent()) {
+            events.getNextEvent(event);
+            if (event.mEventType == Event.NOTIFICATION_SEEN) {
+                found = true;
+            }
+        }
+        assertFalse(found);
+        // Pull down shade
+        mUiDevice.openNotification();
+        outer:
+        for (int i = 0; i < 5; i++) {
+            Thread.sleep(500);
+            endTime = System.currentTimeMillis();
+            events = mUsageStatsManager.queryEvents(startTime, endTime);
+            found = false;
+            while (events.hasNextEvent()) {
+                events.getNextEvent(event);
+                if (event.mEventType == Event.NOTIFICATION_SEEN) {
+                    found = true;
+                    break outer;
+                }
+            }
+        }
+        assertTrue(found);
+        mUiDevice.pressBack();
+    }
 }
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index 0fbb2d6..a77c038 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -25,13 +25,12 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     android-support-test \
-    junit \
-    legacy-android-test
+    junit
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/app/AndroidTest.xml b/tests/tests/app/AndroidTest.xml
index c9ee968..adebf03 100644
--- a/tests/tests/app/AndroidTest.xml
+++ b/tests/tests/app/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for app Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/appwidget/Android.mk b/tests/tests/appwidget/Android.mk
index 4164e57..b6243fc 100644
--- a/tests/tests/appwidget/Android.mk
+++ b/tests/tests/appwidget/Android.mk
@@ -29,8 +29,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     mockito-target-minus-junit4 \
     ctstestrunner \
-    junit \
-    legacy-android-test
+    junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/appwidget/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
index 6f7d053..e7e7944 100644
--- a/tests/tests/appwidget/AndroidManifest.xml
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -38,6 +38,30 @@
               android:resource="@xml/second_appwidget_info" />
       </receiver>
 
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/appwidget_info_with_feature1" />
+      </receiver>
+
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/appwidget_info_with_feature2" />
+      </receiver>
+
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/appwidget_info_with_feature3" />
+      </receiver>
+
       <service android:name="android.appwidget.cts.service.MyAppWidgetService"
           android:permission="android.permission.BIND_REMOTEVIEWS">
       </service>
diff --git a/tests/tests/appwidget/AndroidTest.xml b/tests/tests/appwidget/AndroidTest.xml
index f1d2df2..cc4fe42 100644
--- a/tests/tests/appwidget/AndroidTest.xml
+++ b/tests/tests/appwidget/AndroidTest.xml
@@ -13,6 +13,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -21,6 +22,26 @@
         <option name="test-file-name" value="CtsAppWidgetLauncher3.apk" />
         <option name="test-file-name" value="CtsAppWidgetTestCases.apk" />
     </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/widgetprovider/" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts/widgetprovider"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsAppWidgetProvider1.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider1.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsAppWidgetProvider2.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider2.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsAppWidgetProvider3.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider3.apk" />
+    </target_preparer>
+
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.appwidget.cts" />
         <option name="runtime-hint" value="9m30s" />
diff --git a/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java b/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
index 954a81b..786b2eb 100644
--- a/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
+++ b/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
@@ -27,4 +27,6 @@
     public static final String EXTRA_PACKAGE = "PACKAGE_NAME";
     public static final String EXTRA_REQUEST = "REQUEST";
 
+    public static final String ACTION_APPLY_OVERRIDE =
+            "android.appwidget.cts.widgetprovider.APPLY_OVERRIDE";
 }
diff --git a/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java b/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java
new file mode 100644
index 0000000..3c29746
--- /dev/null
+++ b/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java
@@ -0,0 +1,25 @@
+package android.appwidget.cts.packages;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.cts.common.Constants;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+public class SimpleProvider extends AppWidgetProvider {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        super.onReceive(context, intent);
+
+        if (Constants.ACTION_APPLY_OVERRIDE.equals(intent.getAction())) {
+            String request = intent.getStringExtra(Constants.EXTRA_REQUEST);
+            AppWidgetManager.getInstance(context).updateAppWidgetProviderInfo(
+                    new ComponentName(context, SimpleProvider.class),
+                    request);
+            setResultCode(Activity.RESULT_OK);
+        }
+    }
+}
diff --git a/tests/tests/appwidget/packages/widgetprovider/Android.mk b/tests/tests/appwidget/packages/widgetprovider/Android.mk
new file mode 100644
index 0000000..733a4a8
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/Android.mk
@@ -0,0 +1,78 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+        $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/AndroidManifestV1.xml
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+        $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/AndroidManifestV2.xml
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+        $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/AndroidManifestV3.xml
+
+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/appwidget/packages/widgetprovider/AndroidManifest.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifest.xml
new file mode 100644
index 0000000..77dfc5b
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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 package="android.appwidget.cts.widgetprovider" >
+
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
new file mode 100644
index 0000000..ea23176
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
@@ -0,0 +1,32 @@
+<?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" >
+
+    <application>
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/widget_no_config" />
+            <meta-data android:name="my_custom_info"
+                       android:resource="@xml/widget_config" />
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
new file mode 100644
index 0000000..e46e160
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
@@ -0,0 +1,32 @@
+<?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" >
+
+    <application>
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/widget_no_config" />
+            <meta-data android:name="my_custom_info"
+                       android:resource="@xml/widget_config_no_resize" />
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
new file mode 100644
index 0000000..6da740d
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
@@ -0,0 +1,30 @@
+<?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" >
+
+    <application>
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/widget_no_config" />
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml b/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml
new file mode 100644
index 0000000..b9db450
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ff333333"
+    android:elevation="3dp" />
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml
new file mode 100644
index 0000000..f45c476
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="180dp"
+    android:minHeight="110dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/simple_widget"
+    android:resizeMode="horizontal|vertical"
+    android:configure="android.appwidget.cts.widgetprovider.ExampleAppWidgetConfigure"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml
new file mode 100644
index 0000000..428a780
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="180dp"
+    android:minHeight="110dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/simple_widget"
+    android:resizeMode="none"
+    android:configure="android.appwidget.cts.widgetprovider.ExampleAppWidgetConfigure"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml
new file mode 100644
index 0000000..0dacd1c
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="180dp"
+    android:minHeight="110dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/simple_widget"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml
new file mode 100644
index 0000000..cadb283
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/first_min_appwidget_size"
+    android:minHeight="@dimen/first_min_appwidget_size"
+    android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/first_update_period_millis"
+    android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen|keyguard"
+    android:widgetFeatures="reconfigurable"
+    android:initialLayout="@layout/first_initial_layout"
+    android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+    android:previewImage="@drawable/first_android_icon"
+    android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml
new file mode 100644
index 0000000..89d60eb
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/first_min_appwidget_size"
+    android:minHeight="@dimen/first_min_appwidget_size"
+    android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/first_update_period_millis"
+    android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen|keyguard"
+    android:widgetFeatures="hide_from_picker"
+    android:initialLayout="@layout/first_initial_layout"
+    android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+    android:previewImage="@drawable/first_android_icon"
+    android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml
new file mode 100644
index 0000000..25936ab
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/first_min_appwidget_size"
+    android:minHeight="@dimen/first_min_appwidget_size"
+    android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/first_update_period_millis"
+    android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen|keyguard"
+    android:widgetFeatures="reconfigurable|hide_from_picker"
+    android:initialLayout="@layout/first_initial_layout"
+    android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+    android:previewImage="@drawable/first_android_icon"
+    android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</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 747a8bd..c0870f2 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -33,6 +33,7 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.appwidget.cts.provider.AppWidgetProviderCallbacks;
+import android.appwidget.cts.provider.AppWidgetProviderWithFeatures;
 import android.appwidget.cts.provider.FirstAppWidgetProvider;
 import android.appwidget.cts.provider.SecondAppWidgetProvider;
 import android.appwidget.cts.service.MyAppWidgetService;
@@ -85,10 +86,6 @@
 
 
     public void testGetAppInstalledProvidersForCurrentUserLegacy() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // By default we should get only providers for the current user.
         List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
 
@@ -97,10 +94,6 @@
     }
 
     public void testGetAppInstalledProvidersForCurrentUserNewCurrentProfile() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We ask only for providers for the current user.
         List<AppWidgetProviderInfo> providers = getAppWidgetManager()
                 .getInstalledProvidersForProfile(Process.myUserHandle());
@@ -110,10 +103,6 @@
     }
 
     public void testGetAppInstalledProvidersForCurrentUserNewAllProfiles() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We ask only for providers for all current user's profiles
         UserManager userManager = (UserManager) getInstrumentation()
                 .getTargetContext().getSystemService(Context.USER_SERVICE);
@@ -134,10 +123,6 @@
     }
 
     public void testBindAppWidget() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // Create a host and start listening.
         AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
         host.deleteHost();
@@ -175,9 +160,6 @@
     }
 
     public void testGetAppWidgetIdsForHost() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
         AppWidgetHost host1 = new AppWidgetHost(getInstrumentation().getTargetContext(), 1);
         AppWidgetHost host2 = new AppWidgetHost(getInstrumentation().getTargetContext(), 2);
 
@@ -207,10 +189,6 @@
     }
 
     public void testAppWidgetProviderCallbacks() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         AtomicInteger invocationCounter = new AtomicInteger();
 
         // Set a mock to intercept provider callbacks.
@@ -318,10 +296,6 @@
     }
 
     public void testTwoAppWidgetProviderCallbacks() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         AtomicInteger invocationCounter = new AtomicInteger();
 
         // Set a mock to intercept first provider callbacks.
@@ -413,10 +387,6 @@
     }
 
     public void testGetAppWidgetIdsForProvider() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -464,10 +434,6 @@
     }
 
     public void testGetAppWidgetInfo() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -520,10 +486,6 @@
     }
 
     public void testGetAppWidgetOptions() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -569,10 +531,6 @@
     }
 
     public void testDeleteHost() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -614,10 +572,6 @@
     }
 
     public void testDeleteHosts() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -674,10 +628,6 @@
     }
 
     public void testOnProvidersChanged() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -740,10 +690,6 @@
     }
 
     public void testUpdateAppWidgetViaComponentName() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -834,10 +780,6 @@
     }
 
     public void testUpdateAppWidgetViaWidgetId() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -908,10 +850,6 @@
     }
 
     public void testUpdateAppWidgetViaWidgetIds() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -1004,10 +942,6 @@
     }
 
     public void testPartiallyUpdateAppWidgetViaWidgetId() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -1082,10 +1016,6 @@
     }
 
     public void testPartiallyUpdateAppWidgetViaWidgetIds() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -1196,10 +1126,6 @@
     }
 
     public void testCollectionWidgets() throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
-
         // We want to bind widgets.
         grantBindAppWidgetPermission();
 
@@ -1308,6 +1234,22 @@
         }
     }
 
+    public void testWidgetFeaturesParsed() throws Exception {
+        assertEquals(0, getFirstAppWidgetProviderInfo().widgetFeatures);
+        String packageName = getInstrumentation().getTargetContext().getPackageName();
+
+        assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE,
+                getProviderInfo(new ComponentName(packageName,
+                AppWidgetProviderWithFeatures.Provider1.class.getName())).widgetFeatures);
+        assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER,
+                getProviderInfo(new ComponentName(packageName,
+                        AppWidgetProviderWithFeatures.Provider2.class.getName())).widgetFeatures);
+        assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+                | AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER,
+                getProviderInfo(new ComponentName(packageName,
+                        AppWidgetProviderWithFeatures.Provider3.class.getName())).widgetFeatures);
+    }
+
     private void waitForCallCount(AtomicInteger counter, int expectedCount) {
         synchronized (mLock) {
             final long startTimeMillis = SystemClock.uptimeMillis();
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
index fb0dbd1..b98973a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
@@ -16,6 +16,8 @@
 
 package android.appwidget.cts;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.appwidget.cts.provider.FirstAppWidgetProvider;
 import android.appwidget.cts.provider.SecondAppWidgetProvider;
@@ -36,6 +38,12 @@
     private static final String SECOND_APP_WIDGET_CONFIGURE_ACTIVITY =
             "android.appwidget.cts.provider.SecondAppWidgetConfigureActivity";
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(hasAppWidgets());
+    }
+
     public boolean hasAppWidgets() {
         return getInstrumentation().getTargetContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index 019fb66..4f6c657 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -52,9 +52,6 @@
     }
 
     private void runPinWidgetTest(final String launcherPkg) throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
         setLauncher(launcherPkg + "/" + LAUNCHER_CLASS);
 
         Context context = getInstrumentation().getContext();
@@ -109,9 +106,6 @@
 
     public void verifyIsRequestPinAppWidgetSupported(String launcherPkg, boolean expectedSupport)
         throws Exception {
-        if (!hasAppWidgets()) {
-            return;
-        }
         setLauncher(launcherPkg + "/" + LAUNCHER_CLASS);
 
         Context context = getInstrumentation().getContext();
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
new file mode 100644
index 0000000..2ec8cf7
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.appwidget.cts;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.common.Constants;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Process;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class UpdateProviderInfoTest extends AppWidgetTestCase {
+
+    private static final String PROVIDER_PACKAGE = "android.appwidget.cts.widgetprovider";
+    private static final String PROVIDER_CLASS = "android.appwidget.cts.packages.SimpleProvider";
+
+    private static final String APK_PATH = "data/local/tmp/cts/widgetprovider/";
+    private static final String APK_V1 = APK_PATH + "CtsAppWidgetProvider1.apk";
+    private static final String APK_V2 = APK_PATH + "CtsAppWidgetProvider2.apk";
+    private static final String APK_V3 = APK_PATH + "CtsAppWidgetProvider3.apk";
+
+    private static final String EXTRA_CUSTOM_INFO = "my_custom_info";
+
+    private static final int HOST_ID = 42;
+
+    private static final int RETRY_COUNT = 3;
+
+    private CountDownLatch mProviderChangeNotifier;
+    AppWidgetHost mHost;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        uninstallProvider();
+        createHost();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        uninstallProvider();
+
+        if (mHost != null) {
+            mHost.deleteHost();
+        }
+    }
+
+    public void testInfoOverrides() throws Throwable {
+        // On first install the provider does not have any config activity.
+        installApk(APK_V1);
+        assertNull(getProviderInfo().configure);
+
+        // The provider info is updated
+        updateInfo(EXTRA_CUSTOM_INFO);
+        assertNotNull(getProviderInfo().configure);
+
+        // The provider info is updated
+        updateInfo(null);
+        assertNull(getProviderInfo().configure);
+    }
+
+    public void testOverridesPersistedOnUpdate() throws Exception {
+        installApk(APK_V1);
+        assertNull(getProviderInfo().configure);
+
+        updateInfo(EXTRA_CUSTOM_INFO);
+        assertNotNull(getProviderInfo().configure);
+        assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode),
+                AppWidgetProviderInfo.RESIZE_BOTH);
+
+        // Apk updated, the info is also updated
+        installApk(APK_V2);
+        assertNotNull(getProviderInfo().configure);
+        assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode), 0);
+
+        // The provider info is reverted
+        updateInfo(null);
+        assertNull(getProviderInfo().configure);
+    }
+
+    public void testOverrideClearedWhenMissingInfo() throws Exception {
+        installApk(APK_V1);
+        assertNull(getProviderInfo().configure);
+
+        updateInfo(EXTRA_CUSTOM_INFO);
+        assertNotNull(getProviderInfo().configure);
+
+        // V3 does not have the custom info definition
+        installApk(APK_V3);
+        assertNull(getProviderInfo().configure);
+    }
+
+    private void createHost() throws Exception {
+        try {
+            runTestOnUiThread(() -> {
+                mHost = new AppWidgetHost(getInstrumentation().getTargetContext(), HOST_ID) {
+
+                    @Override
+                    protected void onProvidersChanged() {
+                        super.onProvidersChanged();
+
+                        if (mProviderChangeNotifier != null) {
+                            mProviderChangeNotifier.countDown();
+                        }
+                    }
+                };
+                mHost.startListening();
+            });
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    private void updateInfo(String key) throws Exception {
+        mProviderChangeNotifier = new CountDownLatch(1);
+        Intent intent = new Intent(Constants.ACTION_APPLY_OVERRIDE)
+                .setComponent(new ComponentName(PROVIDER_PACKAGE, PROVIDER_CLASS))
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                .putExtra(Constants.EXTRA_REQUEST, key);
+        getInstrumentation().getTargetContext().sendBroadcast(intent);
+
+        // Wait until the app widget manager is notified
+        mProviderChangeNotifier.await();
+    }
+
+    private void uninstallProvider() throws Exception {
+        runShellCommand("pm uninstall " + PROVIDER_PACKAGE);
+    }
+
+    private void installApk(String path) throws Exception {
+        mProviderChangeNotifier = new CountDownLatch(1);
+        runShellCommand("pm install -r -d " + path);
+
+        // Wait until the app widget manager is notified
+        mProviderChangeNotifier.await();
+    }
+
+    private AppWidgetProviderInfo getProviderInfo() throws Exception {
+        for (int i = 0; i < RETRY_COUNT; i++) {
+            mProviderChangeNotifier = new CountDownLatch(1);
+            List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(getInstrumentation()
+                    .getTargetContext()).getInstalledProvidersForPackage(
+                    PROVIDER_PACKAGE, Process.myUserHandle());
+
+            if (providers != null && !providers.isEmpty()) {
+                return providers.get(0);
+            }
+
+            // Sometimes it could take time for the info to appear after the apk is just installed
+            mProviderChangeNotifier.await(2, TimeUnit.SECONDS);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java
new file mode 100644
index 0000000..d1f848a
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java
@@ -0,0 +1,45 @@
+/*
+ * 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.appwidget.cts.provider;
+
+public abstract class AppWidgetProviderWithFeatures extends StubbableAppWidgetProvider {
+    private static final Object sLock = new Object();
+
+    private static AppWidgetProviderCallbacks sCallbacks;
+
+    public static void setCallbacks(AppWidgetProviderCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    @Override
+    protected AppWidgetProviderCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setProvider(this);
+            }
+            return sCallbacks;
+        }
+    }
+
+    public static final class Provider1 extends AppWidgetProviderWithFeatures { }
+
+    public static final class Provider2 extends AppWidgetProviderWithFeatures { }
+
+    public static final class Provider3 extends AppWidgetProviderWithFeatures { }
+}
diff --git a/tests/tests/assist/Android.mk b/tests/tests/assist/Android.mk
index 6aa818e..f9bc56d 100644
--- a/tests/tests/assist/Android.mk
+++ b/tests/tests/assist/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := CtsAssistCommon ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsAssistTestCases
diff --git a/tests/tests/assist/AndroidTest.xml b/tests/tests/assist/AndroidTest.xml
index d8f31e7..13b1847 100644
--- a/tests/tests/assist/AndroidTest.xml
+++ b/tests/tests/assist/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Assist 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" />
diff --git a/tests/tests/background/Android.mk b/tests/tests/background/Android.mk
index c5a98aa..f84d01c 100755
--- a/tests/tests/background/Android.mk
+++ b/tests/tests/background/Android.mk
@@ -28,7 +28,7 @@
     ctstestrunner \
     ub-uiautomator
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/background/AndroidTest.xml b/tests/tests/background/AndroidTest.xml
index 33e20ea..c54940c 100644
--- a/tests/tests/background/AndroidTest.xml
+++ b/tests/tests/background/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/batterysaving/Android.mk b/tests/tests/batterysaving/Android.mk
new file mode 100755
index 0000000..3bef64e
--- /dev/null
+++ b/tests/tests/batterysaving/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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    BatterySavingCtsCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsBatterySavingTestCases
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/batterysaving/AndroidManifest.xml b/tests/tests/batterysaving/AndroidManifest.xml
new file mode 100755
index 0000000..392a7aa
--- /dev/null
+++ b/tests/tests/batterysaving/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.os.cts.batterysaving">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.os.cts.batterysaving"
+        android:label="CTS tests for battery saving features">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
new file mode 100644
index 0000000..d14c61f
--- /dev/null
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for ShortcutManager CTS test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- Disable keyguard -->
+        <option name="run-command" value="locksettings set-disabled true" />
+    </target_preparer>
+
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" /> <!-- DO NOT SUBMIT WITH FALSE -->
+        <option name="test-file-name" value="CtsBatterySavingTestCases.apk" />
+        <option name="test-file-name" value="CtsBatterySavingAppTargetApiCurrent.apk" />
+        <option name="test-file-name" value="CtsBatterySavingAppTargetApi25.apk" />
+    </target_preparer>
+
+    <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" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.os.cts.batterysaving" />
+        <option name="runtime-hint" value="10m00s" />
+    </test>
+</configuration>
diff --git a/tests/tests/batterysaving/apps/Android.mk b/tests/tests/batterysaving/apps/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/tests/tests/batterysaving/apps/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/tests/tests/batterysaving/apps/app_target_api_25/Android.mk b/tests/tests/batterysaving/apps/app_target_api_25/Android.mk
new file mode 100644
index 0000000..90eed31
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_25/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 := CtsBatterySavingAppTargetApi25
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../app_target_api_current/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    BatterySavingCtsCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ub-uiautomator
+
+LOCAL_SDK_VERSION := test_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/batterysaving/apps/app_target_api_25/AndroidManifest.xml b/tests/tests/batterysaving/apps/app_target_api_25/AndroidManifest.xml
new file mode 100755
index 0000000..6842105
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_25/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.os.cts.batterysaving.app_target_api_25">
+
+    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25" />
+
+    <!-- Keep this section in sync with the other app manifest(s) -->
+    <application>
+        <activity android:name="android.os.cts.batterysaving.app.TestActivity"
+            android:enabled="true" android:exported="true">
+        </activity>
+
+        <receiver android:name="android.os.cts.batterysaving.app.CommReceiver"
+            android:enabled="true" android:exported="true">
+        </receiver>
+
+        <service android:name="android.os.cts.batterysaving.app.TestService"
+            android:enabled="true" android:exported="true">
+        </service>
+    </application>
+</manifest>
+
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/Android.mk b/tests/tests/batterysaving/apps/app_target_api_current/Android.mk
new file mode 100644
index 0000000..2f91625
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/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 := CtsBatterySavingAppTargetApiCurrent
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    BatterySavingCtsCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ub-uiautomator
+
+LOCAL_SDK_VERSION := test_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/batterysaving/apps/app_target_api_current/AndroidManifest.xml b/tests/tests/batterysaving/apps/app_target_api_current/AndroidManifest.xml
new file mode 100755
index 0000000..54b3a31
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.os.cts.batterysaving.app_target_api_current">
+
+    <!-- Keep this section in sync with the other app manifest(s) -->
+    <application>
+        <activity android:name="android.os.cts.batterysaving.app.TestActivity"
+            android:enabled="true" android:exported="true">
+        </activity>
+
+        <receiver android:name="android.os.cts.batterysaving.app.CommReceiver"
+            android:enabled="true" android:exported="true">
+        </receiver>
+
+        <service android:name="android.os.cts.batterysaving.app.TestService"
+            android:enabled="true" android:exported="true">
+        </service>
+    </application>
+</manifest>
+
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
new file mode 100644
index 0000000..6309b60
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.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.os.cts.batterysaving.app;
+
+import static android.os.cts.batterysaving.common.Values.KEY_REQUEST_FOREGROUND;
+import static android.os.cts.batterysaving.common.Values.getTestService;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.SetAlarmRequest;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceResponse;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastRpcBase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class CommReceiver extends BroadcastRpcBase.ReceiverBase<Payload, Payload> {
+    private static final String TAG = "CommReceiver";
+
+    @Override
+    protected Payload handleRequest(Context context, Payload request) {
+        final Payload.Builder responseBuilder = Payload.newBuilder();
+        if (request.hasTestServiceRequest()) {
+            handleBatterySaverBgServiceRequest(context, request, responseBuilder);
+        }
+        return responseBuilder.build();
+    }
+
+    private void handleBatterySaverBgServiceRequest(Context context,
+            Payload request, Payload.Builder responseBuilder) {
+        final TestServiceResponse.Builder rb = TestServiceResponse.newBuilder();
+
+        if (request.getTestServiceRequest().getClearLastIntent()) {
+            // Request to clear the last intent to TestService.
+
+            TestService.LastStartIntent.set(null);
+            rb.setClearLastIntentAck(true);
+
+        } else if (request.getTestServiceRequest().getGetLastIntent()) {
+            // Request to return the last intent action that started TestService.
+
+            final Intent intent = TestService.LastStartIntent.get();
+            if (intent != null) {
+                rb.setGetLastIntentAction(intent.getAction());
+            }
+
+        } else if (request.getTestServiceRequest().hasStartService()) {
+            // Request to start TestService with a given action.
+
+            final String action = request.getTestServiceRequest().getStartService().getAction();
+            final boolean fg = request.getTestServiceRequest().getStartService().getForeground();
+
+            final Intent intent = new Intent(action)
+                    .setComponent(getTestService(context.getPackageName()))
+                    .putExtra(KEY_REQUEST_FOREGROUND, fg);
+
+            Log.d(TAG, "Starting service " + intent);
+
+            if (fg) {
+                context.startForegroundService(intent);
+            } else {
+                context.startService(intent);
+            }
+            rb.setStartServiceAck(true);
+
+        } else if (request.getTestServiceRequest().hasSetAlarm()) {
+            // Set an alarm with a given intent.
+
+            final SetAlarmRequest req = request.getTestServiceRequest().getSetAlarm();
+
+            final AlarmManager am = context.getSystemService(AlarmManager.class);
+
+            final int type = req.getType();
+            final long triggerTime = req.getTriggerTime();
+            final long interval = req.getRepeatInterval();
+            final boolean allowWhileIdle = req.getAllowWhileIdle();
+
+            final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1,
+                    new Intent(req.getIntentAction()), 0);
+
+            Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+                    + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
+            if (interval > 0) {
+                am.setRepeating(type, triggerTime, interval, alarmSender);
+            } else if (allowWhileIdle) {
+                am.setExactAndAllowWhileIdle(type, triggerTime, alarmSender);
+            } else {
+                am.setExact(type, triggerTime, alarmSender);
+            }
+            rb.setSetAlarmAck(true);
+        }
+
+        responseBuilder.setTestServiceResponse(rb);
+    }
+
+    @Override
+    protected byte[] responseToBytes(Payload payload) {
+        return payload.toByteArray();
+    }
+
+    @Override
+    protected Payload bytesToRequest(byte[] bytes) {
+        try {
+            return Payload.parseFrom(bytes);
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("InvalidProtocolBufferException", e);
+        }
+    }
+}
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestActivity.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestActivity.java
new file mode 100644
index 0000000..3d25526
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestActivity.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 android.os.cts.batterysaving.app;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {
+}
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestService.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestService.java
new file mode 100644
index 0000000..e9c4681
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/TestService.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.os.cts.batterysaving.app;
+
+import static android.os.cts.batterysaving.common.Values.KEY_REQUEST_FOREGROUND;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+public class TestService extends Service {
+    private static final String TAG = "TestService";
+
+    public static final AtomicReference<Intent> LastStartIntent = new AtomicReference();
+
+    private final String getNotificationChannelId() {
+        return new ComponentName(this, TestService.class).toShortString();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+
+        Log.d(TAG, "TestService.TestService: intent=" + intent);
+
+        if (intent.getBooleanExtra(KEY_REQUEST_FOREGROUND, false)) {
+            NotificationManager notificationManager = getSystemService(NotificationManager.class);
+            notificationManager.createNotificationChannel(new NotificationChannel(
+                    getNotificationChannelId(), getNotificationChannelId(),
+                    NotificationManager.IMPORTANCE_DEFAULT));
+            Notification notification =
+                    new Notification.Builder(getApplicationContext(), getNotificationChannelId())
+                            .setContentTitle("FgService")
+                            .setSmallIcon(android.R.drawable.ic_popup_sync)
+                            .build();
+
+            startForeground(1, notification);
+        }
+
+        LastStartIntent.set(intent);
+
+        stopSelf();
+
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/tests/tests/batterysaving/common/Android.mk b/tests/tests/batterysaving/common/Android.mk
new file mode 100644
index 0000000..0f1805e
--- /dev/null
+++ b/tests/tests/batterysaving/common/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-proto-files-under, proto)
+
+LOCAL_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target \
+    compatibility-device-util \
+    android.test.runner.stubs
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := BatterySavingCtsCommon
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/batterysaving/common/proto/battery_saver_cts_common.proto b/tests/tests/batterysaving/common/proto/battery_saver_cts_common.proto
new file mode 100644
index 0000000..d55184b
--- /dev/null
+++ b/tests/tests/batterysaving/common/proto/battery_saver_cts_common.proto
@@ -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.
+ */
+syntax = "proto2";
+
+option java_outer_classname = "BatterySavingCtsCommon";
+
+package android.os.cts.batterysaving.common;
+
+message Payload {
+    message TestServiceRequest {
+        // Request to clear TestService.LastStartIntent.
+        optional bool clear_last_intent = 1;
+
+        // Request to return the action set in TestService.LastStartIntent.
+        optional bool get_last_intent = 2;
+
+        // Request to start testService.
+        message StartServiceRequest {
+            optional bool foreground = 1;
+            optional string action = 2;
+        }
+
+        optional StartServiceRequest start_service = 3;
+
+        message SetAlarmRequest {
+            optional int32 type = 1;
+            optional int64 trigger_time = 2;
+            optional int64 repeat_interval = 3;
+            optional bool allow_while_idle = 4;
+            optional string intent_action = 5;
+        }
+        optional SetAlarmRequest set_alarm = 4;
+    }
+
+    optional TestServiceRequest test_service_request = 1;
+
+    message TestServiceResponse {
+        optional bool clear_last_intent_ack = 1;
+        optional string get_last_intent_action = 2;
+        optional bool start_service_ack = 3;
+        optional bool set_alarm_ack = 4;
+    }
+
+    optional TestServiceResponse test_service_response = 2;
+}
diff --git a/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/Values.java b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/Values.java
new file mode 100644
index 0000000..8b1dd36
--- /dev/null
+++ b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/Values.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.os.cts.batterysaving.common;
+
+import android.content.ComponentName;
+
+import java.security.SecureRandom;
+
+public class Values {
+    private static final SecureRandom sRng = new SecureRandom();
+
+    public static final String APP_CURRENT_PACKAGE =
+            "android.os.cts.batterysaving.app_target_api_current";
+
+    public static final String APP_25_PACKAGE =
+            "android.os.cts.batterysaving.app_target_api_25";
+
+    public static final String COMM_RECEIVER = "android.os.cts.batterysaving.app.CommReceiver";
+
+    public static final String TEST_SERVICE = "android.os.cts.batterysaving.app.TestService";
+
+    public static final String KEY_REQUEST_FOREGROUND = "KEY_REQUEST_FOREGROUND";
+
+    public static ComponentName getCommReceiver(String packageName) {
+        return new ComponentName(packageName, COMM_RECEIVER);
+    }
+
+    public static ComponentName getTestService(String packageName) {
+        return new ComponentName(packageName, TEST_SERVICE);
+    }
+
+    public static int getRandomInt() {
+        return sRng.nextInt();
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
new file mode 100644
index 0000000..d05c181
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.batterysaving;
+
+import static android.os.cts.batterysaving.common.Values.APP_25_PACKAGE;
+import static android.os.cts.batterysaving.common.Values.getRandomInt;
+
+import static com.android.compatibility.common.util.AmUtils.runKill;
+import static com.android.compatibility.common.util.AmUtils.runMakeUidIdle;
+import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
+import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
+import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.SetAlarmRequest;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.StartServiceRequest;
+import android.os.cts.batterysaving.common.Values;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * CTS for battery saver alarm throttling
+ *
+ atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySaverAlarmTest extends BatterySavingTestBase {
+    private static final String TAG = "BatterySaverAlarmTest";
+
+    private static final long DEFAULT_WAIT = 1_000;
+    private static final long POLL_INTERVAL = 200;
+
+    // Tweaked alarm manager constants to facilitate testing
+    private static final long MIN_REPEATING_INTERVAL = 5_000;
+    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
+    private static final long ALLOW_WHILE_IDLE_LONG_TIME = 20_000;
+    private static final long MIN_FUTURITY = 2_000;
+
+    private void updateAlarmManagerConstants() throws IOException {
+        putGlobalSetting("alarm_manager_constants",
+                "min_interval=" + MIN_REPEATING_INTERVAL + ","
+                + "min_futurity=" + MIN_FUTURITY + ","
+                + "allow_while_idle_short_time=" + ALLOW_WHILE_IDLE_SHORT_TIME + ","
+                + "allow_while_idle_long_time=" + ALLOW_WHILE_IDLE_LONG_TIME);
+    }
+
+    private void resetAlarmManagerConstants() throws IOException {
+        putGlobalSetting("alarm_manager_constants", "null");
+    }
+
+    // Use a different broadcast action every time.
+    private final String ACTION = "BATTERY_SAVER_ALARM_TEST_ALARM_ACTION_" + Values.getRandomInt();
+
+    private final AtomicInteger mAlarmCount = new AtomicInteger();
+
+    private final BroadcastReceiver mAlarmReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mAlarmCount.incrementAndGet();
+            Log.d(TAG, "Alarm received at " + SystemClock.elapsedRealtime());
+        }
+    };
+
+    @Before
+    public void setUp() throws IOException {
+        updateAlarmManagerConstants();
+
+        final IntentFilter filter = new IntentFilter(ACTION);
+        getContext().registerReceiver(mAlarmReceiver, filter, null,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        resetAlarmManagerConstants();
+        getContext().unregisterReceiver(mAlarmReceiver);
+    }
+
+    private void scheduleAlarm(String targetPackage, int type, long triggerMillis)
+            throws Exception {
+        scheduleAlarm(targetPackage, type, triggerMillis, /*whileIdle=*/ true);
+    }
+
+    private void scheduleAlarm(String targetPackage, int type, long triggerMillis,
+            boolean whileIdle) throws Exception {
+        Log.d(TAG, "Setting an alarm at " + triggerMillis + " (in "
+                + (triggerMillis - SystemClock.elapsedRealtime()) + "ms)");
+        final SetAlarmRequest areq = SetAlarmRequest.newBuilder()
+                .setIntentAction(ACTION)
+                .setType(type)
+                .setAllowWhileIdle(true)
+                .setTriggerTime(triggerMillis)
+                .build();
+        final Payload response = mRpc.sendRequest(targetPackage,
+                Payload.newBuilder().setTestServiceRequest(
+                        TestServiceRequest.newBuilder().setSetAlarm(areq))
+                        .build());
+        assertTrue(response.hasTestServiceResponse()
+                && response.getTestServiceResponse().getSetAlarmAck());
+    }
+
+    /**
+     * Return a service in the target package.
+     */
+    private String startService(String targetPackage, boolean foreground)
+            throws Exception {
+        final String action = "start_service_" + getRandomInt() + "_fg=" + foreground;
+
+        final Payload response = mRpc.sendRequest(targetPackage,
+                Payload.newBuilder().setTestServiceRequest(
+                        TestServiceRequest.newBuilder().setStartService(
+                                StartServiceRequest.newBuilder()
+                                        .setForeground(foreground)
+                                        .setAction(action).build()
+                        )).build());
+        assertTrue(response.hasTestServiceResponse()
+                && response.getTestServiceResponse().getStartServiceAck());
+        return action;
+    }
+
+    @LargeTest
+    @Test
+    public void testAllowWhileIdleThrottled() throws Exception {
+        final String targetPackage = APP_25_PACKAGE;
+
+        runDumpsysBatteryUnplug();
+
+        enableBatterySaver(true);
+
+        // Make sure the UID is not in the FG.
+        runMakeUidIdle(targetPackage);
+        runKill(targetPackage);
+
+        // First alarm shouldn't be throttled.
+        final long triggerElapsed1 = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed1);
+        Thread.sleep(Math.max(0, triggerElapsed1 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm shouldn't be blocked in battery saver",
+                1, mAlarmCount.get());
+
+        // Second one should be throttled.
+        mAlarmCount.set(0);
+
+        final long triggerElapsed2 = triggerElapsed1 + ALLOW_WHILE_IDLE_SHORT_TIME;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed2);
+        Thread.sleep(Math.max(0, triggerElapsed2 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Follow up allow-while-idle alarm shouldn't go off before short time",
+                0, mAlarmCount.get());
+
+        final long triggerElapsed3 = triggerElapsed1 + ALLOW_WHILE_IDLE_LONG_TIME;
+        Thread.sleep(Math.max(0, triggerElapsed3 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Follow-up allow-while-idle alarm should go off after long time",
+                1, mAlarmCount.get());
+
+        // Start an FG service, which should reset throttling.
+        mAlarmCount.set(0);
+
+        startService(targetPackage, true);
+
+        final long triggerElapsed4 = triggerElapsed3 + ALLOW_WHILE_IDLE_SHORT_TIME;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed4);
+        Thread.sleep(Math.max(0, triggerElapsed4 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm shouldn't be throttled in battery saver"
+                +" after FG service started",
+                1, mAlarmCount.get());
+
+        // Battery saver off. Always use the short time.
+        enableBatterySaver(false);
+
+        mAlarmCount.set(0);
+
+        final long triggerElapsed5 = triggerElapsed4 + ALLOW_WHILE_IDLE_SHORT_TIME;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed5);
+        Thread.sleep(Math.max(0, triggerElapsed5 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm shouldn't be throttled in battery saver"
+                        +" when BS is off",
+                1, mAlarmCount.get());
+
+        // One more time.
+        mAlarmCount.set(0);
+
+        final long triggerElapsed6 = triggerElapsed5 + ALLOW_WHILE_IDLE_SHORT_TIME;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed6);
+        Thread.sleep(Math.max(0, triggerElapsed6 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm shouldn't be throttled when BS is off",
+                1, mAlarmCount.get());
+    }
+
+    @LargeTest
+    @Test
+    public void testAlarmsThrottled() throws Exception {
+        final String targetPackage = APP_25_PACKAGE;
+
+        runDumpsysBatteryUnplug();
+
+        enableBatterySaver(true);
+
+        // Make sure the UID is not in the FG.
+        runMakeUidIdle(targetPackage);
+        runKill(targetPackage);
+
+        // When battery saver is enabled, alarms should be blocked.
+        final long triggerElapsed1 = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed1,
+                /* whileIdle=*/ true);
+        Thread.sleep(Math.max(0, triggerElapsed1 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm should be blocked in battery saver",
+                0, mAlarmCount.get());
+
+        // Start an FG service -> should unblock the alarm.
+        startService(targetPackage, true);
+
+        waitUntil("Allow-while-idle alarm should be blocked in battery saver",
+                () -> mAlarmCount.get() == 1);
+
+        // Try again.
+        mAlarmCount.set(0);
+
+        // Make sure the UID is not in the FG.
+        runMakeUidIdle(targetPackage);
+        runKill(targetPackage);
+
+        // When battery saver is enabled, alarms should be blocked.
+        final long triggerElapsed2 = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(targetPackage, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed1,
+                /* whileIdle=*/ true);
+        Thread.sleep(Math.max(0, triggerElapsed1 - SystemClock.elapsedRealtime() + DEFAULT_WAIT));
+        assertEquals("Allow-while-idle alarm should be blocked in battery saver",
+                0, mAlarmCount.get());
+
+        // This time, disable EBS -> should unblock the alarm.
+        enableBatterySaver(false);
+        waitUntil("Allow-while-idle alarm should be blocked in battery saver",
+                () -> mAlarmCount.get() == 1);
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java
new file mode 100644
index 0000000..904cb48
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.batterysaving;
+
+import static android.os.cts.batterysaving.common.Values.APP_25_PACKAGE;
+import static android.os.cts.batterysaving.common.Values.getRandomInt;
+
+import static com.android.compatibility.common.util.AmUtils.runKill;
+import static com.android.compatibility.common.util.AmUtils.runMakeUidIdle;
+import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
+import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.StartServiceRequest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests related to battery saver:
+ *
+ * atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySaverBgServiceTest extends BatterySavingTestBase {
+    private static final String TAG = "BatterySaverBgServiceTest";
+
+    /**
+     * Make sure BG services on pre-O apps can't be started when BS is on.
+     */
+    @Test
+    public void testBgServiceThrottled() throws Exception {
+
+        final String targetPackage = APP_25_PACKAGE;
+
+        runDumpsysBatteryUnplug();
+
+        enableBatterySaver(false);
+
+        // Make sure a BG service on pre-O app can be started with BS off.
+        {
+            runMakeUidIdle(targetPackage);
+            runKill(targetPackage);
+            Thread.sleep(500);
+
+            requestClearIntent(targetPackage);
+
+            final String action = tryStartTestServiceAndReturnAction(targetPackage, false);
+
+            assertEquals(action, waitForLastIntentAction(targetPackage));
+        }
+
+        // Enable battery saver.
+        enableBatterySaver(true);
+        waitUntilForceBackgroundCheck(true);
+
+        // Make sure a BG service on pre-O app *cannot* be started with BS on.
+        {
+            // Do the same thing again.
+            runMakeUidIdle(targetPackage);
+            runKill(targetPackage);
+            Thread.sleep(500);
+
+            requestClearIntent(targetPackage);
+
+            assertNull(requestLastIntent(targetPackage));
+
+            tryStartTestServiceAndReturnAction(targetPackage, false);
+
+            // Wait a little bit and make sure the service didn't start.
+            Thread.sleep(5000);
+
+            assertNull(requestLastIntent(targetPackage));
+        }
+
+        // Make sure an FG service on pre-O app *can* be started with BS on.
+        {
+            runMakeUidIdle(targetPackage);
+            runKill(targetPackage);
+            Thread.sleep(500);
+
+            requestClearIntent(targetPackage);
+
+            final String action = tryStartTestServiceAndReturnAction(targetPackage, true);
+
+            assertEquals(action, waitForLastIntentAction(targetPackage));
+        }
+    }
+
+    /** Ask to clear the last received intent in the test service. */
+    private void requestClearIntent(String targetPackage) throws Exception {
+        final Payload response = mRpc.sendRequest(targetPackage,
+                Payload.newBuilder().setTestServiceRequest(
+                        TestServiceRequest.newBuilder().setClearLastIntent(true))
+                        .build());
+        assertTrue(response.hasTestServiceResponse()
+                && response.getTestServiceResponse().getClearLastIntentAck());
+    }
+
+    /** Get the last received intent in the test service. */
+    private String requestLastIntent(String targetPackage) throws Exception {
+        final Payload response = mRpc.sendRequest(targetPackage,
+                Payload.newBuilder().setTestServiceRequest(
+                        TestServiceRequest.newBuilder().setGetLastIntent(true))
+                        .build());
+        assertTrue(response.hasTestServiceResponse());
+
+        return response.getTestServiceResponse().hasGetLastIntentAction()
+                ? response.getTestServiceResponse().getGetLastIntentAction()
+                : null;
+    }
+
+    /** Wait until the last intent action is non-null. */
+    private String waitForLastIntentAction(String targetPackage) throws Exception {
+        final AtomicReference<String> result = new AtomicReference<>();
+        waitUntil("Service didn't start", () -> {
+            String action = requestLastIntent(targetPackage);
+            if (action != null) {
+                result.set(action);
+                return true;
+            }
+            return false;
+        });
+        return result.get();
+    }
+
+    private String tryStartTestServiceAndReturnAction(String targetPackage, boolean foreground)
+            throws Exception {
+        final String action = "start_service_" + getRandomInt() + "_fg=" + foreground;
+
+        final Payload response = mRpc.sendRequest(targetPackage,
+                Payload.newBuilder().setTestServiceRequest(
+                        TestServiceRequest.newBuilder().setStartService(
+                            StartServiceRequest.newBuilder()
+                                    .setForeground(foreground)
+                                    .setAction(action).build()
+                        )).build());
+        assertTrue(response.hasTestServiceResponse()
+                && response.getTestServiceResponse().getStartServiceAck());
+        return action;
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
new file mode 100644
index 0000000..cbb3fdc
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
@@ -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.os.cts.batterysaving;
+
+import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
+import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+
+import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
+import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
+import static com.android.compatibility.common.util.BatteryUtils.turnOnScreen;
+import static com.android.compatibility.common.util.SettingsUtils.putSecureSetting;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+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.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CallbackAsserter;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.TestUtils.RunnableWithThrow;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests related to battery saver:
+ *
+ atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySaverLocationTest extends BatterySavingTestBase {
+    private static final String TAG = "BatterySaverLocationTest";
+
+    @Rule
+    public final RequiredFeatureRule mRequireLocationRule =
+            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION);
+
+    @Rule
+    public final RequiredFeatureRule mRequireLocationGpsRule =
+            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION_GPS);
+
+    /**
+     * Test for the {@link PowerManager#LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF} mode.
+     */
+    @Test
+    public void testLocationAllDisabled() throws Exception {
+        assertTrue("Screen is off", getPowerManager().isInteractive());
+
+        assertFalse(getPowerManager().isPowerSaveMode());
+        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
+                getPowerManager().getLocationPowerSaveMode());
+
+        assertEquals(0, getLocationGlobalKillSwitch());
+
+        // Make sure GPS is enabled.
+        putSecureSetting(LOCATION_PROVIDERS_ALLOWED, "+gps");
+        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
+        assertTrue(getLocationManager().isLocationEnabled());
+
+        // Unplug the charger and activate battery saver.
+        runDumpsysBatteryUnplug();
+        enableBatterySaver(true);
+
+        // Skip if the location mode is not what's expected.
+        final int mode = getPowerManager().getLocationPowerSaveMode();
+        if (mode != PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) {
+            Log.i(TAG, "Unexpected location power save mode (" + mode + "), skipping.");
+            return;
+        }
+
+        // Make sure screen is on.
+        assertTrue(getPowerManager().isInteractive());
+
+        // Make sure the kill switch is still off.
+        assertEquals(0, getLocationGlobalKillSwitch());
+
+        // Make sure location is still enabled.
+        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
+        assertTrue(getLocationManager().isLocationEnabled());
+
+        // Turn screen off.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(false);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
+            assertEquals(LOCATION_MODE_OFF, getLocationMode());
+            assertFalse(getLocationManager().isLocationEnabled());
+        });
+
+        // On again.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(true);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 0);
+            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
+            assertTrue(getLocationManager().isLocationEnabled());
+        });
+
+        // Off again.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(false);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
+            assertEquals(LOCATION_MODE_OFF, getLocationMode());
+            assertFalse(getLocationManager().isLocationEnabled());
+        });
+
+        // Disable battery saver and make sure the kill swtich is off.
+        runWithExpectingLocationCallback(() -> {
+            enableBatterySaver(false);
+            waitUntil("Kill switch still on", () -> getLocationGlobalKillSwitch() == 0);
+            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
+            assertTrue(getLocationManager().isLocationEnabled());
+        });
+    }
+
+    private int getLocationGlobalKillSwitch() {
+        return Global.getInt(getContext().getContentResolver(),
+                Global.LOCATION_GLOBAL_KILL_SWITCH, 0);
+    }
+
+    private int getLocationMode() {
+        return Secure.getInt(getContext().getContentResolver(), Secure.LOCATION_MODE, 0);
+    }
+
+    private void runWithExpectingLocationCallback(RunnableWithThrow r) throws Exception {
+        CallbackAsserter locationModeBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
+        CallbackAsserter locationModeObserverAsserter = CallbackAsserter.forContentUri(
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        r.run();
+
+        locationModeBroadcastAsserter.assertCalled("Broadcast not received",
+                DEFAULT_TIMEOUT_SECONDS);
+        locationModeObserverAsserter.assertCalled("Observer not notified",
+                DEFAULT_TIMEOUT_SECONDS);
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
new file mode 100644
index 0000000..5f29008
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.batterysaving;
+
+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 android.os.PowerManager;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests related to battery saver:
+ *
+ * atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySaverTest extends BatterySavingTestBase {
+    /**
+     * Enable battery saver and make sure the relevant components get notifed.
+     * @throws Exception
+     */
+    @Test
+    public void testActivateBatterySaver() throws Exception {
+        assertFalse(getPowerManager().isPowerSaveMode());
+        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
+                getPowerManager().getLocationPowerSaveMode());
+
+        // Unplug the charger.
+        runDumpsysBatteryUnplug();
+
+        // Activate battery saver.
+        enableBatterySaver(true);
+
+        // Make sure the job scheduler and the alarm manager are informed.
+        waitUntilAlarmForceAppStandby(true);
+        waitUntilJobForceAppStandby(true);
+        waitUntilForceBackgroundCheck(true);
+
+        // Deactivate.
+        // To avoid too much churn, let's sleep a little bit before deactivating.
+        Thread.sleep(1000);
+
+        enableBatterySaver(false);
+
+        // Make sure the job scheduler and the alarm manager are informed.
+        waitUntilAlarmForceAppStandby(false);
+        waitUntilJobForceAppStandby(false);
+        waitUntilForceBackgroundCheck(false);
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
new file mode 100644
index 0000000..be5a9cf
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.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.os.cts.batterysaving;
+
+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.runShellCommand;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+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.OnFailureRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class BatterySavingTestBase {
+    private static final String TAG = "BatterySavingTestBase";
+
+    public static final int DEFAULT_TIMEOUT_SECONDS = 30;
+
+    public static final boolean DEBUG = false;
+
+    protected final BroadcastRpc mRpc = new BroadcastRpc();
+
+    @Rule
+    public final OnFailureRule mDumpOnFailureRule = new OnFailureRule() {
+        @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");
+        }
+    };
+
+    @Before
+    public final void resetDumpsysBatteryBeforeTest() throws Exception {
+        turnOnScreen(true);
+    }
+
+    @After
+    public final void resetDumpsysBatteryAfterTest() throws Exception {
+        runDumpsysBatteryReset();
+        turnOnScreen(true);
+    }
+
+    public String getLogTag() {
+        return TAG;
+    }
+
+    /** Print a debug log on logcat. */
+    public void debug(String message) {
+        if (DEBUG || Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(getLogTag(), message);
+        }
+    }
+
+    public void waitUntilAlarmForceAppStandby(boolean expected) throws Exception {
+        waitUntil("Force all apps standby still " + !expected + " (alarm)", () ->
+                runShellCommand("dumpsys alarm").contains("Force all apps standby: " + expected));
+    }
+
+    public void waitUntilJobForceAppStandby(boolean expected) throws Exception {
+        waitUntil("Force all apps standby still " + !expected + " (job)", () ->
+                runShellCommand("dumpsys jobscheduler")
+                        .contains("Force all apps standby: " + expected));
+    }
+
+    public void waitUntilForceBackgroundCheck(boolean expected) throws Exception {
+        waitUntil("Force background check still " + !expected + " (job)", () ->
+                runShellCommand("dumpsys activity").contains("mForceBackgroundCheck=" + expected));
+    }
+
+    public static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    public PackageManager getPackageManager() {
+        return getContext().getPackageManager();
+    }
+
+    public PowerManager getPowerManager() {
+        return getContext().getSystemService(PowerManager.class);
+    }
+
+    public BatteryManager getBatteryManager() {
+        return getContext().getSystemService(BatteryManager.class);
+    }
+
+    public LocationManager getLocationManager() {
+        return getContext().getSystemService(LocationManager.class);
+    }
+}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BroadcastRpc.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BroadcastRpc.java
new file mode 100644
index 0000000..3c895af
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BroadcastRpc.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 android.os.cts.batterysaving;
+
+import static android.os.cts.batterysaving.common.Values.getCommReceiver;
+
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+
+import com.android.compatibility.common.util.BroadcastRpcBase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class BroadcastRpc extends BroadcastRpcBase<Payload, Payload> {
+    @Override
+    protected byte[] requestToBytes(Payload testServiceRequest) {
+        return testServiceRequest.toByteArray();
+    }
+
+    @Override
+    protected Payload bytesToResponse(byte[] bytes) {
+        try {
+            return Payload.parseFrom(bytes);
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("InvalidProtocolBufferException", e);
+        }
+    }
+
+    public Payload sendRequest(String targetPackage,
+            Payload testServiceRequest) throws Exception {
+        return super.invoke(getCommReceiver(targetPackage), testServiceRequest);
+    }
+}
diff --git a/tests/tests/bionic/AndroidTest.xml b/tests/tests/bionic/AndroidTest.xml
index ce13307..f6d2b09 100644
--- a/tests/tests/bionic/AndroidTest.xml
+++ b/tests/tests/bionic/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Bionic test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="bionic" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/bluetooth/Android.mk b/tests/tests/bluetooth/Android.mk
index 1af4a3f..3fdb872 100644
--- a/tests/tests/bluetooth/Android.mk
+++ b/tests/tests/bluetooth/Android.mk
@@ -24,8 +24,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 634b033..8aabe9c 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -19,7 +19,6 @@
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
-import android.bluetooth.cts.BluetoothScanReceiver;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanFilter;
@@ -61,8 +60,10 @@
 
     private static final String TAG = "BluetoothLeScanTest";
 
-    private static final int SCAN_DURATION_MILLIS = 5000;
+    private static final int SCAN_DURATION_MILLIS = 10000;
     private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000;
+    private static final int SCAN_STOP_TIMEOUT = 2000;
+    private static final int ADAPTER_ENABLE_TIMEOUT = 3000;
     private CountDownLatch mFlushBatchScanLatch;
 
     private BluetoothAdapter mBluetoothAdapter;
@@ -82,7 +83,7 @@
             // Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
             // bluetooth state.
             mBluetoothAdapter.enable();
-            sleep(3000);
+            sleep(ADAPTER_ENABLE_TIMEOUT);
         }
         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
         mLocationOn = TestUtils.isLocationOn(getContext());
@@ -99,6 +100,8 @@
         if (!mLocationOn) {
             TestUtils.disableLocation(getContext());
         }
+        mBluetoothAdapter.disable();
+        sleep(ADAPTER_ENABLE_TIMEOUT);
     }
 
     /**
@@ -112,6 +115,7 @@
         long scanStartMillis = SystemClock.elapsedRealtime();
         Collection<ScanResult> scanResults = scan();
         long scanEndMillis = SystemClock.elapsedRealtime();
+        Log.d(TAG, "scan result size:" + scanResults.size());
         assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
         verifyTimestamp(scanResults, scanStartMillis, scanEndMillis);
     }
@@ -140,7 +144,7 @@
         mScanner.startScan(filters, settings, filterLeScanCallback);
         sleep(SCAN_DURATION_MILLIS);
         mScanner.stopScan(filterLeScanCallback);
-        sleep(1000);
+        sleep(SCAN_STOP_TIMEOUT);
         Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
         for (ScanResult result : scanResults) {
             assertTrue(filter.matches(result));
@@ -178,51 +182,58 @@
         return null;
     }
 
-    /**
-     * Test of opportunistic BLE scans.
-     */
-    @MediumTest
-    public void testOpportunisticScan() {
-        if (!isBleSupported()) {
-            return;
-        }
-        ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
-                .build();
-        BleScanCallback emptyScanCallback = new BleScanCallback();
-
-        // No scans are really started with opportunistic scans only.
-        mScanner.startScan(Collections.<ScanFilter>emptyList(), opportunisticScanSettings,
-                emptyScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
-        assertTrue(emptyScanCallback.getScanResults().isEmpty());
-
-        BleScanCallback regularScanCallback = new BleScanCallback();
-        ScanSettings regularScanSettings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
-        List<ScanFilter> filters = new ArrayList<>();
-        ScanFilter filter = createScanFilter();
-        if (filter != null) {
-            filters.add(filter);
-        } else {
-            Log.d(TAG, "no appropriate filter can be set");
-        }
-        mScanner.startScan(filters, regularScanSettings, regularScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
-        // With normal BLE scan client, opportunistic scan client will get scan results.
-        assertTrue("opportunistic scan results shouldn't be empty",
-                !emptyScanCallback.getScanResults().isEmpty());
-
-        // No more scan results for opportunistic scan clients once the normal BLE scan clients
-        // stops.
-        mScanner.stopScan(regularScanCallback);
-        // In case we got scan results before scan was completely stopped.
-        sleep(1000);
-        emptyScanCallback.clear();
-        sleep(SCAN_DURATION_MILLIS);
-        assertTrue("opportunistic scan shouldn't have scan results",
-                emptyScanCallback.getScanResults().isEmpty());
-    }
+//    /**
+//     * Test of opportunistic BLE scans.
+//     * Temporarily disable this test because it is interfered by the GmsCore;
+//     * it fails when it obtains results from GmsCore explicit scan.
+//     * TODO(b/70865144): re-enable this test.
+//     */
+//    @MediumTest
+//    public void testOpportunisticScan() {
+//        if (!isBleSupported()) {
+//            return;
+//        }
+//        ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
+//                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
+//                .build();
+//        BleScanCallback emptyScanCallback = new BleScanCallback();
+//        assertTrue("opportunistic scan shouldn't have scan results",
+//                emptyScanCallback.getScanResults().isEmpty());
+//
+//        // No scans are really started with opportunistic scans only.
+//        mScanner.startScan(Collections.<ScanFilter>emptyList(), opportunisticScanSettings,
+//                emptyScanCallback);
+//        sleep(SCAN_DURATION_MILLIS);
+//        Log.d(TAG, "result: " + emptyScanCallback.getScanResults());
+//        assertTrue("opportunistic scan shouldn't have scan results",
+//                emptyScanCallback.getScanResults().isEmpty());
+//
+//        BleScanCallback regularScanCallback = new BleScanCallback();
+//        ScanSettings regularScanSettings = new ScanSettings.Builder()
+//                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+//        List<ScanFilter> filters = new ArrayList<>();
+//        ScanFilter filter = createScanFilter();
+//        if (filter != null) {
+//            filters.add(filter);
+//        } else {
+//            Log.d(TAG, "no appropriate filter can be set");
+//        }
+//        mScanner.startScan(filters, regularScanSettings, regularScanCallback);
+//        sleep(SCAN_DURATION_MILLIS);
+//        // With normal BLE scan client, opportunistic scan client will get scan results.
+//        assertTrue("opportunistic scan results shouldn't be empty",
+//                !emptyScanCallback.getScanResults().isEmpty());
+//
+//        // No more scan results for opportunistic scan clients once the normal BLE scan clients
+//        // stops.
+//        mScanner.stopScan(regularScanCallback);
+//        // In case we got scan results before scan was completely stopped.
+//        sleep(SCAN_STOP_TIMEOUT);
+//        emptyScanCallback.clear();
+//        sleep(SCAN_DURATION_MILLIS);
+//        assertTrue("opportunistic scan shouldn't have scan results",
+//                emptyScanCallback.getScanResults().isEmpty());
+//    }
 
     /**
      * Test case for BLE Batch scan.
@@ -370,7 +381,7 @@
         mScanner.startScan(regularLeScanCallback);
         sleep(SCAN_DURATION_MILLIS);
         mScanner.stopScan(regularLeScanCallback);
-        sleep(1000);
+        sleep(SCAN_STOP_TIMEOUT);
         return regularLeScanCallback.getScanResults();
     }
 
diff --git a/tests/tests/calendarcommon/Android.mk b/tests/tests/calendarcommon/Android.mk
index 18ae49e..bfd9f26 100644
--- a/tests/tests/calendarcommon/Android.mk
+++ b/tests/tests/calendarcommon/Android.mk
@@ -25,9 +25,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/calendarcommon/AndroidManifest.xml b/tests/tests/calendarcommon/AndroidManifest.xml
index 4f21b18..951404a 100644
--- a/tests/tests/calendarcommon/AndroidManifest.xml
+++ b/tests/tests/calendarcommon/AndroidManifest.xml
@@ -30,7 +30,7 @@
             android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
-    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15"></uses-sdk>
+    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="17"></uses-sdk>
 
 </manifest>
 
diff --git a/tests/tests/calendarcommon/AndroidTest.xml b/tests/tests/calendarcommon/AndroidTest.xml
index 9169b79..76efb9b 100644
--- a/tests/tests/calendarcommon/AndroidTest.xml
+++ b/tests/tests/calendarcommon/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Calendar test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/car/Android.mk b/tests/tests/car/Android.mk
index db12143..07cbd83 100644
--- a/tests/tests/car/Android.mk
+++ b/tests/tests/car/Android.mk
@@ -24,9 +24,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
 
-LOCAL_JAVA_LIBRARIES := android.car
+LOCAL_JAVA_LIBRARIES := android.car android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java b/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java
deleted file mode 100644
index 0f9c8f1..0000000
--- a/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java
+++ /dev/null
@@ -1,55 +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.car.cts;
-
-import android.car.Car;
-import android.car.media.CarAudioManager;
-import android.media.AudioAttributes;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.suitebuilder.annotation.SmallTest;
-
-
-@SmallTest
-@RequiresDevice
-/** Unit tests for {@link CarAudioManager}. */
-public class CarAudioManagerTest extends CarApiTestBase {
-    private static final String TAG = CarAudioManagerTest.class.getSimpleName();
-    private CarAudioManager mManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
-        assertNotNull(mManager);
-    }
-
-    public void testGetAudioAttributesForCarUsageForMusic() throws Exception {
-        AudioAttributes.Builder musicBuilder = new AudioAttributes.Builder();
-        musicBuilder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-                .setUsage(AudioAttributes.USAGE_MEDIA);
-
-        assertEquals(musicBuilder.build(), mManager.getAudioAttributesForCarUsage(
-                             CarAudioManager.CAR_AUDIO_USAGE_MUSIC));
-    }
-
-    public void testGetAudioAttributesForCarUsageForUnknown() throws Exception {
-        AudioAttributes.Builder unknownBuilder = new AudioAttributes.Builder();
-        unknownBuilder.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
-                .setUsage(AudioAttributes.USAGE_UNKNOWN);
-
-        assertEquals(unknownBuilder.build(), mManager.getAudioAttributesForCarUsage(10007));
-    }
-}
diff --git a/tests/tests/carrierapi/Android.mk b/tests/tests/carrierapi/Android.mk
index 176cb9e..3adac15 100644
--- a/tests/tests/carrierapi/Android.mk
+++ b/tests/tests/carrierapi/Android.mk
@@ -25,8 +25,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     compatibility-device-util \
-    junit \
-    legacy-android-test
+    junit
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -36,6 +35,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES += android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs telephony-common
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index e46d53a..204d339 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Carrier APIs test cases">
+    <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">
diff --git a/tests/tests/colormode/Android.mk b/tests/tests/colormode/Android.mk
index a506fa5..d589a4f 100644
--- a/tests/tests/colormode/Android.mk
+++ b/tests/tests/colormode/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/colormode/AndroidTest.xml b/tests/tests/colormode/AndroidTest.xml
index 9c09038..eb491c2 100644
--- a/tests/tests/colormode/AndroidTest.xml
+++ b/tests/tests/colormode/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/contactsproviderwipe/Android.mk b/tests/tests/contactsproviderwipe/Android.mk
index d843256..e1c9d44 100644
--- a/tests/tests/contactsproviderwipe/Android.mk
+++ b/tests/tests/contactsproviderwipe/Android.mk
@@ -28,7 +28,7 @@
     ctstestrunner \
     ub-uiautomator
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -39,6 +39,5 @@
 LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
-#include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/contactsproviderwipe/AndroidTest.xml b/tests/tests/contactsproviderwipe/AndroidTest.xml
index b12398b..3a03a1e 100644
--- a/tests/tests/contactsproviderwipe/AndroidTest.xml
+++ b/tests/tests/contactsproviderwipe/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Provider test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index e132908..39587eb 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -24,15 +24,15 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libnativecursorwindow_jni libnativehelper_compat_libc++
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs android.test.mock
 
 LOCAL_STATIC_JAVA_LIBRARIES :=  \
     compatibility-device-util \
     ctstestrunner \
     services.core \
     junit \
-    legacy-android-test \
-    truth-prebuilt
+    truth-prebuilt \
+    accountaccesslib
 
 LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
 
@@ -43,6 +43,18 @@
 
 # Resource unit tests use a private locale and some densities
 LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
+LOCAL_AAPT_FLAGS := \
+	-c cs \
+	-c fil,fil-rPH,fil-rSA \
+	-c fr,fr-rFR \
+	-c iw,iw-rIL \
+	-c kok,b+kok+419,b+kok+419+variant,b+kok+IN,b+kok+Knda,b+kok+Knda+419,b+kok+Knda+419+variant \
+	-c b+kok+variant \
+	-c mk,mk-rMK \
+	-c tl,tl-rPH \
+	-c tgl,tgl-rPH \
+	-c tlh \
+	-c xx,xx-rYY
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_MULTILIB := both
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index b191a11..ae78e52 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -29,6 +29,7 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.content.cts.permission.TEST_GRANTED" />
 
     <!-- Used for PackageManager test, don't delete this INTERNET permission -->
@@ -119,7 +120,8 @@
                 android:maxRecents="1"
                 android:multiArch="true"
                 android:name="android.content.cts.MockApplication"
-                android:supportsRtl="true">
+                android:supportsRtl="true"
+                android:appCategory="productivity">
         <activity android:name="android.content.cts.MockActivity">
             <meta-data android:name="android.app.alias"
                 android:resource="@xml/alias" />
@@ -288,6 +290,24 @@
                 android:process=":providerProcess">
         </provider>
 
+        <activity android:name="com.android.cts.content.StubActivity"/>
+
+        <service android:name="com.android.cts.content.NotAlwaysSyncableSyncService">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                android:resource="@xml/not_always_syncable_account_access_adapter" />
+        </service>
+
+        <service android:name="com.android.cts.content.AlwaysSyncableSyncService">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                android:resource="@xml/always_syncable_account_access_adapter" />
+        </service>
+
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 5cab0ac..1fd262f 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -31,6 +31,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsContentTestCases.apk" />
+        <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk
new file mode 100644
index 0000000..01df0b5
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk
@@ -0,0 +1,41 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    ctstestrunner \
+    accountaccesslib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSyncAccountAccessOtherCertTestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
new file mode 100644
index 0000000..e1fd828
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.content">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".StubActivity"/>
+
+        <service android:name=".AlwaysSyncableSyncService">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                   android:resource="@xml/syncadapter" />
+        </service>
+
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.content" />
+
+</manifest>
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidTest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidTest.xml
new file mode 100644
index 0000000..cc6da02
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/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 tests of sync adapters with different certs as the authenticator">
+    <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="CtsSyncAccountAccessOtherCertTestCases.apk" />
+        <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.cts.content" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
rename to tests/tests/content/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
new file mode 100644
index 0000000..a2d5353
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.content;
+
+import static com.android.cts.content.Utils.ALWAYS_SYNCABLE_AUTHORITY;
+import static com.android.cts.content.Utils.SYNC_TIMEOUT_MILLIS;
+import static com.android.cts.content.Utils.allowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.disallowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.getUiDevice;
+import static com.android.cts.content.Utils.hasDataConnection;
+import static com.android.cts.content.Utils.hasNotificationSupport;
+import static com.android.cts.content.Utils.isWatch;
+import static com.android.cts.content.Utils.requestSync;
+import static com.android.cts.content.Utils.withAccount;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.SyncRequest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.util.regex.Pattern;
+
+/**
+ * Tests whether a sync adapter can access accounts.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CtsSyncAccountAccessOtherCertTestCases {
+    private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec
+    private static final String LOG_TAG =
+            CtsSyncAccountAccessOtherCertTestCases.class.getSimpleName();
+
+    private static final Pattern PERMISSION_REQUESTED = Pattern.compile(
+            "Permission Requested|Permission requested");
+    private static final Pattern ALLOW_SYNC = Pattern.compile("ALLOW|Allow");
+
+    @Rule
+    public final TestRule mFlakyTestRule = new FlakyTestRule(3);
+
+    @Rule
+    public final ActivityTestRule<StubActivity> activity = new ActivityTestRule(StubActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        allowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        disallowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @Test
+    public void testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount() throws Exception {
+        assumeTrue(hasDataConnection());
+        assumeTrue(hasNotificationSupport());
+        assumeFalse(isRunningInVR());
+
+        // If running in a test harness the Account Manager never denies access to an account. Hence
+        // the permission request will not trigger. b/72114924
+        assumeFalse(ActivityManager.isRunningInTestHarness());
+
+        try (AutoCloseable ignored = withAccount(activity.getActivity())) {
+            AbstractThreadedSyncAdapter adapter = AlwaysSyncableSyncService.getInstance(
+                    activity.getActivity()).setNewDelegate();
+
+            SyncRequest request = requestSync(ALWAYS_SYNCABLE_AUTHORITY);
+            Log.i(LOG_TAG, "Sync requested " + request);
+
+            Thread.sleep(SYNC_TIMEOUT_MILLIS);
+            verify(adapter, never()).onPerformSync(any(), any(), any(), any(), any());
+            Log.i(LOG_TAG, "Did not get onPerformSync");
+
+            UiDevice uiDevice = getUiDevice();
+            if (isWatch()) {
+                UiObject2 notification = findPermissionNotificationInStream(uiDevice);
+                notification.click();
+            } else {
+                uiDevice.openNotification();
+                try {
+                    UiObject2 permissionRequest = uiDevice.wait(
+                            Until.findObject(By.text(PERMISSION_REQUESTED)), UI_TIMEOUT_MILLIS);
+
+                    permissionRequest.click();
+                } catch (Throwable t) {
+                    ByteArrayOutputStream os = new ByteArrayOutputStream();
+                    getUiDevice().dumpWindowHierarchy(os);
+
+                    Log.w(LOG_TAG, "Window hierarchy:");
+                    for (String line : os.toString("UTF-8").split("\n")) {
+                        Log.w(LOG_TAG, line);
+
+                        // Do not overwhelm logging
+                        Thread.sleep(10);
+                    }
+
+                    throw  t;
+                }
+            }
+
+            uiDevice.wait(Until.findObject(By.text(ALLOW_SYNC)), UI_TIMEOUT_MILLIS).click();
+
+            ContentResolver.requestSync(request);
+
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(),
+                    any());
+            Log.i(LOG_TAG, "Got onPerformSync");
+        }
+    }
+
+    private UiObject2 findPermissionNotificationInStream(UiDevice uiDevice) {
+        uiDevice.pressHome();
+        swipeUp(uiDevice);
+        if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
+          return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
+        }
+        for (int i = 0; i < 100; i++) {
+          if (!swipeUp(uiDevice)) {
+            // We have reached the end of the stream and not found the target.
+            break;
+          }
+          if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
+            return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
+          }
+        }
+        return null;
+    }
+
+    private boolean swipeUp(UiDevice uiDevice) {
+        int width = uiDevice.getDisplayWidth();
+        int height = uiDevice.getDisplayHeight();
+        return uiDevice.swipe(
+            width / 2 /* startX */,
+            height - 1 /* startY */,
+            width / 2 /* endX */,
+            1 /* endY */,
+            50 /* numberOfSteps */);
+    }
+
+    private boolean isRunningInVR() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        return ((context.getResources().getConfiguration().uiMode &
+                 Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_VR_HEADSET);
+    }
+}
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/Android.mk b/tests/tests/content/SyncAccountAccessStubs/Android.mk
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/Android.mk
rename to tests/tests/content/SyncAccountAccessStubs/Android.mk
diff --git a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
new file mode 100644
index 0000000..a0dee84
--- /dev/null
+++ b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.stub">
+
+    <application>
+        <service
+                android:name=".StubAuthenticator">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator"/>
+            </intent-filter>
+            <meta-data
+                android:name="android.accounts.AccountAuthenticator"
+                android:resource="@xml/authenticator" />
+        </service>
+
+        <provider
+            android:name=".StubProvider"
+            android:authorities="com.android.cts.stub.provider"
+            android:exported="true"
+            android:syncable="true">
+        </provider>
+
+        <provider
+            android:name=".StubProvider2"
+            android:authorities="com.android.cts.stub.provider2"
+            android:exported="true"
+            android:syncable="true">
+        </provider>
+
+    </application>
+
+</manifest>
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/res/xml/authenticator.xml b/tests/tests/content/SyncAccountAccessStubs/res/xml/authenticator.xml
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/res/xml/authenticator.xml
rename to tests/tests/content/SyncAccountAccessStubs/res/xml/authenticator.xml
diff --git a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
new file mode 100644
index 0000000..0275fa9
--- /dev/null
+++ b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.stub;
+
+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 StubAuthenticator extends Service {
+    public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
+
+    private static long sNumAccountsAdded;
+    private Authenticator mAuthenticator;
+
+    @Override
+    public void onCreate() {
+        mAuthenticator = new Authenticator(this);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mAuthenticator.getIBinder();
+    }
+
+    public class Authenticator extends AbstractAccountAuthenticator {
+        public Authenticator(Context context) {
+            super(context);
+            removeAccounts();
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response,
+                String accountType) {
+            return null;
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response,
+                String accountType, String tokenType, String[] strings,
+                Bundle bundle) throws NetworkErrorException {
+            AccountManager accountManager = getSystemService(AccountManager.class);
+
+            String accountName = "foo" + sNumAccountsAdded;
+            sNumAccountsAdded++;
+
+            accountManager.addAccountExplicitly(new Account(accountName, accountType), "bar", null);
+
+            Bundle result = new Bundle();
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+            response.onResult(result);
+
+            return null;
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response,
+                Account account, Bundle bundle) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response,
+                Account account, String type, Bundle bundle) throws NetworkErrorException {
+            if (TOKEN_TYPE_REMOVE_ACCOUNTS.equals(type)) {
+                removeAccounts();
+            }
+            return null;
+        }
+
+        @Override
+        public String getAuthTokenLabel(String tokenName) {
+            return null;
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response,
+                Account account, String tokenType, Bundle bundle)
+                throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response,
+                Account account, String[] options) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
+                Account account) throws NetworkErrorException {
+            Bundle result = new Bundle();
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+            return result;
+        }
+
+        private void removeAccounts() {
+            AccountManager accountManager = getSystemService(AccountManager.class);
+            for (Account account : accountManager.getAccounts()) {
+                accountManager.removeAccountExplicitly(account);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
rename to tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
diff --git a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider2.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider2.java
new file mode 100644
index 0000000..0ecb23b
--- /dev/null
+++ b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider2.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 com.android.cts.stub;
+
+public class StubProvider2 extends StubProvider {
+}
diff --git a/tests/tests/content/assets/subdir/subdir_text.txt b/tests/tests/content/assets/subdir/subdir_text.txt
new file mode 100644
index 0000000..80034a1
--- /dev/null
+++ b/tests/tests/content/assets/subdir/subdir_text.txt
@@ -0,0 +1 @@
+subdir
diff --git a/tests/tests/content/fonts_readme.txt b/tests/tests/content/fonts_readme.txt
new file mode 100644
index 0000000..f0de576
--- /dev/null
+++ b/tests/tests/content/fonts_readme.txt
@@ -0,0 +1,15 @@
+All fonts included in this project follow the below copyright and licensing:
+
+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.
\ No newline at end of file
diff --git a/tests/tests/content/lib/Android.mk b/tests/tests/content/lib/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/tests/tests/content/lib/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/tests/tests/content/lib/accountaccess/Android.mk b/tests/tests/content/lib/accountaccess/Android.mk
new file mode 100644
index 0000000..2832452
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := accountaccesslib
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target \
+                               ub-uiautomator \
+                               compatibility-device-util
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/AlwaysSyncableSyncService.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/AlwaysSyncableSyncService.java
new file mode 100644
index 0000000..80c496a
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/AlwaysSyncableSyncService.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.content;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class AlwaysSyncableSyncService extends Service {
+    private static final Object sLock = new Object();
+    private static SyncAdapter sInstance;
+
+    public static SyncAdapter getInstance(Context context) {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new SyncAdapter(context.getApplicationContext(), false);
+            }
+        }
+
+        return sInstance;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return getInstance(this).getSyncAdapterBinder();
+    }
+}
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java
new file mode 100644
index 0000000..2d41ad6
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java
@@ -0,0 +1,58 @@
+/*
+ * 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
+ */
+
+package com.android.cts.content;
+
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Rule for running flaky tests that runs the test up to attempt
+ * count and if one run succeeds reports the tests as passing.
+ */
+// TODO: Move this puppy in a common place, so ppl can use it.
+public class FlakyTestRule implements TestRule {
+    private static final String LOG_TAG = FlakyTestRule.class.getSimpleName();
+
+    private final int mAttemptCount;
+
+    public FlakyTestRule(int attemptCount) {
+        mAttemptCount = attemptCount;
+    }
+
+    @Override
+    public Statement apply(Statement statement, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                Throwable throwable = null;
+                for (int i = 0; i < mAttemptCount; i++) {
+                    try {
+                        statement.evaluate();
+                        return;
+                    } catch (Throwable t) {
+                        Log.e(LOG_TAG, "Test failed ", t);
+
+                        throwable = t;
+                    }
+                }
+                throw throwable;
+            };
+        };
+    }
+}
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/NotAlwaysSyncableSyncService.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/NotAlwaysSyncableSyncService.java
new file mode 100644
index 0000000..58524cd
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/NotAlwaysSyncableSyncService.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.content;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class NotAlwaysSyncableSyncService extends Service {
+    private static final Object sLock = new Object();
+    private static SyncAdapter sInstance;
+
+    public static SyncAdapter getInstance(Context context) {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new SyncAdapter(context.getApplicationContext(), false);
+            }
+        }
+
+        return sInstance;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return getInstance(this).getSyncAdapterBinder();
+    }
+}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/StubActivity.java
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java
rename to tests/tests/content/lib/accountaccess/src/com.android.cts.content/StubActivity.java
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java
new file mode 100644
index 0000000..a81d6cc
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.content;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+    private final Object mLock = new Object();
+    private AbstractThreadedSyncAdapter mDelegate;
+
+    public AbstractThreadedSyncAdapter setNewDelegate() {
+        AbstractThreadedSyncAdapter delegate = mock(AbstractThreadedSyncAdapter.class);
+        when(delegate.onUnsyncableAccount()).thenCallRealMethod();
+
+        synchronized (mLock) {
+            mDelegate = delegate;
+        }
+
+        return delegate;
+    }
+
+    public SyncAdapter(Context context, boolean autoInitialize) {
+        super(context, autoInitialize);
+    }
+
+    private AbstractThreadedSyncAdapter getCopyOfDelegate() {
+        synchronized (mLock) {
+            return mDelegate;
+        }
+    }
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+        AbstractThreadedSyncAdapter delegate = getCopyOfDelegate();
+
+        if (delegate != null) {
+            delegate.onPerformSync(account, extras, authority, provider, syncResult);
+        }
+    }
+
+    @Override
+    public boolean onUnsyncableAccount() {
+        AbstractThreadedSyncAdapter delegate = getCopyOfDelegate();
+
+        if (delegate == null) {
+            return true;
+        } else {
+            return delegate.onUnsyncableAccount();
+        }
+    }
+}
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/Utils.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/Utils.java
new file mode 100644
index 0000000..b2ff8c9
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/Utils.java
@@ -0,0 +1,132 @@
+package com.android.cts.content;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncRequest;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+
+public class Utils {
+    private static final String LOG_TAG = Utils.class.getSimpleName();
+
+    public static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
+    public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
+    public static final String SYNC_ACCOUNT_TYPE = "com.stub";
+    public static final String ALWAYS_SYNCABLE_AUTHORITY = "com.android.cts.stub.provider";
+    public static final String NOT_ALWAYS_SYNCABLE_AUTHORITY = "com.android.cts.stub.provider2";
+
+    public static boolean hasDataConnection() {
+        ConnectivityManager connectivityManager = getContext().getSystemService(
+                ConnectivityManager.class);
+        NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
+        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
+    }
+
+    public static boolean hasNotificationSupport() {
+        return !getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    public static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    public static boolean isWatch() {
+        return (getContext().getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
+    }
+
+    public static UiDevice getUiDevice() {
+        return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    public static void waitForSyncManagerAccountChangeUpdate() {
+        // Wait for the sync manager to be notified for the new account.
+        // Unfortunately, there is no way to detect this event, sigh...
+        SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
+    }
+
+    public static void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+        // Allow us to run in the background
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd deviceidle whitelist +" + getContext().getPackageName());
+        // Allow us to use data in the background
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
+    }
+
+    public static  void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+        // Allow us to run in the background
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd deviceidle whitelist -" + getContext().getPackageName());
+        // Allow us to use data in the background
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
+    }
+
+    public static class ClosableAccount implements AutoCloseable {
+        public final Account account;
+
+        private ClosableAccount(@NonNull Account account) {
+            this.account = account;
+        }
+
+        @Override
+        public void close() throws Exception {
+            AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+
+            accountManager.getAuthToken(account, TOKEN_TYPE_REMOVE_ACCOUNTS, null, false, null,
+                    null);
+        }
+    }
+
+    public static ClosableAccount withAccount(@NonNull Activity activity)
+            throws AuthenticatorException, OperationCanceledException, IOException {
+        AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+        Bundle result = accountManager.addAccount(SYNC_ACCOUNT_TYPE, null, null, null,
+                activity, null, null).getResult();
+        Account addedAccount = new Account(
+                result.getString(AccountManager.KEY_ACCOUNT_NAME),
+                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+        Log.i(LOG_TAG, "Added account " + addedAccount);
+
+        waitForSyncManagerAccountChangeUpdate();
+
+        return new ClosableAccount(addedAccount);
+    }
+
+    public static SyncRequest requestSync(String authority) {
+        Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
+        extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+        SyncRequest request = new SyncRequest.Builder()
+                .setSyncAdapter(null, authority)
+                .syncOnce()
+                .setExtras(extras)
+                .setExpedited(true)
+                .setManual(true)
+                .build();
+        ContentResolver.requestSync(request);
+
+        return request;
+    }
+}
diff --git a/tests/tests/content/res/drawable/fake_image_will_not_decode.jpg b/tests/tests/content/res/drawable/fake_image_will_not_decode.jpg
new file mode 100644
index 0000000..32f7531
--- /dev/null
+++ b/tests/tests/content/res/drawable/fake_image_will_not_decode.jpg
@@ -0,0 +1 @@
+lol not a jpg
\ No newline at end of file
diff --git a/tests/tests/content/res/font/sample_font_collection.ttc b/tests/tests/content/res/font/sample_font_collection.ttc
new file mode 100644
index 0000000..9252f3d
--- /dev/null
+++ b/tests/tests/content/res/font/sample_font_collection.ttc
Binary files differ
diff --git a/tests/tests/content/res/font/sample_ttc_family.xml b/tests/tests/content/res/font/sample_ttc_family.xml
new file mode 100644
index 0000000..ae9b63f
--- /dev/null
+++ b/tests/tests/content/res/font/sample_ttc_family.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:fontStyle="normal" android:font="@font/sample_font_collection" android:ttcIndex="0" />
+    <font android:fontStyle="italic" android:font="@font/sample_font_collection" android:ttcIndex="1" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/content/res/font/sample_variation_settings_family1.xml b/tests/tests/content/res/font/sample_variation_settings_family1.xml
new file mode 100644
index 0000000..c719fa9
--- /dev/null
+++ b/tests/tests/content/res/font/sample_variation_settings_family1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:font="@font/variable_width_dash_font" android:fontVariationSettings="'wdth' 100.0" />
+</font-family>
diff --git a/tests/tests/content/res/font/sample_variation_settings_family2.xml b/tests/tests/content/res/font/sample_variation_settings_family2.xml
new file mode 100644
index 0000000..94d4d79
--- /dev/null
+++ b/tests/tests/content/res/font/sample_variation_settings_family2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:font="@font/variable_width_dash_font" android:fontVariationSettings="'wdth' 500.0" />
+</font-family>
diff --git a/tests/tests/content/res/font/variable_width_dash_font.ttf b/tests/tests/content/res/font/variable_width_dash_font.ttf
new file mode 100644
index 0000000..f7a256a
--- /dev/null
+++ b/tests/tests/content/res/font/variable_width_dash_font.ttf
Binary files differ
diff --git a/tests/tests/content/res/font/variable_width_dash_font_source.ttx b/tests/tests/content/res/font/variable_width_dash_font_source.ttx
new file mode 100644
index 0000000..401fe0f
--- /dev/null
+++ b/tests/tests/content/res/font/variable_width_dash_font_source.ttx
@@ -0,0 +1,241 @@
+<?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.9">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="dash"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x81ee73dd"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Wed Sep 9 08:01:17 2015"/>
+    <modified value="Thu Mar 9 22:22:31 2017"/>
+    <xMin value="0"/>
+    <yMin value="0"/>
+    <xMax value="0"/>
+    <yMax value="0"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="500"/>
+    <minLeftSideBearing value="0"/>
+    <minRightSideBearing value="0"/>
+    <xMaxExtent 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"/>
+    <numberOfHMetrics value="1"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="2"/>
+    <maxPoints value="0"/>
+    <maxContours value="0"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="2"/>
+    <maxTwilightPoints value="12"/>
+    <maxStorage value="28"/>
+    <maxFunctionDefs value="119"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="61"/>
+    <maxSizeOfInstructions value="2967"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth 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="97"/>
+    <usLastCharIndex value="97"/>
+    <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="dash" width="500" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x2D" name="dash"/><!-- ASCII DASH -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour>
+        <pt x="100" y="-500" on="1"/>
+        <pt x="900" y="-500" on="1"/>
+        <pt x="900" y="1000" on="1"/>
+        <pt x="100" y="1000" on="1"/>
+      </contour>
+      <instructions/>
+    </TTGlyph>
+
+    <TTGlyph name="dash">
+      <contour>
+        <pt x="0" y="200" on="1"/>
+        <pt x="100" y="200" on="1"/>
+        <pt x="100" y="300" on="1"/>
+        <pt x="0" y="300" on="1"/>
+      </contour>
+    <instructions/>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      MultiAxisFont Test
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      MultiAxisFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      MultiAxisFontTest-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      MultiAxisFont Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      MultiAxisFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      MultiAxisFontTest-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-200"/>
+    <underlineThickness value="20"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+  <fvar>
+    <Axis>
+      <AxisTag>wdth</AxisTag>
+      <MinValue>100.0</MinValue>
+      <DefaultValue>100.0</DefaultValue>
+      <MaxValue>500.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+  </fvar>
+
+  <gvar>
+    <glyphVariations glyph="dash">
+      <tuple>
+        <coord axis="wdth" value="1.0"/>
+        <delta pt="0" x="0" y="0"/>
+        <delta pt="1" x="400" y="0"/>
+        <delta pt="2" x="400" y="0"/>
+        <delta pt="3" x="0" y="0"/>
+        <delta pt="4" x="-200" y="0"/>
+        <delta pt="5" x="200" y="0"/>
+      </tuple>
+    </glyphVariations>
+  </gvar>
+
+</ttFont>
diff --git a/tests/tests/content/res/mipmap/icon_background.png b/tests/tests/content/res/mipmap/icon_background.png
new file mode 100644
index 0000000..72a065c
--- /dev/null
+++ b/tests/tests/content/res/mipmap/icon_background.png
Binary files differ
diff --git a/tests/tests/content/res/mipmap/icon_recursive.xml b/tests/tests/content/res/mipmap/icon_recursive.xml
new file mode 100644
index 0000000..b44fe19
--- /dev/null
+++ b/tests/tests/content/res/mipmap/icon_recursive.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@mipmap/icon_background" />
+    <foreground android:drawable="@mipmap/icon_recursive" />
+</adaptive-icon>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml b/tests/tests/content/res/xml/always_syncable_account_access_adapter.xml
similarity index 100%
copy from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
copy to tests/tests/content/res/xml/always_syncable_account_access_adapter.xml
diff --git a/tests/tests/content/res/xml/not_always_syncable_account_access_adapter.xml b/tests/tests/content/res/xml/not_always_syncable_account_access_adapter.xml
new file mode 100644
index 0000000..bc2e9fd
--- /dev/null
+++ b/tests/tests/content/res/xml/not_always_syncable_account_access_adapter.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<sync-adapter
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:contentAuthority="com.android.cts.stub.provider2"
+    android:accountType="com.stub"
+    android:userVisible="false"
+    android:supportsUploading="false"
+    android:allowParallelSyncs="false"
+    android:isAlwaysSyncable="false">
+</sync-adapter>
diff --git a/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java b/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java
new file mode 100644
index 0000000..95af4ca
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.cts;
+
+import static com.android.cts.content.Utils.ALWAYS_SYNCABLE_AUTHORITY;
+import static com.android.cts.content.Utils.SYNC_TIMEOUT_MILLIS;
+import static com.android.cts.content.Utils.allowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.disallowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.hasDataConnection;
+import static com.android.cts.content.Utils.requestSync;
+import static com.android.cts.content.Utils.withAccount;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.AbstractThreadedSyncAdapter;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.cts.content.AlwaysSyncableSyncService;
+import com.android.cts.content.FlakyTestRule;
+import com.android.cts.content.StubActivity;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests whether a sync adapter can access accounts.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccountAccessSameCertTest {
+    @Rule
+    public final TestRule mFlakyTestTRule = new FlakyTestRule(3);
+
+    @Rule
+    public final ActivityTestRule<StubActivity> activity = new ActivityTestRule(StubActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        allowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        disallowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @Test
+    public void testAccountAccess_sameCertAsAuthenticatorCanSeeAccount() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        try (AutoCloseable ignored = withAccount(activity.getActivity())) {
+            AbstractThreadedSyncAdapter adapter = AlwaysSyncableSyncService.getInstance(
+                    activity.getActivity()).setNewDelegate();
+
+            requestSync(ALWAYS_SYNCABLE_AUTHORITY);
+
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(),
+                    any());
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index dbebbc0..2695fac 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -353,4 +353,18 @@
             assertDefaultHandlerValidPriority(intent);
         }
     }
+
+    public void testFingerprintEnrollStart() {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            assertCanBeHandled(new Intent(Settings.ACTION_FINGERPRINT_ENROLL));
+        }
+    }
+
+    public void testPictureInPictureSettings() {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
+            assertCanBeHandled(new Intent(Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS));
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
index e038117f..c5a7d7d 100644
--- a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
@@ -16,29 +16,45 @@
 
 package android.content.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.ClipData;
+import android.content.ClipData.Item;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
+import android.content.ClipboardManager.OnPrimaryClipChangedListener;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ClipData.Item;
 import android.net.Uri;
-import android.test.InstrumentationTestCase;
-import android.test.UiThreadTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-public class ClipboardManagerTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ClipboardManagerTest {
     private Context mContext;
+    private ClipboardManager mClipboardManager;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mClipboardManager = mContext.getSystemService(ClipboardManager.class);
     }
 
-    @UiThreadTest
+    @Test
     public void testSetGetText() {
-        ClipboardManager clipboardManager = makeClipboardManager();
+        ClipboardManager clipboardManager = mClipboardManager;
         clipboardManager.setText("Test Text 1");
         assertEquals("Test Text 1", clipboardManager.getText());
 
@@ -46,9 +62,9 @@
         assertEquals("Test Text 2", clipboardManager.getText());
     }
 
-    @UiThreadTest
+    @Test
     public void testHasPrimaryClip() {
-        ClipboardManager clipboardManager = makeClipboardManager();
+        ClipboardManager clipboardManager = mClipboardManager;
         if (clipboardManager.hasPrimaryClip()) {
             assertNotNull(clipboardManager.getPrimaryClip());
             assertNotNull(clipboardManager.getPrimaryClipDescription());
@@ -61,7 +77,7 @@
         assertTrue(clipboardManager.hasPrimaryClip());
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_plainText() {
         ClipData textData = ClipData.newPlainText("TextLabel", "Text");
         assertSetPrimaryClip(textData, "TextLabel",
@@ -69,7 +85,7 @@
                 new ExpectedClipItem("Text", null, null));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_intent() {
         Intent intent = new Intent(mContext, ClipboardManagerTest.class);
         ClipData intentData = ClipData.newIntent("IntentLabel", intent);
@@ -78,7 +94,7 @@
                 new ExpectedClipItem(null, intent, null));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_rawUri() {
         Uri uri = Uri.parse("http://www.google.com");
         ClipData uriData = ClipData.newRawUri("UriLabel", uri);
@@ -87,7 +103,7 @@
                 new ExpectedClipItem(null, null, uri));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_contentUri() {
         Uri contentUri = Uri.parse("content://cts/test/for/clipboardmanager");
         ClipData contentUriData = ClipData.newUri(mContext.getContentResolver(),
@@ -97,7 +113,7 @@
                 new ExpectedClipItem(null, null, contentUri));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_complexItem() {
         Intent intent = new Intent(mContext, ClipboardManagerTest.class);
         Uri uri = Uri.parse("http://www.google.com");
@@ -113,7 +129,7 @@
                 new ExpectedClipItem("Text", intent, uri));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_multipleItems() {
         Intent intent = new Intent(mContext, ClipboardManagerTest.class);
         Uri uri = Uri.parse("http://www.google.com");
@@ -129,7 +145,7 @@
                 new ExpectedClipItem(null, null, uri));
     }
 
-    @UiThreadTest
+    @Test
     public void testSetPrimaryClip_multipleMimeTypes() {
         ContentResolver contentResolver = mContext.getContentResolver();
 
@@ -184,6 +200,38 @@
                 new ExpectedClipItem(null, null, contentUri8));
     }
 
+    @Test
+    public void testPrimaryClipChangedListener() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mClipboardManager.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() {
+            @Override
+            public void onPrimaryClipChanged() {
+                latch.countDown();
+            }
+        });
+
+        final ClipData clipData = ClipData.newPlainText("TextLabel", "Text");
+        mClipboardManager.setPrimaryClip(clipData);
+
+        latch.await(5, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testClearPrimaryClip() {
+        final ClipData clipData = ClipData.newPlainText("TextLabel", "Text");
+        mClipboardManager.setPrimaryClip(clipData);
+        assertTrue(mClipboardManager.hasPrimaryClip());
+        assertTrue(mClipboardManager.hasText());
+        assertNotNull(mClipboardManager.getPrimaryClip());
+        assertNotNull(mClipboardManager.getPrimaryClipDescription());
+
+        mClipboardManager.clearPrimaryClip();
+        assertFalse(mClipboardManager.hasPrimaryClip());
+        assertFalse(mClipboardManager.hasText());
+        assertNull(mClipboardManager.getPrimaryClip());
+        assertNull(mClipboardManager.getPrimaryClipDescription());
+    }
+
     private class ExpectedClipItem {
         CharSequence mText;
         Intent mIntent;
@@ -200,7 +248,7 @@
             String expectedLabel,
             String[] expectedMimeTypes,
             ExpectedClipItem... expectedClipItems) {
-        ClipboardManager clipboardManager = makeClipboardManager();
+        ClipboardManager clipboardManager = mClipboardManager;
 
         clipboardManager.setPrimaryClip(clipData);
         assertTrue(clipboardManager.hasPrimaryClip());
@@ -223,7 +271,7 @@
                 expectedLabel, expectedMimeTypes);
     }
 
-    private void assertClipData(ClipData actualData, String expectedLabel,
+    private static void assertClipData(ClipData actualData, String expectedLabel,
             String[] expectedMimeTypes, ExpectedClipItem... expectedClipItems) {
         if (expectedClipItems != null) {
             assertEquals(expectedClipItems.length, actualData.getItemCount());
@@ -237,7 +285,7 @@
         assertClipDescription(actualData.getDescription(), expectedLabel, expectedMimeTypes);
     }
 
-    private void assertClipDescription(ClipDescription description, String expectedLabel,
+    private static void assertClipDescription(ClipDescription description, String expectedLabel,
             String... mimeTypes) {
         assertEquals(expectedLabel, description.getLabel());
         assertEquals(mimeTypes.length, description.getMimeTypeCount());
@@ -247,7 +295,7 @@
         }
     }
 
-    private void assertClipItem(ExpectedClipItem expectedItem, Item item) {
+    private static void assertClipItem(ExpectedClipItem expectedItem, Item item) {
         assertEquals(expectedItem.mText, item.getText());
         if (expectedItem.mIntent != null) {
             assertNotNull(item.getIntent());
@@ -260,8 +308,4 @@
             assertNull(item.getUri());
         }
     }
-
-    private ClipboardManager makeClipboardManager() {
-        return (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
-    }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index 0d9c56d..f84c771 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -17,7 +17,6 @@
 package android.content.cts;
 
 import android.content.Context;
-import android.content.cts.util.XmlUtils;
 import android.content.res.ColorStateList;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
@@ -25,12 +24,15 @@
 import android.content.res.XmlResourceParser;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.Handler;
+import android.os.Looper;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
 import android.view.WindowManager;
 
+import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
@@ -335,6 +337,21 @@
         assertValidFile(longFile);
     }
 
+    public void testMainLooper() throws Exception {
+        final Thread mainThread = Looper.getMainLooper().getThread();
+        final Handler handler = new Handler(mContext.getMainLooper());
+        handler.post(() -> {
+            assertEquals(mainThread, Thread.currentThread());
+        });
+    }
+
+    public void testMainExecutor() throws Exception {
+        final Thread mainThread = Looper.getMainLooper().getThread();
+        mContext.getMainExecutor().execute(() -> {
+            assertEquals(mainThread, Thread.currentThread());
+        });
+    }
+
     private void assertValidFile(File file) throws Exception {
         Log.d(TAG, "Checking " + file);
         assertTrue("Failed to create " + file, file.createNewFile());
@@ -345,12 +362,31 @@
         assertTrue("Failed to delete after stream " + file, file.delete());
     }
 
+    static void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException
+    {
+        int type;
+        while ((type=parser.next()) != parser.START_TAG
+                && type != parser.END_DOCUMENT) {
+            ;
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+                    ", expected " + firstElementName);
+        }
+    }
+
     private AttributeSet getAttributeSet(int resourceId) {
         final XmlResourceParser parser = getContext().getResources().getXml(
                 resourceId);
 
         try {
-            XmlUtils.beginDocument(parser, "RelativeLayout");
+            beginDocument(parser, "RelativeLayout");
         } catch (XmlPullParserException e) {
             e.printStackTrace();
         } catch (IOException e) {
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index 5f48260..408855d 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -40,6 +40,7 @@
 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;
@@ -57,8 +58,6 @@
  * Test {@link ContextWrapper}.
  */
 public class ContextWrapperTest extends AndroidTestCase {
-    private static final String PERMISSION_HARDWARE_TEST = "android.permission.HARDWARE_TEST";
-
     private static final String ACTUAL_RESULT = "ResultSetByReceiver";
 
     private static final String INTIAL_RESULT = "IntialResult";
@@ -80,10 +79,13 @@
     private final static String MOCK_ACTION1 = ACTION_BROADCAST_TESTORDER + "1";
     private final static String MOCK_ACTION2 = ACTION_BROADCAST_TESTORDER + "2";
 
-    public static final String PERMISSION_GRANTED = "android.content.cts.permission.TEST_GRANTED";
-    public static final String PERMISSION_DENIED = "android.content.cts.permission.TEST_DENIED";
+    // 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;
 
@@ -134,17 +136,6 @@
         new ContextWrapper(null);
     }
 
-    public void testEnforceCallingPermission() {
-        try {
-            mContextWrapper.enforceCallingPermission(
-                    PERMISSION_HARDWARE_TEST,
-                    "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 testSendOrderedBroadcast1() throws InterruptedException {
         final HighPriorityBroadcastReceiver highPriorityReceiver =
                 new HighPriorityBroadcastReceiver();
@@ -273,17 +264,6 @@
         mContextWrapper.unregisterReceiver(broadcastReceiver);
     }
 
-    public void testEnforceCallingOrSelfPermission() {
-        try {
-            mContextWrapper.enforceCallingOrSelfPermission(PERMISSION_HARDWARE_TEST,
-                    "enforceCallingOrSelfPermission is not working without possessing an IPC.");
-            fail("enforceCallingOrSelfPermission 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 testAccessWallpaper() throws IOException, InterruptedException {
         // set Wallpaper by contextWrapper#setWallpaper(Bitmap)
         Bitmap bitmap = Bitmap.createBitmap(20, 30, Bitmap.Config.RGB_565);
@@ -347,11 +327,11 @@
 
         // Test openOrCreateDatabase with null and actual factory
         mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME1,
-                ContextWrapper.MODE_PRIVATE, factory);
+                ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
         assertNotNull(mDatabase);
         mDatabase.close();
         mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME2,
-                ContextWrapper.MODE_PRIVATE, factory);
+                ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
         assertNotNull(mDatabase);
         mDatabase.close();
 
@@ -360,7 +340,7 @@
 
         // Test databaseList()
         List<String> list = Arrays.asList(mContextWrapper.databaseList());
-        assertEquals(4, list.size()); // Each database has a journal
+        assertEquals(2, list.size());
         assertTrue("1) database list: " + list, list.contains(DATABASE_NAME1));
         assertTrue("2) database list: " + list, list.contains(DATABASE_NAME2));
 
@@ -390,8 +370,8 @@
     public void testEnforceUriPermission2() {
         Uri uri = Uri.parse("content://ctstest");
         try {
-            mContextWrapper.enforceUriPermission(uri, PERMISSION_HARDWARE_TEST,
-                    PERMISSION_HARDWARE_TEST, Binder.getCallingPid(), Binder.getCallingUid(),
+            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.");
@@ -570,16 +550,96 @@
                 Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
     }
 
-    public void testEnforcePermission() {
+    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(
-                    PERMISSION_HARDWARE_TEST, Binder.getCallingPid(),
-                    Binder.getCallingUid(),
-                    "enforcePermission is not working without possessing an IPC.");
-            fail("enforcePermission is not working without possessing an IPC.");
+                    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) {
-            // If the function is ok, it should throw a SecurityException here
-            // because currently no IPC is handled by this process.
+            // Currently no IPC is handled by this process, this exception is expected
         }
     }
 
@@ -598,22 +658,17 @@
     public void testCheckUriPermission2() {
         Uri uri = Uri.parse("content://ctstest");
 
-        int retValue = mContextWrapper.checkUriPermission(uri, PERMISSION_HARDWARE_TEST,
-                PERMISSION_HARDWARE_TEST, Binder.getCallingPid(), 0,
+        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, PERMISSION_HARDWARE_TEST,
-                PERMISSION_HARDWARE_TEST, Binder.getCallingPid(), Binder.getCallingUid(),
+        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 testCheckCallingPermission() {
-        int retValue = mContextWrapper.checkCallingPermission(PERMISSION_HARDWARE_TEST);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
     public void testCheckCallingUriPermission() {
         Uri uri = Uri.parse("content://ctstest");
 
@@ -644,12 +699,6 @@
         assertSame(mContext.getPackageManager(), mContextWrapper.getPackageManager());
     }
 
-    public void testCheckCallingOrSelfPermission() {
-        int retValue = mContextWrapper.checkCallingOrSelfPermission(
-                "android.permission.SET_WALLPAPER");
-        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
-    }
-
     public void testSendBroadcast1() throws InterruptedException {
         final ResultReceiver receiver = new ResultReceiver();
 
@@ -693,27 +742,6 @@
         }
     }
 
-    public void testCheckPermission() {
-        // Test with root user, everything will be granted.
-        int returnValue = mContextWrapper.checkPermission(PERMISSION_HARDWARE_TEST, 1, 0);
-        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
-
-        // Test with non-root user, only included granted permission.
-        returnValue = mContextWrapper.checkPermission(PERMISSION_HARDWARE_TEST, 1, 1);
-        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
-
-        // Test with null permission.
-        try {
-            returnValue = mContextWrapper.checkPermission(null, 0, 0);
-            fail("checkPermission should not accept null permission");
-        } catch (IllegalArgumentException e) {
-        }
-
-        // Test with invalid uid and included granted permission.
-        returnValue = mContextWrapper.checkPermission("android.permission.SET_WALLPAPER", 1, -11);
-        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
-    }
-
     public void testGetSystemService() {
         // Test invalid service name
         assertNull(mContextWrapper.getSystemService("invalid"));
diff --git a/tests/tests/content/src/android/content/cts/DeferSyncTest.java b/tests/tests/content/src/android/content/cts/DeferSyncTest.java
new file mode 100644
index 0000000..457da10
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/DeferSyncTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.cts;
+
+import static com.android.cts.content.Utils.ALWAYS_SYNCABLE_AUTHORITY;
+import static com.android.cts.content.Utils.NOT_ALWAYS_SYNCABLE_AUTHORITY;
+import static com.android.cts.content.Utils.SYNC_TIMEOUT_MILLIS;
+import static com.android.cts.content.Utils.allowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.disallowSyncAdapterRunInBackgroundAndDataInBackground;
+import static com.android.cts.content.Utils.hasDataConnection;
+import static com.android.cts.content.Utils.requestSync;
+import static com.android.cts.content.Utils.withAccount;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.cts.content.AlwaysSyncableSyncService;
+import com.android.cts.content.FlakyTestRule;
+import com.android.cts.content.NotAlwaysSyncableSyncService;
+import com.android.cts.content.StubActivity;
+import com.android.cts.content.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(AndroidJUnit4.class)
+public class DeferSyncTest {
+    @Rule
+    public final TestRule flakyTestRule = new FlakyTestRule(3);
+
+    @Rule
+    public final ActivityTestRule<StubActivity> activity = new ActivityTestRule(StubActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        allowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        disallowSyncAdapterRunInBackgroundAndDataInBackground();
+    }
+
+    @Test
+    public void noSyncsWhenDeferred() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        AbstractThreadedSyncAdapter notAlwaysSyncableAdapter =
+                NotAlwaysSyncableSyncService.getInstance(activity.getActivity()).setNewDelegate();
+        AbstractThreadedSyncAdapter alwaysSyncableAdapter =
+                AlwaysSyncableSyncService.getInstance(activity.getActivity()).setNewDelegate();
+
+        when(alwaysSyncableAdapter.onUnsyncableAccount()).thenReturn(false);
+        when(notAlwaysSyncableAdapter.onUnsyncableAccount()).thenReturn(false);
+
+        try (Utils.ClosableAccount ignored = withAccount(activity.getActivity())) {
+            requestSync(NOT_ALWAYS_SYNCABLE_AUTHORITY);
+            requestSync(ALWAYS_SYNCABLE_AUTHORITY);
+
+            Thread.sleep(SYNC_TIMEOUT_MILLIS);
+
+            verify(notAlwaysSyncableAdapter, atLeast(1)).onUnsyncableAccount();
+            verify(notAlwaysSyncableAdapter, never()).onPerformSync(any(), any(), any(), any(),
+                    any());
+
+            verify(alwaysSyncableAdapter, atLeast(1)).onUnsyncableAccount();
+            verify(alwaysSyncableAdapter, never()).onPerformSync(any(), any(), any(), any(), any());
+        }
+    }
+
+    @Test
+    public void deferSyncAndMakeSyncable() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        AbstractThreadedSyncAdapter adapter = NotAlwaysSyncableSyncService.getInstance(
+                activity.getActivity()).setNewDelegate();
+        when(adapter.onUnsyncableAccount()).thenReturn(false);
+
+        try (Utils.ClosableAccount account = withAccount(activity.getActivity())) {
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onUnsyncableAccount();
+
+            // Enable the adapter by making the account/provider syncable
+            ContentResolver.setIsSyncable(account.account, NOT_ALWAYS_SYNCABLE_AUTHORITY, 1);
+            requestSync(NOT_ALWAYS_SYNCABLE_AUTHORITY);
+
+            ArgumentCaptor<Bundle> extrasCaptor = forClass(Bundle.class);
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(),
+                    extrasCaptor.capture(), any(), any(), any());
+
+            // As the adapter is made syncable, we should not get an initialization sync
+            assertFalse(
+                    extrasCaptor.getValue().containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+        }
+    }
+
+    @Test
+    public void deferSyncAndReportIsReady() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        AbstractThreadedSyncAdapter adapter = NotAlwaysSyncableSyncService.getInstance(
+                activity.getActivity()).setNewDelegate();
+        when(adapter.onUnsyncableAccount()).thenReturn(false);
+
+        try (Utils.ClosableAccount ignored = withAccount(activity.getActivity())) {
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onUnsyncableAccount();
+
+            // Enable the adapter by returning true from onNewAccount
+            when(adapter.onUnsyncableAccount()).thenReturn(true);
+            requestSync(NOT_ALWAYS_SYNCABLE_AUTHORITY);
+            verify(adapter, atLeast(1)).onUnsyncableAccount();
+
+            ArgumentCaptor<Bundle> extrasCaptor = forClass(Bundle.class);
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(),
+                    extrasCaptor.capture(), any(), any(), any());
+
+            // As the adapter is not syncable yet, we should get an initialization sync
+            assertTrue(extrasCaptor.getValue().getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+        }
+    }
+
+    @Test
+    public void deferSyncAndReportIsReadyAlwaysSyncable() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        AbstractThreadedSyncAdapter adapter = AlwaysSyncableSyncService.getInstance(
+                activity.getActivity()).setNewDelegate();
+        when(adapter.onUnsyncableAccount()).thenReturn(false);
+
+        try (Utils.ClosableAccount ignored = withAccount(activity.getActivity())) {
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onUnsyncableAccount();
+
+            // Enable the adapter by returning true from onNewAccount
+            when(adapter.onUnsyncableAccount()).thenReturn(true);
+            requestSync(ALWAYS_SYNCABLE_AUTHORITY);
+            verify(adapter, atLeast(1)).onUnsyncableAccount();
+
+            ArgumentCaptor<Bundle> extrasCaptor = forClass(Bundle.class);
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(),
+                    extrasCaptor.capture(), any(), any(), any());
+
+            // The adapter is always syncable, hence there is no init sync
+            assertFalse(
+                    extrasCaptor.getValue().containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+        }
+    }
+
+    @Test
+    public void onNewAccountForEachAccount() throws Exception {
+        assumeTrue(hasDataConnection());
+
+        AbstractThreadedSyncAdapter adapter = NotAlwaysSyncableSyncService.getInstance(
+                activity.getActivity()).setNewDelegate();
+        when(adapter.onUnsyncableAccount()).thenReturn(true, false);
+
+        try (Utils.ClosableAccount account1 = withAccount(activity.getActivity())) {
+            try (Utils.ClosableAccount account2 = withAccount(activity.getActivity())) {
+                verify(adapter, timeout(SYNC_TIMEOUT_MILLIS).atLeast(2)).onUnsyncableAccount();
+
+                // Exactly account should have gotten the init-sync. No further syncs happen as
+                // onNewAccount returns false again.
+                ArgumentCaptor<Bundle> extrasCaptor = forClass(Bundle.class);
+                verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(),
+                        extrasCaptor.capture(), any(), any(), any());
+                assertTrue(
+                        extrasCaptor.getValue().getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+            }
+        }
+    }
+
+}
diff --git a/tests/tests/content/src/android/content/cts/MockSyncAdapter.java b/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
index 957859e..c8b0bec 100644
--- a/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
+++ b/tests/tests/content/src/android/content/cts/MockSyncAdapter.java
@@ -19,6 +19,7 @@
 import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.ISyncAdapter;
+import android.content.ISyncAdapterUnsyncableAccountCallback;
 import android.content.ISyncContext;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -76,6 +77,12 @@
         this.mLatch = mLatch;
     }
 
+    @Override
+    public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb)
+            throws RemoteException {
+        cb.onUnsyncableAccountDone(true);
+    }
+
     public void startSync(ISyncContext syncContext, String authority, Account account,
             Bundle extras) throws RemoteException {
 
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index b4fcb31..7c9e538 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -22,6 +22,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.StrictMode;
+import android.os.StrictMode.ViolationInfo;
+import android.os.StrictMode.ViolationLogger;
 import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -32,6 +34,8 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test {@link SharedPreferences}.
@@ -322,28 +326,27 @@
         }
     }
 
-    public void testModeMultiProcess() {
+    public void testModeMultiProcess() throws InterruptedException {
         // Pre-load it.
         mContext.getSharedPreferences("multiprocessTest", 0);
 
         final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
             StrictMode.ThreadPolicy diskReadDeath =
-                    new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build();
+                    new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build();
             StrictMode.setThreadPolicy(diskReadDeath);
+            final CountDownLatch latch = new CountDownLatch(1);
+            StrictMode.setViolationLogger(info -> latch.countDown());
 
             // This shouldn't hit disk.  (it was already pre-loaded above)
             mContext.getSharedPreferences("multiprocessTest", 0);
+            boolean triggered = latch.await(1, TimeUnit.SECONDS);
+            assertFalse(triggered);
 
-            boolean didRead = false;
             // This SHOULD hit disk.  (multi-process flag is set)
-            try {
-                mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS);
-                fail();  // we shouldn't get here.
-            } catch (StrictMode.StrictModeViolation e) {
-                didRead = true;
-            }
-            assertTrue(didRead);
+            mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS);
+            triggered = latch.await(1, TimeUnit.SECONDS);
+            assertTrue(triggered);
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
         }
diff --git a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
index 6a06eb8..611dfb9 100644
--- a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
@@ -16,28 +16,51 @@
 
 package android.content.pm.cts;
 
+import static android.content.pm.ApplicationInfo.CATEGORY_MAPS;
+import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
+import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.LAST_APPLICATION_UID;
+
+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 static org.junit.Assert.assertTrue;
+
+import android.content.Context;
 import android.content.cts.R;
-
-
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.StringBuilderPrinter;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Test {@link ApplicationInfo}.
  */
-public class ApplicationInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ApplicationInfoTest {
+    private static final String SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME = "com.android.cts.stub";
+
     private ApplicationInfo mApplicationInfo;
     private String mPackageName;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mPackageName = getContext().getPackageName();
     }
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testConstructor() {
         ApplicationInfo info = new ApplicationInfo();
         // simple test to ensure packageName is copied by copy constructor
@@ -47,8 +70,9 @@
         assertEquals(info.packageName, copy.packageName);
     }
 
+    @Test
     public void testWriteToParcel() throws NameNotFoundException {
-        mApplicationInfo = mContext.getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
 
         Parcel p = Parcel.obtain();
         mApplicationInfo.writeToParcel(p, 0);
@@ -71,17 +95,20 @@
         assertEquals(mApplicationInfo.descriptionRes, info.descriptionRes);
     }
 
+    @Test
     public void testToString() {
         mApplicationInfo = new ApplicationInfo();
         assertNotNull(mApplicationInfo.toString());
     }
 
+    @Test
     public void testDescribeContents() throws NameNotFoundException {
-       mApplicationInfo = mContext.getPackageManager().getApplicationInfo(mPackageName, 0);
+       mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
 
         assertEquals(0, mApplicationInfo.describeContents());
     }
 
+    @Test
     public void testDump() {
         mApplicationInfo = new ApplicationInfo();
 
@@ -95,13 +122,73 @@
         assertTrue(sb.length() > 0);
     }
 
+    @Test
     public void testLoadDescription() throws NameNotFoundException {
-        mApplicationInfo = mContext.getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
 
-        assertNull(mApplicationInfo.loadDescription(mContext.getPackageManager()));
+        assertNull(mApplicationInfo.loadDescription(getContext().getPackageManager()));
 
         mApplicationInfo.descriptionRes = R.string.hello_world;
-        assertEquals(mContext.getResources().getString(R.string.hello_world),
-                mApplicationInfo.loadDescription(mContext.getPackageManager()));
+        assertEquals(getContext().getResources().getString(R.string.hello_world),
+                mApplicationInfo.loadDescription(getContext().getPackageManager()));
+    }
+
+    @Test
+    public void verifyOwnInfo() throws NameNotFoundException {
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+
+        assertEquals("android.content.cts.MockApplication", mApplicationInfo.name);
+        assertEquals(true, mApplicationInfo.hasRtlSupport());
+        assertEquals(CATEGORY_PRODUCTIVITY, mApplicationInfo.category);
+    }
+
+    @Test
+    public void verifyDefaultValues() throws NameNotFoundException {
+        // The application "com.android.cts.stub" does not have any attributes set
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(
+                SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, 0);
+
+        assertNull(mApplicationInfo.className);
+        assertNull(mApplicationInfo.permission);
+        assertEquals(SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, mApplicationInfo.packageName);
+        assertEquals(SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, mApplicationInfo.processName);
+        assertEquals(SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, mApplicationInfo.taskAffinity);
+        assertTrue(FIRST_APPLICATION_UID <= mApplicationInfo.uid
+                && LAST_APPLICATION_UID >= mApplicationInfo.uid);
+        assertEquals(0, mApplicationInfo.theme);
+        assertEquals(0, mApplicationInfo.requiresSmallestWidthDp);
+        assertEquals(0, mApplicationInfo.compatibleWidthLimitDp);
+        assertEquals(0, mApplicationInfo.largestWidthLimitDp);
+        assertNotNull(mApplicationInfo.sourceDir);
+        assertEquals(mApplicationInfo.sourceDir, mApplicationInfo.publicSourceDir);
+        assertNull(mApplicationInfo.splitSourceDirs);
+        assertArrayEquals(mApplicationInfo.splitSourceDirs, mApplicationInfo.splitPublicSourceDirs);
+        assertEquals("/data/user/0/" + SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME,
+                mApplicationInfo.dataDir);
+        assertEquals("/data/user_de/0/" + SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME,
+                mApplicationInfo.deviceProtectedDataDir);
+        assertEquals("/data/user/0/" + SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME,
+                mApplicationInfo.credentialProtectedDataDir);
+        assertNull(mApplicationInfo.sharedLibraryFiles);
+        assertNull(mApplicationInfo.classLoaderName);
+        assertNull(mApplicationInfo.splitClassLoaderNames);
+        assertTrue(mApplicationInfo.enabled);
+        assertEquals(1, mApplicationInfo.targetSandboxVersion);
+        assertNull(mApplicationInfo.manageSpaceActivityName);
+        assertEquals(0, mApplicationInfo.descriptionRes);
+        assertEquals(0, mApplicationInfo.uiOptions);
+        assertEquals(CATEGORY_UNDEFINED, mApplicationInfo.category);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void setOwnAppCategory() throws Exception {
+        getContext().getPackageManager().setApplicationCategoryHint(getContext().getPackageName(),
+                CATEGORY_MAPS);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void setAppCategoryByNotInstaller() throws Exception {
+        getContext().getPackageManager().setApplicationCategoryHint(
+                SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, CATEGORY_MAPS);
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index 886b4f5..9e4fc5c 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -63,7 +63,7 @@
 
     private void checkPkgInfoSame(PackageInfo expected, PackageInfo actual) {
         assertEquals(expected.packageName, actual.packageName);
-        assertEquals(expected.versionCode, actual.versionCode);
+        assertEquals(expected.getLongVersionCode(), actual.getLongVersionCode());
         assertEquals(expected.versionName, actual.versionName);
         assertEquals(expected.sharedUserId, actual.sharedUserId);
         assertEquals(expected.sharedUserLabel, actual.sharedUserLabel);
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 5140e69..edc7924 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -45,12 +45,12 @@
 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 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 =
                                 "android.content.pm.cts.activity.PMTEST_SERVICE";
-    private static final String PERMISSION_NAME = "android.permission.INTERNET";
+    private static final String GRANTED_PERMISSION_NAME = "android.permission.INTERNET";
+    private static final String NOT_GRANTED_PERMISSION_NAME = "android.permission.HARDWARE_TEST";
     private static final String ACTIVITY_NAME = "android.content.pm.cts.TestPmActivity";
     private static final String SERVICE_NAME = "android.content.pm.cts.TestPmService";
     private static final String RECEIVER_NAME = "android.content.pm.cts.PmTestReceiver";
@@ -230,7 +230,8 @@
         assertTrue(mPackageManager.getPackageGids(PACKAGE_NAME).length > 0);
 
         // Test getPermissionInfo
-        assertEquals(PERMISSION_NAME, mPackageManager.getPermissionInfo(PERMISSION_NAME, 0).name);
+        assertEquals(GRANTED_PERMISSION_NAME,
+                mPackageManager.getPermissionInfo(GRANTED_PERMISSION_NAME, 0).name);
 
         // Test getPermissionGroupInfo
         assertEquals(PERMISSIONGROUP_NAME, mPackageManager.getPermissionGroupInfo(
@@ -388,17 +389,6 @@
                 mPackageManager.getComponentEnabledSetting(componentName));
     }
 
-    public void testOpPermission() {
-        PermissionInfo permissionInfo = new PermissionInfo();
-        String permissionName = "android.content.cts.permission.TEST_DYNAMIC.ADD";
-        permissionInfo.name = permissionName;
-        permissionInfo.labelRes = R.string.permlab_testDynamic;
-        permissionInfo.nonLocalizedLabel = "Test Tree";
-
-        // TODO: Bug ID 1561181.
-        // Can't add permission in dynamic way
-    }
-
     public void testGetIcon() throws NameNotFoundException {
         assertNotNull(mPackageManager.getApplicationIcon(PACKAGE_NAME));
         assertNotNull(mPackageManager.getApplicationIcon(mPackageManager.getApplicationInfo(
@@ -415,11 +405,41 @@
         assertNotNull(mPackageManager.getDrawable(PACKAGE_NAME, iconRes, appInfo));
     }
 
-    public void testCheckMethods() {
+    public void testCheckSignaturesMatch() {
+        // 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,
-                CONTENT_PKG_NAME));
+                "com.android.cts.stub"));
+        // This package's signature should match its own signature.
+        assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(PACKAGE_NAME,
+                PACKAGE_NAME));
+    }
+
+    public void testCheckSignaturesNoMatch() {
+        // This test package's signature shouldn't match the system's signature.
+        assertEquals(PackageManager.SIGNATURE_NO_MATCH, mPackageManager.checkSignatures(
+                PACKAGE_NAME, "android"));
+    }
+
+    public void testCheckSignaturesUnknownPackage() {
+        assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, mPackageManager.checkSignatures(
+                PACKAGE_NAME, "this.package.does.not.exist"));
+    }
+
+    public void testCheckPermissionGranted() {
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                mPackageManager.checkPermission(PERMISSION_NAME, PACKAGE_NAME));
+                mPackageManager.checkPermission(GRANTED_PERMISSION_NAME, PACKAGE_NAME));
+    }
+
+    public void testCheckPermissionNotGranted() {
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                mPackageManager.checkPermission(NOT_GRANTED_PERMISSION_NAME, PACKAGE_NAME));
+    }
+
+    public void testCheckPermissionSystem() {
+        // Everything will be granted to the system.
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                mPackageManager.checkPermission(NOT_GRANTED_PERMISSION_NAME, "android"));
     }
 
     public void testResolveMethods() {
diff --git a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
index f5dc5ca..b617164 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
@@ -200,5 +200,7 @@
         assertEquals(out.getDeclaredLength(), mAssetFileDes.getDeclaredLength());
         assertEquals(out.getParcelFileDescriptor().getStatSize(),
                 mAssetFileDes.getParcelFileDescriptor().getStatSize());
+
+        parcel.recycle();
     }
 }
diff --git a/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java b/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
index 1387be0..a1845d7 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetManagerTest.java
@@ -15,8 +15,16 @@
  */
 package android.content.res.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
 import android.content.cts.R;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -25,67 +33,84 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.TypedValue;
 
 import java.io.BufferedReader;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.Arrays;
 import java.util.HashSet;
 
-
-public class AssetManagerTest extends AndroidTestCase{
+@RunWith(AndroidJUnit4.class)
+public class AssetManagerTest {
     private AssetManager mAssets;
+    private Context mContext;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    private Context getContext() {
+        return mContext;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
         mAssets = mContext.getAssets();
     }
 
     @SmallTest
-    public void testAssetOperations() throws IOException, XmlPullParserException {
-        final Resources res = getContext().getResources();
-        final TypedValue value = new TypedValue();
-        res.getValue(R.raw.text, value, true);
+    @Test
+    public void testAssetOperations() throws Exception {
         final String fileName = "text.txt";
-        InputStream inputStream = mAssets.open(fileName);
-        assertNotNull(inputStream);
         final String expect = "OneTwoThreeFourFiveSixSevenEightNineTen";
+
+        final TypedValue value = new TypedValue();
+        final Resources res = getContext().getResources();
+        res.getValue(R.raw.text, value, true);
+
+        InputStream inputStream = mAssets.open(fileName);
+        assertThat(inputStream).isNotNull();
         assertContextEquals(expect, inputStream);
+
         inputStream = mAssets.open(fileName, AssetManager.ACCESS_BUFFER);
-        assertNotNull(inputStream);
+        assertThat(inputStream).isNotNull();
         assertContextEquals(expect, inputStream);
 
         AssetFileDescriptor assetFileDes = mAssets.openFd(fileName);
-        assertNotNull(assetFileDes);
+        assertThat(assetFileDes).isNotNull();
         assertContextEquals(expect, assetFileDes.createInputStream());
+
         assetFileDes = mAssets.openNonAssetFd(value.string.toString());
-        assertNotNull(assetFileDes);
+        assertThat(assetFileDes).isNotNull();
         assertContextEquals(expect, assetFileDes.createInputStream());
+
         assetFileDes = mAssets.openNonAssetFd(value.assetCookie, value.string.toString());
-        assertNotNull(assetFileDes);
+        assertThat(assetFileDes).isNotNull();
         assertContextEquals(expect, assetFileDes.createInputStream());
 
         XmlResourceParser parser = mAssets.openXmlResourceParser("AndroidManifest.xml");
-        assertNotNull(parser);
+        assertThat(parser).isNotNull();
         XmlUtils.beginDocument(parser, "manifest");
         parser = mAssets.openXmlResourceParser(0, "AndroidManifest.xml");
-        assertNotNull(parser);
+        assertThat(parser).isNotNull();
         beginDocument(parser, "manifest");
 
         String[] files = mAssets.list("");
-        boolean result = false;
-        for (int i = 0; i < files.length; i++) {
-            if (files[i].equals(fileName)) {
-                result = true;
-                break;
-            }
-        }
-        assertTrue(result);
+        assertThat(files).isNotNull();
+
+        // We don't do an exact match because the framework can add asset files and this test
+        // would be too brittle.
+        assertThat(files).asList().containsAllOf(fileName, "subdir");
+
+        files = mAssets.list("subdir");
+        assertThat(files).isNotNull();
+        assertThat(files).asList().contains("subdir_text.txt");
+
+        // This directory doesn't exist.
+        assertThat(mAssets.list("__foo__bar__dir__")).asList().isEmpty();
 
         try {
             mAssets.open("notExistFile.txt", AssetManager.ACCESS_BUFFER);
@@ -115,12 +140,12 @@
             // expected
         }
 
-        assertNotNull(mAssets.getLocales());
-
+        assertThat(mAssets.getLocales()).isNotNull();
     }
 
     @SmallTest
-    public void testClose() throws IOException, XmlPullParserException {
+    @Test
+    public void testClose() throws Exception {
         final AssetManager assets = new AssetManager();
         assets.close();
 
@@ -136,6 +161,7 @@
     }
 
     @SmallTest
+    @Test
     public void testGetNonSystemLocales() {
         // This is the list of locales built into this test package. It is basically the locales
         // specified in the Android.mk files (assuming they have corresponding resources), plus the
@@ -166,9 +192,6 @@
             "xx-YY"
         };
 
-        final HashSet<String> KNOWN_LOCALES_SET =
-                new HashSet<String>(Arrays.asList(KNOWN_LOCALES));
-
         final String PSEUDO_OR_EMPTY_LOCALES[] = {
             "",
             "en-XA",
@@ -177,31 +200,26 @@
 
         String locales[] = mAssets.getNonSystemLocales();
         HashSet<String> localesSet = new HashSet<String>(Arrays.asList(locales));
-        for (String l : PSEUDO_OR_EMPTY_LOCALES) {
-            localesSet.remove(l);
-        }
-
-        assertEquals(KNOWN_LOCALES_SET, localesSet);
+        localesSet.removeAll(Arrays.asList(PSEUDO_OR_EMPTY_LOCALES));
+        assertThat(localesSet).containsExactlyElementsIn(Arrays.asList(KNOWN_LOCALES));
     }
 
     private void assertContextEquals(final String expect, final InputStream inputStream)
             throws IOException {
-        final BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
-        final String result = bf.readLine();
-        inputStream.close();
-        assertNotNull(result);
-        assertEquals(expect, result);
+        try (final BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream))) {
+            assertThat(bf.readLine()).isEqualTo(expect);
+        }
     }
 
-    private void beginDocument(final XmlPullParser parser,final  String firstElementName)
+    private void beginDocument(final XmlPullParser parser, final String firstElementName)
             throws XmlPullParserException, IOException {
         int type;
         while ((type = parser.next()) != XmlPullParser.START_TAG) {
         }
+
         if (type != XmlPullParser.START_TAG) {
             fail("No start tag found");
         }
-        assertEquals(firstElementName, parser.getName());
+        assertThat(firstElementName).isEqualTo(parser.getName());
     }
-
 }
diff --git a/tests/tests/content/src/android/content/res/cts/AssetManager_AssetInputStreamTest.java b/tests/tests/content/src/android/content/res/cts/AssetManager_AssetInputStreamTest.java
index 32c1a3a..32db789 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetManager_AssetInputStreamTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetManager_AssetInputStreamTest.java
@@ -15,114 +15,145 @@
  */
 package android.content.res.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 
 import android.content.res.AssetManager;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
 
-public class AssetManager_AssetInputStreamTest extends AndroidTestCase {
-    private AssetManager.AssetInputStream mAssetInputStream;
-    private final String CONTENT_STRING = "OneTwoThreeFourFiveSixSevenEightNineTen";
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAssetInputStream = (AssetManager.AssetInputStream)mContext.getAssets().open("text.txt");
+@SmallTest
+public class AssetManager_AssetInputStreamTest {
+    private static final byte[] EXPECTED_BYTES = "OneTwoThreeFourFiveSixSevenEightNineTen".getBytes(
+            StandardCharsets.UTF_8);
+    private InputStream mAssetInputStream;
+
+    @Before
+    public void setUp() throws Exception {
+        mAssetInputStream = InstrumentationRegistry.getContext().getAssets().open("text.txt");
     }
 
-    public void testClose() throws IOException {
+    @After
+    public void tearDown() throws Exception {
         mAssetInputStream.close();
+    }
 
+    @Test
+    public void testClose() throws Exception {
+        mAssetInputStream.close();
         try {
             mAssetInputStream.read();
-            fail("should throw exception");
-        } catch (NullPointerException e) {
+            fail("read after close should throw an exception");
+        } catch (IllegalStateException e) {
             // expected
         }
     }
 
+    @Test
     public void testGetAssetInt() {
+        AssetManager.AssetInputStream assetInputStream =
+                (AssetManager.AssetInputStream) mAssetInputStream;
         try {
             // getAssetInt is no longer supported.
-            mAssetInputStream.getAssetInt();
-            fail();
+            assetInputStream.getAssetInt();
+            fail("getAssetInt should throw an exception");
         } catch (UnsupportedOperationException expected) {
         }
     }
 
+    @Test
     public void testMarkReset() throws IOException {
         assertTrue(mAssetInputStream.markSupported());
-        final int readlimit = 10;
-        final byte[] bytes = CONTENT_STRING.getBytes();
-        for (int i = 0; i < readlimit; i++) {
-            assertEquals(bytes[i], mAssetInputStream.read());
+        for (int i = 0; i < 10; i++) {
+            assertEquals(EXPECTED_BYTES[i], mAssetInputStream.read());
         }
-        mAssetInputStream.mark(readlimit);
+        mAssetInputStream.mark(10);
         mAssetInputStream.reset();
-        for (int i = 0; i < readlimit; i++) {
-            assertEquals(bytes[i + readlimit], mAssetInputStream.read());
+        for (int i = 0; i < 10; i++) {
+            assertEquals(EXPECTED_BYTES[10 + i], mAssetInputStream.read());
         }
     }
 
-    public void testReadMethods() throws IOException {
-        // test available()
-        final byte[] bytes = CONTENT_STRING.getBytes();
-        int len = mAssetInputStream.available();
-        int end = -1;
-        assertEquals(CONTENT_STRING.length(), len);
-        for (int i = 0; i < len; i++) {
-            assertEquals(bytes[i], mAssetInputStream.read());
+    @Test
+    public void testSingleByteRead() throws Exception {
+        assertEquals(EXPECTED_BYTES.length, mAssetInputStream.available());
+        for (int i = 0; i < EXPECTED_BYTES.length; i++) {
+            assertEquals(EXPECTED_BYTES[i], mAssetInputStream.read());
+            assertEquals(EXPECTED_BYTES.length - i - 1, mAssetInputStream.available());
         }
-        assertEquals(end, mAssetInputStream.read());
 
-        // test read(byte[])
-        mAssetInputStream.reset();
-        int dataLength = 10;
-        byte[] data = new byte[dataLength];
-        int ret = mAssetInputStream.read(data);
-        assertEquals(dataLength, ret);
-        for (int i = 0; i < dataLength; i++) {
-            assertEquals(bytes[i], data[i]);
-        }
-        data = new byte[len - dataLength];
-        assertEquals(len - dataLength, mAssetInputStream.read(data));
-        for (int i = 0; i < len - dataLength; i++) {
-            assertEquals(bytes[i + dataLength], data[i]);
-        }
-        assertEquals(end, mAssetInputStream.read(data));
+        // Check end-of-file condition.
+        assertEquals(-1, mAssetInputStream.read());
+        assertEquals(0, mAssetInputStream.available());
+    }
 
-        // test read(bytep[], int, int)
-        mAssetInputStream.reset();
-        int offset = 0;
-        ret = mAssetInputStream.read(data, offset, dataLength);
-        assertEquals(dataLength, ret);
-        for (int i = offset; i < ret; i++) {
-            assertEquals(bytes[i], data[offset + i]);
+    @Test
+    public void testByteArrayRead() throws Exception {
+        byte[] buffer = new byte[10];
+        assertEquals(10, mAssetInputStream.read(buffer));
+        for (int i = 0; i < 10; i++) {
+            assertEquals(EXPECTED_BYTES[i], buffer[i]);
         }
-        mAssetInputStream.reset();
-        offset = 2;
-        ret = mAssetInputStream.read(data, offset, dataLength);
-        assertEquals(dataLength, ret);
-        for (int i = offset; i < ret; i++) {
-            assertEquals(bytes[i], data[offset + i]);
-        }
-        data = new byte[len + offset];
-        ret = mAssetInputStream.read(data, offset, len);
-        assertEquals(len - dataLength, ret);
-        for (int i = offset; i < ret; i++) {
-            assertEquals(bytes[i + dataLength], data[offset + i]);
-        }
-        assertEquals(end, mAssetInputStream.read(data, offset, len));
-        // test len is zero,
-        mAssetInputStream.reset();
-        assertEquals(0, mAssetInputStream.read(data, 0, 0));
-        // test skip(int)
-        int skipLenth = 8;
-        mAssetInputStream.reset();
-        mAssetInputStream.skip(skipLenth);
-        assertEquals(CONTENT_STRING.charAt(skipLenth), mAssetInputStream.read());
 
-        // test read(byte[] b), b is null
+        buffer = new byte[5];
+        assertEquals(5, mAssetInputStream.read(buffer));
+        for (int i = 0; i < 5; i++) {
+            assertEquals(EXPECTED_BYTES[10 + i], buffer[i]);
+        }
+
+        // Check end-of-file condition.
+        buffer = new byte[EXPECTED_BYTES.length - 15];
+        assertEquals(buffer.length, mAssetInputStream.read(buffer));
+        assertEquals(-1, mAssetInputStream.read(buffer));
+        assertEquals(0, mAssetInputStream.available());
+    }
+
+    @Test
+    public void testByteArrayReadOffset() throws Exception {
+        byte[] buffer = new byte[15];
+        assertEquals(10, mAssetInputStream.read(buffer, 0, 10));
+        assertEquals(EXPECTED_BYTES.length - 10, mAssetInputStream.available());
+        for (int i = 0; i < 10; i++) {
+            assertEquals(EXPECTED_BYTES[i], buffer[i]);
+        }
+
+        assertEquals(5, mAssetInputStream.read(buffer, 10, 5));
+        assertEquals(EXPECTED_BYTES.length - 15, mAssetInputStream.available());
+        for (int i = 0; i < 15; i++) {
+            assertEquals(EXPECTED_BYTES[i], buffer[i]);
+        }
+
+        // Check end-of-file condition.
+        buffer = new byte[EXPECTED_BYTES.length];
+        assertEquals(EXPECTED_BYTES.length - 15,
+                mAssetInputStream.read(buffer, 15, EXPECTED_BYTES.length - 15));
+        assertEquals(-1, mAssetInputStream.read(buffer, 0, 1));
+        assertEquals(0, mAssetInputStream.available());
+    }
+
+    @Test
+    public void testSkip() throws Exception {
+        assertEquals(8, mAssetInputStream.skip(8));
+        assertEquals(EXPECTED_BYTES.length - 8, mAssetInputStream.available());
+        assertEquals(EXPECTED_BYTES[8], mAssetInputStream.read());
+
+        // Check that skip respects the available space.
+        assertEquals(EXPECTED_BYTES.length - 8 - 1, mAssetInputStream.skip(1000));
+        assertEquals(0, mAssetInputStream.available());
+    }
+
+    @Test
+    public void testArgumentEdgeCases() throws Exception {
+        // test read(byte[]): byte[] is null
         try {
             mAssetInputStream.read(null);
             fail("should throw NullPointerException ");
@@ -130,34 +161,39 @@
             // expected
         }
 
+        // test read(byte[], int, int): byte[] is null
         try {
             mAssetInputStream.read(null, 0, mAssetInputStream.available());
             fail("should throw NullPointerException ");
         } catch (NullPointerException e) {
             // expected
         }
-        // test read(bytep[], int, int): offset is negative,
+
+        // test read(byte[]): byte[] is len 0
+        final int previousAvailable = mAssetInputStream.available();
+        assertEquals(0, mAssetInputStream.read(new byte[0]));
+        assertEquals(previousAvailable, mAssetInputStream.available());
+
+        // test read(byte[]): byte[] is len 0
+        assertEquals(0, mAssetInputStream.read(new byte[0], 0, 0));
+        assertEquals(previousAvailable, mAssetInputStream.available());
+
+        // test read(byte[], int, int): offset is negative
         try {
-            data = new byte[10];
+            byte[] data = new byte[10];
             mAssetInputStream.read(data, -1, mAssetInputStream.available());
             fail("should throw IndexOutOfBoundsException ");
         } catch (IndexOutOfBoundsException e) {
             // expected
         }
 
-        // test read(bytep[], int, int): len+offset greater than data length
+        // test read(byte[], int, int): len + offset greater than data length
         try {
-            data = new byte[10];
+            byte[] data = new byte[10];
             assertEquals(0, mAssetInputStream.read(data, 0, data.length + 2));
             fail("should throw IndexOutOfBoundsException ");
         } catch (IndexOutOfBoundsException e) {
+            // expected
         }
     }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mAssetInputStream.close();
-    }
-
 }
diff --git a/tests/tests/content/src/android/content/res/cts/ColorStateListTest.java b/tests/tests/content/src/android/content/res/cts/ColorStateListTest.java
index 2029b95..f3be862 100644
--- a/tests/tests/content/src/android/content/res/cts/ColorStateListTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ColorStateListTest.java
@@ -111,6 +111,8 @@
         final ColorStateList actual = ColorStateList.CREATOR.createFromParcel(parcel);
         assertEquals(c.isStateful(), actual.isStateful());
         assertEquals(c.getDefaultColor(), actual.getDefaultColor());
+
+        parcel.recycle();
     }
 
     @SmallTest
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index 7355437..4db1ee5 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -16,8 +16,6 @@
 
 package android.content.res.cts;
 
-import java.util.Locale;
-
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.LocaleList;
@@ -25,6 +23,8 @@
 import android.test.AndroidTestCase;
 import android.view.View;
 
+import java.util.Locale;
+
 public class ConfigurationTest extends AndroidTestCase {
 
     private Configuration mConfigDefault;
@@ -397,11 +397,11 @@
     }
 
     public void testWriteToParcel() {
-        assertWriteToParcel(createConfig((Locale) null), Parcel.obtain());
-        assertWriteToParcel(createConfig(new Locale("")), Parcel.obtain());
-        assertWriteToParcel(createConfig(Locale.JAPAN), Parcel.obtain());
-        assertWriteToParcel(createConfig(Locale.forLanguageTag("en-Shaw")), Parcel.obtain());
-        assertWriteToParcel(createConfig(LocaleList.forLanguageTags("fr,en-US")), Parcel.obtain());
+        assertWriteToParcel(createConfig((Locale) null));
+        assertWriteToParcel(createConfig(new Locale("")));
+        assertWriteToParcel(createConfig(Locale.JAPAN));
+        assertWriteToParcel(createConfig(Locale.forLanguageTag("en-Shaw")));
+        assertWriteToParcel(createConfig(LocaleList.forLanguageTags("fr,en-US")));
     }
 
     public void testSetLocale() {
@@ -732,11 +732,16 @@
         return config;
     }
 
-    private void assertWriteToParcel(Configuration config, Parcel parcel) {
-        config.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Configuration readConf = new Configuration();
-        readConf.readFromParcel(parcel);
-        assertEquals(config, readConf);
+    private void assertWriteToParcel(Configuration config) {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            config.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            Configuration readConf = new Configuration();
+            readConf.readFromParcel(parcel);
+            assertEquals(config, readConf);
+        } finally {
+            parcel.recycle();
+        }
     }
 }
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 7f5d6ce..d49803a 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -31,6 +31,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.Paint;
 import android.graphics.Typeface;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -209,6 +210,11 @@
         assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
     }
 
+    public void testGetColorStateListNull() {
+        // XML that's not a selector or gradient returns null
+        assertNull(mResources.getColorStateList(R.drawable.density_test));
+    }
+
     public void testGetColor() {
         try {
             mResources.getColor(-1);
@@ -313,6 +319,9 @@
         assertNotNull(draw);
         assertEquals(212 * targetDensity / defaultDensity, draw.getIntrinsicWidth(), 1);
         assertEquals(142 * targetDensity / defaultDensity, draw.getIntrinsicHeight(), 1);
+
+        // Some apps rely on the fact that this will return null (rather than throwing).
+        assertNull(mResources.getDrawable(R.drawable.fake_image_will_not_decode));
     }
 
     public void testGetDrawable_StackOverflowErrorDrawable() {
@@ -324,6 +333,15 @@
         }
     }
 
+    public void testGetDrawable_StackOverflowErrorDrawable_mipmap() {
+        try {
+            mResources.getDrawable(R.mipmap.icon_recursive);
+            fail("Failed at testGetDrawable_StackOverflowErrorDrawable_mipmap");
+        } catch (NotFoundException e) {
+            //expected
+        }
+    }
+
     public void testGetDrawableForDensity() {
         final Drawable ldpi = mResources.getDrawableForDensity(
                 R.drawable.density_test, DisplayMetrics.DENSITY_LOW);
@@ -788,6 +806,55 @@
         assertNotSame(Typeface.DEFAULT, font);
     }
 
+    private Typeface getLargerTypeface(String text, Typeface typeface1, Typeface typeface2) {
+        Paint p1 = new Paint();
+        p1.setTypeface(typeface1);
+        float width1 = p1.measureText(text);
+        Paint p2 = new Paint();
+        p2.setTypeface(typeface2);
+        float width2 = p2.measureText(text);
+
+        if (width1 > width2) {
+            return typeface1;
+        } else if (width1 < width2) {
+            return typeface2;
+        } else {
+            fail("The widths of the text should not be the same");
+            return null;
+        }
+    }
+
+    public void testGetFont_xmlFileWithTtc() {
+        // Here we test that building typefaces by indexing in font collections works correctly.
+        // We want to ensure that the built typefaces correspond to the fonts with the right index.
+        // sample_font_collection.ttc contains two fonts (with indices 0 and 1). The first one has
+        // glyph "a" of 3em width, and all the other glyphs 1em. The second one has glyph "b" of
+        // 3em width, and all the other glyphs 1em. Hence, we can compare the width of these
+        // glyphs to assert that ttc indexing works.
+        Typeface normalFont = mResources.getFont(R.font.sample_ttc_family);
+        assertNotNull(normalFont);
+        Typeface italicFont = Typeface.create(normalFont, Typeface.ITALIC);
+        assertNotNull(italicFont);
+
+        assertEquals(getLargerTypeface("a", normalFont, italicFont), normalFont);
+        assertEquals(getLargerTypeface("b", normalFont, italicFont), italicFont);
+    }
+
+    public void testGetFont_xmlFileWithVariationSettings() {
+        // Here we test that specifying variation settings for fonts in XMLs works.
+        // We build typefaces from two families containing one font each, using the same font
+        // resource, but having different values for the 'wdth' tag. Then we measure the painted
+        // text to ensure that the tag affects the text width. The font resource used supports
+        // the 'wdth' axis for the dash (-) character.
+        Typeface typeface1 = mResources.getFont(R.font.sample_variation_settings_family1);
+        assertNotNull(typeface1);
+        Typeface typeface2 = mResources.getFont(R.font.sample_variation_settings_family2);
+        assertNotNull(typeface2);
+
+        assertNotSame(typeface1, typeface2);
+        assertEquals(getLargerTypeface("-", typeface1, typeface2), typeface2);
+    }
+
     public void testGetFont_invalidXmlFile() {
         try {
             assertNull(mResources.getFont(R.font.invalid_xmlfamily));
diff --git a/tests/tests/database/Android.mk b/tests/tests/database/Android.mk
index 0e24c15..9bec237 100644
--- a/tests/tests/database/Android.mk
+++ b/tests/tests/database/Android.mk
@@ -26,10 +26,9 @@
     ctstestrunner \
     ctstestrunner \
     ub-uiautomator \
-    junit \
-    legacy-android-test
+    junit
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/database/AndroidTest.xml b/tests/tests/database/AndroidTest.xml
index 62338f1..bab4a44 100644
--- a/tests/tests/database/AndroidTest.xml
+++ b/tests/tests/database/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Database 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" />
diff --git a/tests/tests/database/src/android/database/cts/CursorWindowTest.java b/tests/tests/database/src/android/database/cts/CursorWindowTest.java
index 65ce3fd..2d7473e 100644
--- a/tests/tests/database/src/android/database/cts/CursorWindowTest.java
+++ b/tests/tests/database/src/android/database/cts/CursorWindowTest.java
@@ -21,16 +21,31 @@
 import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteException;
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Random;
 
-public class CursorWindowTest extends AndroidTestCase {
+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;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CursorWindowTest {
 
     private static final String TEST_STRING = "Test String";
 
+    @Test
     public void testWriteCursorToWindow() throws Exception {
         // create cursor
         String[] colNames = new String[]{"_id", "name", "number", "profit"};
@@ -75,6 +90,7 @@
         assertEquals(0, window.getNumRows());
     }
 
+    @Test
     public void testNull() {
         CursorWindow window = getOneByOneWindow();
 
@@ -82,10 +98,11 @@
         assertTrue(window.putNull(0, 0));
         assertNull(window.getString(0, 0));
         assertEquals(0, window.getLong(0, 0));
-        assertEquals(0.0, window.getDouble(0, 0));
+        assertEquals(0.0, window.getDouble(0, 0), 0.0);
         assertNull(window.getBlob(0, 0));
     }
 
+    @Test
     public void testEmptyString() {
         CursorWindow window = getOneByOneWindow();
 
@@ -93,9 +110,10 @@
         assertTrue(window.putString("", 0, 0));
         assertEquals("", window.getString(0, 0));
         assertEquals(0, window.getLong(0, 0));
-        assertEquals(0.0, window.getDouble(0, 0));
+        assertEquals(0.0, window.getDouble(0, 0), 0.0);
     }
 
+    @Test
     public void testConstructors() {
         int TEST_NUMBER = 5;
         CursorWindow cursorWindow;
@@ -121,8 +139,11 @@
         cursorWindow = CursorWindow.CREATOR.createFromParcel(parcel);
         assertEquals(TEST_NUMBER, cursorWindow.getStartPosition());
         assertEquals(TEST_STRING, cursorWindow.getString(TEST_NUMBER, 0));
+
+        parcel.recycle();
     }
 
+    @Test
     public void testDataStructureOperations() {
         CursorWindow cursorWindow = new CursorWindow(true);
 
@@ -175,6 +196,7 @@
         }
     }
 
+    @Test
     public void testAccessDataValues() {
         final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL;
         final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER;
@@ -214,8 +236,8 @@
         assertEquals(0, cursorWindow.getLong(0, 0));
         assertEquals(0, cursorWindow.getInt(0, 0));
         assertEquals(0, cursorWindow.getShort(0, 0));
-        assertEquals(0.0, cursorWindow.getDouble(0, 0));
-        assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.00000001f);
+        assertEquals(0.0, cursorWindow.getDouble(0, 0), 0.0);
+        assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.0);
         assertFalse(cursorWindow.isNull(0, 0));
         assertFalse(cursorWindow.isBlob(0, 0));
 
@@ -226,8 +248,8 @@
         assertEquals(0, cursorWindow.getLong(0, 1));
         assertEquals(0, cursorWindow.getInt(0, 1));
         assertEquals(0, cursorWindow.getShort(0, 1));
-        assertEquals(0.0, cursorWindow.getDouble(0, 1));
-        assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.00000001f);
+        assertEquals(0.0, cursorWindow.getDouble(0, 1), 0.0);
+        assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.0);
         assertNull(cursorWindow.getBlob(0, 1));
         assertTrue(cursorWindow.isNull(0, 1));
         // If the field is null, isBlob will return true.
@@ -239,8 +261,8 @@
         assertEquals(NUMBER_INTEGER, cursorWindow.getInt(0, 2));
         assertEquals(Long.toString(NUMBER_LONG_INTEGER), cursorWindow.getString(0, 2));
         assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 2));
-        assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 2), 0.00000001f);
-        assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 2), 0.00000001);
+        assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 2), 0.0);
+        assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 2), 0.0);
         try {
             cursorWindow.getBlob(0, 2);
             fail("Can't get Blob from a Integer value.");
@@ -259,8 +281,8 @@
         assertEquals(NUMBER_FLOAT_SCIENCE_STRING2.substring(0, 6), cursorWindow.getString(0, 3)
                 .substring(0, 6));
         assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 3));
-        assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 3), 0.00000001f);
-        assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 3), 0.00000001);
+        assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 3), 0.0);
+        assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 3), 0.0);
         try {
             cursorWindow.getBlob(0, 3);
             fail("Can't get Blob from a Double value.");
@@ -279,6 +301,7 @@
         assertTrue(cursorWindow.isBlob(0, 4));
     }
 
+    @Test
     public void testCopyStringToBuffer() {
         int DEFAULT_ARRAY_LENGTH = 64;
         String baseString = "0123456789";
@@ -314,6 +337,7 @@
         assertEquals(expectedString.length(), charArrayBuffer.data.length);
     }
 
+    @Test
     public void testAccessStartPosition() {
         final int TEST_POSITION_1 = 0;
         final int TEST_POSITION_2 = 3;
@@ -343,6 +367,7 @@
         }
     }
 
+    @Test
     public void testClearAndOnAllReferencesReleased() {
         MockCursorWindow cursorWindow = new MockCursorWindow(true);
 
@@ -369,11 +394,43 @@
         assertTrue(cursorWindow.hasReleasedAllReferences());
     }
 
+    @Test
     public void testDescribeContents() {
         CursorWindow cursorWindow = new CursorWindow(true);
         assertEquals(0, cursorWindow.describeContents());
     }
 
+    @Test
+    public void testDefaultCursorWindowSize() {
+        CursorWindow cursorWindow = new CursorWindow("test");
+        cursorWindow.setNumColumns(1);
+        byte[] bytes = new byte[1024];
+        Arrays.fill(bytes, (byte) 1);
+        // Ensure that the default is not too small and it's possible to fill CursorWindow
+        // with ~2Mb of data
+        int testRowCount = 2016;
+        for (int i = 0; i < testRowCount; i++) {
+            assertTrue(cursorWindow.allocRow());
+            assertTrue("Allocation failed for row " + i, cursorWindow.putBlob(bytes, i, 0));
+        }
+        assertTrue(cursorWindow.allocRow());
+        assertFalse("Allocation should fail for row " + testRowCount,
+                cursorWindow.putBlob(bytes, testRowCount, 0));
+    }
+
+    @Test
+    public void testCustomSize() {
+        // Allocate CursorWindow with max size 10KB and test that restriction is enforced
+        CursorWindow cursorWindow = new CursorWindow("test", 10000);
+        cursorWindow.setNumColumns(1);
+        byte[] bytes = new byte[8000];
+        Arrays.fill(bytes, (byte) 1);
+        assertTrue(cursorWindow.allocRow());
+        assertTrue("Allocation of 1 row should succeed", cursorWindow.putBlob(bytes, 0, 0));
+        assertTrue(cursorWindow.allocRow());
+        assertFalse("Allocation of 2nd row should fail", cursorWindow.putBlob(bytes, 1, 0));
+    }
+
     private class MockCursorWindow extends CursorWindow {
         private boolean mHasReleasedAllReferences = false;
 
diff --git a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
index 9b441a0..a35a6ac 100644
--- a/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
+++ b/tests/tests/database/src/android/database/cts/DatabaseUtilsTest.java
@@ -519,6 +519,7 @@
             // expected
         }
 
+        parcel.recycle();
         parcel = Parcel.obtain();
         DatabaseUtils.writeExceptionToParcel(parcel, new SQLiteAbortException());
         parcel.setDataPosition(0);
@@ -529,6 +530,7 @@
             // expected
         }
 
+        parcel.recycle();
         parcel = Parcel.obtain();
         DatabaseUtils.writeExceptionToParcel(parcel, new FileNotFoundException());
         parcel.setDataPosition(0);
@@ -539,6 +541,7 @@
             // expected
         }
 
+        parcel.recycle();
         parcel = Parcel.obtain();
         DatabaseUtils.writeExceptionToParcel(parcel, new FileNotFoundException());
         parcel.setDataPosition(0);
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
index a506de5..a11abf6 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
@@ -19,10 +19,13 @@
 
 import android.content.Context;
 import android.database.AbstractCursor;
+import android.database.AbstractWindowedCursor;
 import android.database.Cursor;
 import android.database.CursorWindow;
 import android.database.DataSetObserver;
+import android.database.SQLException;
 import android.database.StaleDataException;
+import android.database.sqlite.SQLiteBlobTooBigException;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDirectCursorDriver;
@@ -210,6 +213,46 @@
         assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount());
     }
 
+    public void testRowTooBig() {
+        mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);");
+        byte[] testArr = new byte[10000];
+        Arrays.fill(testArr, (byte) 1);
+        for (int i = 0; i < 10; i++) {
+            mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr});
+        }
+
+        // Now reduce window size, so that no rows can fit
+        Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+        CursorWindow cw = new CursorWindow("test", 5000);
+        AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor;
+        ac.setWindow(cw);
+
+        try {
+            ac.moveToNext();
+            fail("Exception is expected when row exceeds CursorWindow size");
+        } catch (SQLiteBlobTooBigException expected) {
+        }
+    }
+
+    public void testFillWindowForwardOnly() {
+        mDatabase.execSQL("CREATE TABLE Tst (Num Integer NOT NULL);");
+        mDatabase.beginTransaction();
+        for (int i = 0; i < 100; i++) {
+            mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{i});
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+        Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+        SQLiteCursor ac = (SQLiteCursor) cursor;
+        CursorWindow window = new CursorWindow("test", 1000);
+        ac.setFillWindowForwardOnly(true);
+        ac.setWindow(window);
+        assertTrue(ac.moveToFirst());
+        // Now skip 70 rows and check that the window start position corresponds to row 70
+        ac.move(70);
+        assertEquals(70, window.getStartPosition());
+    }
+
     public void testOnMove() {
         // Do not test this API. It is callback which:
         // 1. The callback mechanism has been tested in super class
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index 26f8794..7a9232d 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.Semaphore;
 
@@ -33,6 +34,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteGlobal;
 import android.database.sqlite.SQLiteQuery;
 import android.database.sqlite.SQLiteStatement;
 import android.database.sqlite.SQLiteTransactionListener;
@@ -1390,6 +1392,16 @@
         assertFalse(mDatabase.isWriteAheadLoggingEnabled());
     }
 
+    public void testDisableWriteAheadLogging() {
+        assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+        mDatabase.disableWriteAheadLogging();
+        assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+        // Verify that default journal mode is set if WAL is disabled
+        String defaultJournalMode = SQLiteGlobal.getDefaultJournalMode();
+        assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+                .equalsIgnoreCase(defaultJournalMode));
+    }
+
     public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
         closeAndDeleteDatabase();
         mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), null,
@@ -1584,4 +1596,100 @@
         } catch (IllegalArgumentException expected) {
         }
     }
+
+    public void testDefaultJournalModeNotWAL() {
+        String defaultJournalMode = SQLiteGlobal.getDefaultJournalMode();
+        assertFalse("Default journal mode should not be WAL",
+                "WAL".equalsIgnoreCase(defaultJournalMode));
+    }
+
+    public void testCompatibilityWALIsDefaultWhenSupported() {
+        if (!SQLiteGlobal.isCompatibilityWalSupported()) {
+            Log.i(TAG, "Compatibility WAL not supported. "
+                    + "Skipping testCompatibilityWALIsDefaultWhenSupported");
+            return;
+        }
+
+        assertTrue("Journal mode should be WAL if compatibility WAL is supported",
+                DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+                        .equalsIgnoreCase("WAL"));
+    }
+
+    /**
+     * Test that app can specify journal mode/synchronous mode
+     */
+    public void testJournalModeSynchronousModeOverride() {
+        mDatabase.close();
+        SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+                .setJournalMode("DELETE").setSynchronousMode("OFF").build();
+        mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile, params);
+
+        String journalMode = DatabaseUtils
+                .stringForQuery(mDatabase, "PRAGMA journal_mode", null);
+
+        assertEquals("DELETE", journalMode.toUpperCase());
+        String syncMode = DatabaseUtils
+                .stringForQuery(mDatabase, "PRAGMA synchronous", null);
+
+        assertEquals("0", syncMode);
+    }
+
+    /**
+     * Test that enableWriteAheadLogging is not affected by app's journal mode/synchronous mode
+     * settings
+     */
+    public void testEnableWalOverridesJournalModeSynchronousMode() {
+        mDatabase.close();
+        SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+                .setJournalMode("DELETE").setSynchronousMode("OFF").build();
+        mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile, params);
+        mDatabase.enableWriteAheadLogging();
+
+        String journalMode = DatabaseUtils
+                .stringForQuery(mDatabase, "PRAGMA journal_mode", null);
+
+        assertEquals("WAL", journalMode.toUpperCase());
+        String syncMode = DatabaseUtils
+                .stringForQuery(mDatabase, "PRAGMA synchronous", null);
+
+        assertEquals("2" /* FULL */, syncMode);
+    }
+
+    /**
+     * This test starts a transaction and verifies that other threads are blocked on
+     * accessing the database. Waiting threads should be unblocked once transaction is complete.
+     *
+     * This is done to ensure that Compatibility WAL follows the original transaction semantics of
+     * {@link SQLiteDatabase} instance when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag
+     * is not set.
+     */
+    public void testActiveTransactionIsBlocking() throws Exception {
+        mDatabase.beginTransactionNonExclusive();
+        mDatabase.execSQL("CREATE TABLE t1 (i int);");
+        final List<Throwable> errors = new ArrayList<>();
+
+        Thread readThread = new Thread(
+                () -> {
+                    try {
+                        DatabaseUtils.longForQuery(mDatabase, "SELECT count(*) from t1", null);
+                    } catch (Throwable t) {
+                        Log.e(TAG, "ReadThread failed", t);
+                        errors.add(t);
+                    }
+                });
+        readThread.start();
+        readThread.join(500L);
+        assertTrue("ReadThread should be blocked while transaction is active",
+                readThread.isAlive());
+
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+
+        readThread.join(500L);
+        assertFalse("ReadThread should finish after transaction has ended",
+                readThread.isAlive());
+
+        assertTrue("ReadThread failed with errors: " + errors, errors.isEmpty());
+    }
+
 }
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
index 853f24b..f191801 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.database.Cursor;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
@@ -31,6 +32,9 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.io.File;
+import java.util.Arrays;
+
 import static android.database.sqlite.cts.DatabaseTestUtils.getDbInfoOutput;
 import static android.database.sqlite.cts.DatabaseTestUtils.waitForConnectionToClose;
 
@@ -55,6 +59,7 @@
     @Override
     protected void tearDown() throws Exception {
         mOpenHelper.close();
+        SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME));
         super.tearDown();
     }
 
@@ -204,17 +209,103 @@
                 output.contains("Connection #0:"));
     }
 
+    public void testOpenParamsConstructor() {
+        SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+                .build();
+
+        MockOpenHelper helper = new MockOpenHelper(mContext, null, 1, params);
+        SQLiteDatabase database = helper.getWritableDatabase();
+        assertNotNull(database);
+        helper.close();
+    }
+
+    /**
+     * Test for {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)}.
+     * <p>Opens the database using the helper and verifies that params have been applied</p>
+     */
+    public void testSetOpenParams() {
+        mOpenHelper.close();
+
+        SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
+        paramsBuilder.addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING);
+
+        MockOpenHelper helper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 1);
+        helper.setOpenParams(paramsBuilder.build());
+        assertTrue("database must be opened with ENABLE_WRITE_AHEAD_LOGGING flag",
+                helper.getWritableDatabase().isWriteAheadLoggingEnabled());
+    }
+
+    /**
+     * Verifies that {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)} cannot be
+     * called after opening the database.
+     */
+    public void testSetOpenParamsFailsIfDbIsOpen() {
+        mOpenHelper.getWritableDatabase();
+        try {
+            mOpenHelper.setOpenParams(new SQLiteDatabase.OpenParams.Builder().build());
+            fail("setOpenParams should fail if the database is open");
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Tests a scenario in WAL mode with multiple connections, when a connection should see schema
+     * changes made from another connection.
+     */
+    public void testWalSchemaChangeVisibilityOnUpgrade() {
+        File dbPath = mContext.getDatabasePath(TEST_DATABASE_NAME);
+        SQLiteDatabase.deleteDatabase(dbPath);
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
+        db.execSQL("CREATE TABLE test_table (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+        db.setVersion(1);
+        db.close();
+        mOpenHelper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 2) {
+            {
+                setWriteAheadLoggingEnabled(true);
+            }
+
+            @Override
+            public void onCreate(SQLiteDatabase db) {
+            }
+
+            @Override
+            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+                if (oldVersion == 1) {
+                    db.execSQL("ALTER TABLE test_table ADD column2 INT DEFAULT 1234");
+                    db.execSQL("CREATE TABLE test_table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+                }
+            }
+        };
+        // Check if can see the new column
+        try (Cursor cursor = mOpenHelper.getReadableDatabase()
+                .rawQuery("select * from test_table", null)) {
+            assertEquals("Newly added column should be visible. Returned columns: " + Arrays
+                    .toString(cursor.getColumnNames()), 2, cursor.getColumnCount());
+        }
+        // Check if can see the new table
+        try (Cursor cursor = mOpenHelper.getReadableDatabase()
+                .rawQuery("select * from test_table2", null)) {
+            assertEquals(1, cursor.getColumnCount());
+        }
+    }
+
     private MockOpenHelper getOpenHelper() {
         return new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
     }
 
-    private class MockOpenHelper extends SQLiteOpenHelper {
+    private static class MockOpenHelper extends SQLiteOpenHelper {
         private boolean mHasCalledOnOpen = false;
 
-        public MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
+        MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
             super(context, name, factory, version);
         }
 
+        MockOpenHelper(Context context, String name, int version,
+                SQLiteDatabase.OpenParams openParams) {
+            super(context, name, version, openParams);
+        }
+
         @Override
         public void onCreate(SQLiteDatabase db) {
         }
@@ -237,8 +328,8 @@
         }
     }
 
-    private class MockCursor extends SQLiteCursor {
-        public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
+    private static class MockCursor extends SQLiteCursor {
+        MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
                 SQLiteQuery query) {
             super(db, driver, editTable, query);
         }
diff --git a/tests/tests/debug/Android.mk b/tests/tests/debug/Android.mk
index f1b9b27..c715d07 100644
--- a/tests/tests/debug/Android.mk
+++ b/tests/tests/debug/Android.mk
@@ -30,7 +30,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner nativetesthelper
 
 LOCAL_JNI_SHARED_LIBRARIES := libdebugtest
 
diff --git a/tests/tests/debug/AndroidManifest.xml b/tests/tests/debug/AndroidManifest.xml
index 4b3254a..091e778 100644
--- a/tests/tests/debug/AndroidManifest.xml
+++ b/tests/tests/debug/AndroidManifest.xml
@@ -26,8 +26,6 @@
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.debug.cts"
                      android:label="CTS tests of native debugging API">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/debug/libdebugtest/Android.mk b/tests/tests/debug/libdebugtest/Android.mk
index 80eb256..d3db70f 100644
--- a/tests/tests/debug/libdebugtest/Android.mk
+++ b/tests/tests/debug/libdebugtest/Android.mk
@@ -31,6 +31,7 @@
 	android_debug_cts.cpp
 
 LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
 
 LOCAL_SDK_VERSION := 23
 LOCAL_NDK_STL_VARIANT := c++_static
diff --git a/tests/tests/debug/libdebugtest/android_debug_cts.cpp b/tests/tests/debug/libdebugtest/android_debug_cts.cpp
index fb87a28..3aa4318 100644
--- a/tests/tests/debug/libdebugtest/android_debug_cts.cpp
+++ b/tests/tests/debug/libdebugtest/android_debug_cts.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <jni.h>
+#include <gtest/gtest.h>
 #include <android/log.h>
 
 #include <errno.h>
@@ -29,6 +30,7 @@
 
 #define LOG_TAG "Cts-DebugTest"
 
+// Used by child processes only
 #define assert_or_exit(x)                                                                         \
     do {                                                                                          \
         if(x) break;                                                                              \
@@ -36,29 +38,21 @@
                 errno, strerror(errno));                                                          \
         _exit(1);                                                                                 \
     } while (0)
-#define assert_or_return(x)                                                                       \
-    do {                                                                                          \
-        if(x) break;                                                                              \
-        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Assertion " #x " failed. errno(%d): %s", \
-                errno, strerror(errno));                                                          \
-        return false;                                                                             \
-    } while (0)
 
-static bool parent(pid_t child) {
+static void parent(pid_t child) {
     int status;
     int wpid = waitpid(child, &status, 0);
-    assert_or_return(wpid == child);
-    assert_or_return(WIFEXITED(status));
-    assert_or_return(WEXITSTATUS(status ) == 0);
-    return true;
+    ASSERT_EQ(child, wpid);
+    ASSERT_TRUE(WIFEXITED(status));
+    ASSERT_EQ(0, WEXITSTATUS(status));
 }
 
-static bool run_test(const std::function<void(pid_t)> &test) {
+static void run_test(const std::function<void(pid_t)> &test) {
     pid_t pid = fork();
-    assert_or_return(pid >= 0);
-    if (pid != 0)
-        return parent(pid);
-    else {
+    ASSERT_NE(-1, pid) << "fork() failed with " << strerror(errno);
+    if (pid != 0) {
+        parent(pid);
+    } else {
         // child
         test(getppid());
         _exit(0);
@@ -75,12 +69,10 @@
     assert_or_exit(ptrace(PTRACE_DETACH, parent, nullptr, nullptr) == 0);
 }
 
-// public static native boolean ptraceAttach();
-extern "C" jboolean Java_android_debug_cts_DebugTest_ptraceAttach(JNIEnv *, jclass) {
-    return run_test(ptraceAttach);
+TEST(DebugTest, ptraceAttach) {
+    run_test(ptraceAttach);
 }
 
-
 static void processVmReadv(pid_t parent, const std::vector<long *> &addresses) {
     long destination;
     iovec local = { &destination, sizeof destination };
@@ -99,11 +91,11 @@
 
 static long global_variable = 0x47474747;
 // public static native boolean processVmReadv();
-extern "C" jboolean Java_android_debug_cts_DebugTest_processVmReadv(JNIEnv *, jclass) {
+TEST(DebugTest, processVmReadv) {
     long stack_variable = 0x42424242;
     // This runs the test with a selection of different kinds of addresses and
     // makes sure the child process (simulating a debugger) can read them.
-    return run_test([&](pid_t parent) {
+    run_test([&](pid_t parent) {
         processVmReadv(parent, std::vector<long *>{
                                    &global_variable, &stack_variable,
                                    reinterpret_cast<long *>(&processVmReadv)});
@@ -111,9 +103,9 @@
 }
 
 // public static native boolean processVmReadvNullptr();
-extern "C" jboolean Java_android_debug_cts_DebugTest_processVmReadvNullptr(JNIEnv *, jclass) {
+TEST(DebugTest, processVmReadvNullptr) {
     // Make sure reading unallocated memory behaves reasonably.
-    return run_test([](pid_t parent) {
+    run_test([](pid_t parent) {
         long destination;
         iovec local = {&destination, sizeof destination};
         iovec remote = {nullptr, sizeof(long)};
diff --git a/tests/tests/debug/src/android/debug/cts/DebugTest.java b/tests/tests/debug/src/android/debug/cts/DebugTest.java
index ca55d9c..993f02b 100644
--- a/tests/tests/debug/src/android/debug/cts/DebugTest.java
+++ b/tests/tests/debug/src/android/debug/cts/DebugTest.java
@@ -16,26 +16,10 @@
 
 package android.debug.cts;
 
-import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
 
-public class DebugTest extends TestCase {
-
-    static {
-        System.loadLibrary("debugtest");
-    }
-
-    public static native boolean ptraceAttach();
-    public void test_ptraceAttach() {
-        assertEquals(true, ptraceAttach());
-    }
-
-    public static native boolean processVmReadv();
-    public void test_processVmReadv() {
-        assertEquals(true, processVmReadv());
-    }
-
-    public static native boolean processVmReadvNullptr();
-    public void test_processVmReadvNullptr() {
-        assertEquals(true, processVmReadvNullptr());
-    }
-}
+@RunWith(GtestRunner.class)
+@TargetLibrary("debugtest")
+public class DebugTest {}
diff --git a/tests/tests/display/Android.mk b/tests/tests/display/Android.mk
index 53ae177..86f011d 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -27,13 +27,15 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_PACKAGE_NAME := CtsDisplayTestCases
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
index 638bdb1..7b1e371 100644
--- a/tests/tests/display/AndroidManifest.xml
+++ b/tests/tests/display/AndroidManifest.xml
@@ -20,6 +20,9 @@
 
     <!-- For special presentation windows when testing mode switches. -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <!-- For testing brightness slider tracking. -->
+    <uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/display/AndroidTest.xml b/tests/tests/display/AndroidTest.xml
index 8b174f1..3e1b38d 100644
--- a/tests/tests/display/AndroidTest.xml
+++ b/tests/tests/display/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Display 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" />
diff --git a/tests/tests/display/src/android/display/cts/BrightnessTest.java b/tests/tests/display/src/android/display/cts/BrightnessTest.java
new file mode 100644
index 0000000..62617d6
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/BrightnessTest.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.display.cts;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.DisplayManager;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.test.InstrumentationTestCase;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+
+public class BrightnessTest extends InstrumentationTestCase {
+
+    private Map<Long, BrightnessChangeEvent> mLastReadEvents = new HashMap<>();
+    private DisplayManager mDisplayManager;
+    PowerManager.WakeLock mWakeLock;
+
+    @Override
+    public void setUp() {
+        mDisplayManager =
+                InstrumentationRegistry.getContext().getSystemService(DisplayManager.class);
+        PowerManager pm =
+                InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
+        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "BrightnessTest");
+        mWakeLock.acquire();
+    }
+
+    @Override
+    public void tearDown() {
+        if (mWakeLock != null) {
+            mWakeLock.release();
+        }
+    }
+
+    public void testBrightnessSliderTracking() throws IOException, InterruptedException {
+        if (!systemAppWithPermission("android.permission.BRIGHTNESS_SLIDER_USAGE",
+                InstrumentationRegistry.getContext())) {
+            // Don't run as there is no app that has permission to access slider usage.
+            return;
+        }
+
+        int previousBrightness = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS);
+        int previousBrightnessMode =
+                getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
+        try {
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
+                    Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+            int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
+            assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
+
+            runShellCommand("pm grant " + InstrumentationRegistry.getContext().getPackageName()
+                    + " android.permission.BRIGHTNESS_SLIDER_USAGE");
+
+            // Setup and remember some initial state.
+            recordSliderEvents();
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, 20);
+            getNewEvents(1);
+
+            // Update brightness
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, 60);
+
+            // Check we got a slider event for the change.
+            List<BrightnessChangeEvent> newEvents = getNewEvents(1);
+            assertEquals(1, newEvents.size());
+            BrightnessChangeEvent firstEvent = newEvents.get(0);
+            assertValidLuxData(firstEvent);
+
+            // Update brightness again
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, 200);
+
+            // Check we get a second slider event.
+            newEvents = getNewEvents(1);
+            assertEquals(1, newEvents.size());
+            BrightnessChangeEvent secondEvent = newEvents.get(0);
+            assertValidLuxData(secondEvent);
+            assertEquals(secondEvent.lastBrightness, firstEvent.brightness, 1.0f);
+            assertTrue(secondEvent.isUserSetBrightness);
+            assertTrue("failed " + secondEvent.brightness + " not greater than " +
+                    firstEvent.brightness, secondEvent.brightness > firstEvent.brightness);
+        } finally {
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, previousBrightness);
+            setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE, previousBrightnessMode);
+        }
+    }
+
+    private void assertValidLuxData(BrightnessChangeEvent event) {
+        assertNotNull(event.luxTimestamps);
+        assertNotNull(event.luxValues);
+        assertTrue(event.luxTimestamps.length > 0);
+        assertEquals(event.luxValues.length, event.luxTimestamps.length);
+        for (int i = 1; i < event.luxTimestamps.length; ++i) {
+            assertTrue(event.luxTimestamps[i - 1] <= event.luxTimestamps[i]);
+        }
+        for (int i = 0; i < event.luxValues.length; ++i) {
+            assertTrue(event.luxValues[i] >= 0.0f);
+            assertTrue(event.luxValues[i] <= Float.MAX_VALUE);
+            assertFalse(Float.isNaN(event.luxValues[i]));
+        }
+    }
+
+    /**
+     * Check if there exists a system app that has the permission.
+     */
+    private boolean systemAppWithPermission(String permission, Context context) {
+        List<PackageInfo> packages = context.getPackageManager().getPackagesHoldingPermissions(
+                new String[] {permission}, PackageManager.MATCH_SYSTEM_ONLY);
+        return !packages.isEmpty();
+    }
+
+    private List<BrightnessChangeEvent> getNewEvents(int expected)
+            throws InterruptedException {
+        List<BrightnessChangeEvent> newEvents = new ArrayList<>();
+        for (int i = 0; newEvents.size() < expected && i < 20; ++i) {
+            if (i != 0) {
+                Thread.sleep(100);
+            }
+            List<BrightnessChangeEvent> events = mDisplayManager.getBrightnessEvents();
+            for (BrightnessChangeEvent event : events) {
+                if (!mLastReadEvents.containsKey(event.timeStamp)) {
+                    newEvents.add(event);
+                }
+            }
+            mLastReadEvents = new HashMap<>();
+            for (BrightnessChangeEvent event : events) {
+                mLastReadEvents.put(event.timeStamp, event);
+            }
+        }
+        return newEvents;
+    }
+
+    private void recordSliderEvents() {
+        mLastReadEvents = new HashMap<>();
+        List<BrightnessChangeEvent> eventsBefore = mDisplayManager.getBrightnessEvents();
+        for (BrightnessChangeEvent event: eventsBefore) {
+            mLastReadEvents.put(event.timeStamp, event);
+        }
+    }
+
+    private int getSystemSetting(String setting) throws IOException {
+        return Integer.parseInt(runShellCommand("settings get system " + setting));
+    }
+
+    private void setSystemSetting(String setting, int value)
+            throws IOException {
+        runShellCommand("settings put system " + setting + " " + Integer.toString(value));
+    }
+
+    private String runShellCommand(String cmd) {
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor output = automation.executeShellCommand(cmd);
+        String result = convertFileDescriptorToString(output.getFileDescriptor());
+        return result.trim();
+    }
+
+    private String convertFileDescriptorToString(FileDescriptor desc) {
+        try (Scanner s = new Scanner(new FileInputStream(desc)).useDelimiter("\\Z")) {
+            return s.hasNext() ? s.next() : "";
+        }
+    }
+}
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 1a2753b..c7ea939 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -16,7 +16,7 @@
 
 package android.display.cts;
 
-import org.junit.Rule;
+import static org.junit.Assert.*;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -37,6 +37,7 @@
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.support.test.rule.ActivityTestRule;
 import android.test.InstrumentationTestCase;
 import android.util.DisplayMetrics;
@@ -52,7 +53,15 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class DisplayTest extends InstrumentationTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+public class DisplayTest {
     // The CTS package brings up an overlay display on the target device (see AndroidTest.xml).
     // The overlay display parameters must match the ones defined there which are
     // 181x161/214 (wxh/dpi).  It only matters that these values are different from any real
@@ -84,18 +93,17 @@
             new ActivityTestRule<>(DisplayTestActivity.class,
                     false /* initialTouchMode */, false /* launchActivity */);
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mScreenOnActivity = launchScreenOnActivity();
-        mContext = getInstrumentation().getContext();
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         mUiModeManager = (UiModeManager)mContext.getSystemService(Context.UI_MODE_SERVICE);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mScreenOnActivity != null) {
             mScreenOnActivity.finish();
         }
@@ -104,20 +112,21 @@
     private void enableAppOps() {
         StringBuilder cmd = new StringBuilder();
         cmd.append("appops set ");
-        cmd.append(getInstrumentation().getContext().getPackageName());
+        cmd.append(InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
         cmd.append(" android:system_alert_window allow");
-        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand(cmd.toString());
 
         StringBuilder query = new StringBuilder();
         query.append("appops get ");
-        query.append(getInstrumentation().getContext().getPackageName());
+        query.append(InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
         query.append(" android:system_alert_window");
         String queryStr = query.toString();
 
         String result = "No operations.";
         while (result.contains("No operations")) {
-            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
-                    queryStr);
+            ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation().executeShellCommand(queryStr);
             InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
             result = convertStreamToString(inputStream);
         }
@@ -147,6 +156,7 @@
     /**
      * Verify that the getDisplays method returns both a default and an overlay display.
      */
+    @Test
     public void testGetDisplays() {
         Display[] displays = mDisplayManager.getDisplays();
         assertNotNull(displays);
@@ -169,6 +179,7 @@
      * Verify that the WindowManager returns the default display.
      */
     @Presubmit
+    @Test
     public void testDefaultDisplay() {
         assertEquals(Display.DEFAULT_DISPLAY, mWindowManager.getDefaultDisplay().getDisplayId());
     }
@@ -176,6 +187,7 @@
     /**
      * Verify default display's HDR capability.
      */
+    @Test
     public void testDefaultDisplayHdrCapability() {
         Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
         HdrCapabilities cap = display.getHdrCapabilities();
@@ -196,6 +208,7 @@
     /**
      * Verify that there is a secondary display.
      */
+    @Test
     public void testSecondaryDisplay() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
         assertNotNull(display);
@@ -205,6 +218,7 @@
     /**
      * Test the properties of the secondary Display.
      */
+    @Test
     public void testGetDisplayAttrs() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
 
@@ -230,6 +244,7 @@
     /**
      * Test that the getMetrics method fills in correct values.
      */
+    @Test
     public void testGetMetrics() {
         testGetMetrics(mDisplayManager);
     }
@@ -237,6 +252,7 @@
     /**
      * Tests getting metrics from the Activity context.
      */
+    @Test
     public void testActivityContextGetMetrics() {
         final Activity activity = launchActivity(mDisplayTestActivity);
         final DisplayManager dm =
@@ -264,13 +280,14 @@
                 && outMetrics.scaledDensity <= SCALE_DENSITY_UPPER_BOUND);
 
         assertEquals(SECONDARY_DISPLAY_DPI, outMetrics.densityDpi);
-        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.xdpi);
-        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.ydpi);
+        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.xdpi, 0.0001f);
+        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.ydpi, 0.0001f);
     }
 
     /**
      * Test that the getFlags method returns no flag bits set for the overlay display.
      */
+    @Test
     public void testFlags() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
 
@@ -280,6 +297,7 @@
     /**
      * Tests that the mode-related attributes and methods work as expected.
      */
+    @Test
     public void testMode() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
         assertEquals(2, display.getSupportedModes().length);
@@ -287,12 +305,13 @@
         assertEquals(display.getSupportedModes()[0], mode);
         assertEquals(SECONDARY_DISPLAY_WIDTH, mode.getPhysicalWidth());
         assertEquals(SECONDARY_DISPLAY_HEIGHT, mode.getPhysicalHeight());
-        assertEquals(display.getRefreshRate(), mode.getRefreshRate());
+        assertEquals(display.getRefreshRate(), mode.getRefreshRate(), 0.0001f);
     }
 
     /**
      * Tests that mode switch requests are correctly executed.
      */
+    @Test
     public void testModeSwitch() throws Exception {
         // Standalone VR devices globally ignore SYSTEM_ALERT_WINDOW via AppOps.
         // Skip this test, which depends on a Presentation SYSTEM_ALERT_WINDOW to pass.
@@ -333,7 +352,8 @@
             @Override
             public void run() {
                 mPresentation = new TestPresentation(
-                        getInstrumentation().getContext(), display, newMode.getModeId());
+                        InstrumentationRegistry.getInstrumentation().getContext(),
+                        display, newMode.getModeId());
                 mPresentation.show();
                 presentationSignal.countDown();
             }
@@ -387,10 +407,13 @@
 
     private Activity launchScreenOnActivity() {
         Class clazz = ScreenOnActivity.class;
-        String targetPackage = getInstrumentation().getContext().getPackageName();
-        Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(0, new Intent());
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
-        getInstrumentation().addMonitor(monitor);
+        String targetPackage =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+        Instrumentation.ActivityResult result =
+                new Instrumentation.ActivityResult(0, new Intent());
+        Instrumentation.ActivityMonitor monitor =
+                new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
+        InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
         launchActivity(targetPackage, clazz, null);
         return monitor.waitForActivity();
     }
@@ -400,4 +423,57 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         return activity;
     }
+
+    /**
+     * Utility method for launching an activity. Copied from InstrumentationTestCase since
+     * InstrumentationRegistry does not provide these APIs anymore.
+     *
+     * <p>The {@link Intent} used to launch the Activity is:
+     *  action = {@link Intent#ACTION_MAIN}
+     *  extras = null, unless a custom bundle is provided here
+     * All other fields are null or empty.
+     *
+     * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
+     * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
+     * file.  This is not necessarily the same as the java package name.
+     *
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param extras Optional extra stuff to pass to the activity.
+     * @return The activity, or null if non launched.
+     */
+    private final <T extends Activity> T launchActivity(
+            String pkg,
+            Class<T> activityCls,
+            Bundle extras) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        return launchActivityWithIntent(pkg, activityCls, intent);
+    }
+
+    /**
+     * Utility method for launching an activity with a specific Intent.
+     *
+     * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
+     * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
+     * file.  This is not necessarily the same as the java package name.
+     *
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param intent The intent to launch with
+     * @return The activity, or null if non launched.
+     */
+    @SuppressWarnings("unchecked")
+    private final <T extends Activity> T launchActivityWithIntent(
+            String pkg,
+            Class<T> activityCls,
+            Intent intent) {
+        intent.setClassName(pkg, activityCls.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        T activity = (T) InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        return activity;
+    }
 }
diff --git a/tests/tests/dpi/Android.mk b/tests/tests/dpi/Android.mk
index 8bb7d64..e6686bc 100644
--- a/tests/tests/dpi/Android.mk
+++ b/tests/tests/dpi/Android.mk
@@ -17,7 +17,9 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -41,7 +43,7 @@
 # CTS tests, so drop it into a library that other tests can use.
 include $(CLEAR_VARS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := src/android/dpi/cts/DefaultManifestAttributesTest.java
 
@@ -49,4 +51,6 @@
 
 LOCAL_MODULE := android.cts.dpi
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/dpi/AndroidTest.xml b/tests/tests/dpi/AndroidTest.xml
index e12b36d..5e07b7c 100644
--- a/tests/tests/dpi/AndroidTest.xml
+++ b/tests/tests/dpi/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS DPI 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" />
diff --git a/tests/tests/dpi2/Android.mk b/tests/tests/dpi2/Android.mk
index dcd2c0f..f366781 100644
--- a/tests/tests/dpi2/Android.mk
+++ b/tests/tests/dpi2/Android.mk
@@ -20,7 +20,7 @@
 # We use the DefaultManifestAttributesTest from the android.cts.dpi package.
 LOCAL_STATIC_JAVA_LIBRARIES := android.cts.dpi ctstestrunner junit
 
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/dpi2/AndroidManifest.xml b/tests/tests/dpi2/AndroidManifest.xml
index 689be29..f3d5be0 100644
--- a/tests/tests/dpi2/AndroidManifest.xml
+++ b/tests/tests/dpi2/AndroidManifest.xml
@@ -25,7 +25,7 @@
 
     <!-- target cupcake so we can test the default attributes get set
          properly for the screen size attributes. -->
-    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="3" />
+    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="17" />
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.dpi2.cts"
diff --git a/tests/tests/dpi2/AndroidTest.xml b/tests/tests/dpi2/AndroidTest.xml
index 589935d..0c2f6dc 100644
--- a/tests/tests/dpi2/AndroidTest.xml
+++ b/tests/tests/dpi2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS DPI 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" />
diff --git a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
index 04db412..abb8d66 100644
--- a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
+++ b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
@@ -34,6 +34,6 @@
 
     // This is a sanity test to make sure that we're instrumenting the proper package
     public void testPackageHasExpectedSdkVersion() {
-        assertEquals(3, getAppInfo().targetSdkVersion);
+        assertEquals(3, getAppInfo().minSdkVersion);
     }
 }
diff --git a/tests/tests/dreams/Android.mk b/tests/tests/dreams/Android.mk
index 14c43a8..f1a31d8 100644
--- a/tests/tests/dreams/Android.mk
+++ b/tests/tests/dreams/Android.mk
@@ -24,9 +24,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
 
-LOCAL_JAVA_LIBRARIES :=  android.test.runner
+LOCAL_JAVA_LIBRARIES :=  android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index 7274740..6f77cca 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/drm/Android.mk b/tests/tests/drm/Android.mk
index 13ac8d6..b1f2aac 100644
--- a/tests/tests/drm/Android.mk
+++ b/tests/tests/drm/Android.mk
@@ -24,7 +24,9 @@
 # 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 legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/drm/AndroidTest.xml b/tests/tests/drm/AndroidTest.xml
index 1a9d0b7..996f3f1 100644
--- a/tests/tests/drm/AndroidTest.xml
+++ b/tests/tests/drm/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS DRM test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/dynamic_linker/Android.mk b/tests/tests/dynamic_linker/Android.mk
index 97518bd..ef122ca 100644
--- a/tests/tests/dynamic_linker/Android.mk
+++ b/tests/tests/dynamic_linker/Android.mk
@@ -18,6 +18,7 @@
 LOCAL_MODULE := libdynamiclinker_native_lib_a
 LOCAL_MODULE_TAGS := optional
 LOCAL_SRC_FILES := native_lib_a.cpp
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 LOCAL_SDK_VERSION := 25
 LOCAL_NDK_STL_VARIANT := c++_static
@@ -28,6 +29,7 @@
 LOCAL_MODULE := libdynamiclinker_native_lib_b
 LOCAL_MODULE_TAGS := optional
 LOCAL_SRC_FILES := native_lib_b.cpp
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 LOCAL_SDK_VERSION := 25
 LOCAL_NDK_STL_VARIANT := c++_static
@@ -37,7 +39,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 LOCAL_SRC_FILES := $(call all-java-files-under, .)
 LOCAL_MULTILIB := both
 LOCAL_JNI_SHARED_LIBRARIES := libdynamiclinker_native_lib_a libdynamiclinker_native_lib_b
diff --git a/tests/tests/dynamic_linker/AndroidTest.xml b/tests/tests/dynamic_linker/AndroidTest.xml
index 5cc4317..483ae03 100644
--- a/tests/tests/dynamic_linker/AndroidTest.xml
+++ b/tests/tests/dynamic_linker/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS dynamic linker test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="bionic" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/effect/AndroidTest.xml b/tests/tests/effect/AndroidTest.xml
index 072028b..ae32cd5 100644
--- a/tests/tests/effect/AndroidTest.xml
+++ b/tests/tests/effect/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Effect test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/externalservice/Android.mk b/tests/tests/externalservice/Android.mk
index 62afaad..c64c0ca 100644
--- a/tests/tests/externalservice/Android.mk
+++ b/tests/tests/externalservice/Android.mk
@@ -27,6 +27,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := CtsExternalServiceCommon compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 # Tag this module as a cts test artifact
diff --git a/tests/tests/externalservice/service/Android.mk b/tests/tests/externalservice/service/Android.mk
index 9fc0033..8563cb1 100644
--- a/tests/tests/externalservice/service/Android.mk
+++ b/tests/tests/externalservice/service/Android.mk
@@ -21,7 +21,11 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := CtsExternalServiceCommon ctstestrunner compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    CtsExternalServiceCommon \
+    ctstestrunner \
+    compatibility-device-util \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/externalservice/service/AndroidManifest.xml b/tests/tests/externalservice/service/AndroidManifest.xml
index 06fa80e..74d0825 100644
--- a/tests/tests/externalservice/service/AndroidManifest.xml
+++ b/tests/tests/externalservice/service/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.externalservice.service">
 
     <application android:label="External Service Host">
+        <uses-library android:name="android.test.runner" />
+
         <!-- Service used to start .ExternalService from this package. -->
         <service android:name=".ServiceCreator"
                  android:isolatedProcess="false"
diff --git a/tests/tests/gesture/src/android/gesture/cts/GestureTest.java b/tests/tests/gesture/src/android/gesture/cts/GestureTest.java
index 4d33024..524e9a3 100644
--- a/tests/tests/gesture/src/android/gesture/cts/GestureTest.java
+++ b/tests/tests/gesture/src/android/gesture/cts/GestureTest.java
@@ -113,5 +113,7 @@
         Gesture readGesture = Gesture.CREATOR.createFromParcel(parcel);
         // check that all attributes are equal
         new GestureComparator().assertGesturesEquals(mGesture, readGesture);
+
+        parcel.recycle();
     }
 }
diff --git a/tests/tests/graphics/Android.mk b/tests/tests/graphics/Android.mk
index 7ea5eec..c7c558a 100644
--- a/tests/tests/graphics/Android.mk
+++ b/tests/tests/graphics/Android.mk
@@ -20,17 +20,17 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android-support-test \
+    android-support-v4 \
     mockito-target-minus-junit4 \
     compatibility-device-util \
     ctsdeviceutillegacy \
     ctstestrunner \
     android-support-annotations \
-    junit \
-    legacy-android-test
+    junit
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsgraphics_jni
 
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index 205e81b..b153685 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -39,6 +39,16 @@
         <activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
                   android:theme="@style/WhiteBackgroundNoWindowAnimation"
                   android:screenOrientation="locked"/>
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="android.graphics.cts.fileprovider"
+            android:exported="false"
+            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"
diff --git a/tests/tests/graphics/AndroidTest.xml b/tests/tests/graphics/AndroidTest.xml
index baa1e70..1cf31d9 100644
--- a/tests/tests/graphics/AndroidTest.xml
+++ b/tests/tests/graphics/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Graphics test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttf b/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttf
new file mode 100644
index 0000000..06df4cb
--- /dev/null
+++ b/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttx b/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttx
new file mode 100644
index 0000000..fe8cdeb
--- /dev/null
+++ b/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttx
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT 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="100"/>
+    <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="1em" width="0" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="0" platEncID="3" 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 -->
+    </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="100" yMax="100">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="0" y="50" on="1"/>
+        <pt x="0" y="100" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <HVAR>
+    <Version value="0x00010000"/>
+    <VarStore Format="1">
+      <Format value="1" />
+      <VarRegionList>
+        <Region index="0">
+          <VarRegionAxis index="0">
+            <StartCoord value="0" />
+            <PeakCoord value="1.0" />
+            <EndCoord value="1.0" />
+          </VarRegionAxis>
+        </Region>
+      </VarRegionList>
+      <VarData index="0">
+        <NumShorts value="0" />
+        <VarRegionIndex index="0" value="0" />
+        <Item index="0" value="[100]" />
+      </VarData>
+    </VarStore>
+    <AdvWidthMap>
+      <Map index="0" outer="0" inner="0" />
+      <Map index="1" outer="0" inner="0" />
+      <Map index="2" outer="0" inner="0" />
+    </AdvWidthMap>
+  </HVAR>
+
+  <fvar>
+    <Axis>
+      <AxisTag>wght</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1000.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+  </fvar>
+
+  <gvar>
+    <version value="1" />
+    <reserved value="0" />
+    <glyphVariations glyph="1em">
+      <tuple>
+        <coord axis="wght" 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">
+      Weight
+    </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/a3em.ttx
new file mode 100644
index 0000000..d3b9e16
--- /dev/null
+++ b/tests/tests/graphics/assets/a3em.ttx
@@ -0,0 +1,187 @@
+<?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_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="3em" />
+      <map code="0x0062" name="1em" />
+      <map code="0x0063" name="1em" />
+      <map code="0x0064" name="1em" />
+      <map code="0x0065" name="1em" />
+    </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) 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/b3em.ttx b/tests/tests/graphics/assets/b3em.ttx
new file mode 100644
index 0000000..b5a77ef
--- /dev/null
+++ b/tests/tests/graphics/assets/b3em.ttx
@@ -0,0 +1,187 @@
+<?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_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="1em" />
+      <map code="0x0062" name="3em" />
+      <map code="0x0063" name="1em" />
+      <map code="0x0064" name="1em" />
+      <map code="0x0065" name="1em" />
+    </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) 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/c3em.ttx b/tests/tests/graphics/assets/c3em.ttx
new file mode 100644
index 0000000..f5ed8e5
--- /dev/null
+++ b/tests/tests/graphics/assets/c3em.ttx
@@ -0,0 +1,187 @@
+<?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_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="1em" />
+      <map code="0x0062" name="1em" />
+      <map code="0x0063" name="3em" />
+      <map code="0x0064" name="1em" />
+      <map code="0x0065" name="1em" />
+    </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) 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/jni/Android.mk b/tests/tests/graphics/jni/Android.mk
index b92fcd7..c69341f 100644
--- a/tests/tests/graphics/jni/Android.mk
+++ b/tests/tests/graphics/jni/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_SRC_FILES := \
 	CtsGraphicsJniOnLoad.cpp \
 	android_graphics_cts_ANativeWindowTest.cpp \
+	android_graphics_cts_ASurfaceTextureTest.cpp \
 	android_graphics_cts_BitmapTest.cpp \
 	android_graphics_cts_SyncTest.cpp \
 	android_graphics_cts_CameraGpuCtsActivity.cpp \
diff --git a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
index fe3bbc7..85fad6e 100644
--- a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
+++ b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
@@ -18,6 +18,7 @@
 #include <stdio.h>
 
 extern int register_android_graphics_cts_ANativeWindowTest(JNIEnv*);
+extern int register_android_graphics_cts_ASurfaceTextureTest(JNIEnv*);
 extern int register_android_graphics_cts_BitmapTest(JNIEnv*);
 extern int register_android_graphics_cts_CameraGpuCtsActivity(JNIEnv*);
 extern int register_android_graphics_cts_VulkanFeaturesTest(JNIEnv*);
@@ -28,6 +29,8 @@
         return JNI_ERR;
     if (register_android_graphics_cts_ANativeWindowTest(env))
         return JNI_ERR;
+    if (register_android_graphics_cts_ASurfaceTextureTest(env))
+        return JNI_ERR;
     if (register_android_graphics_cts_BitmapTest(env))
         return JNI_ERR;
     if (register_android_graphics_cts_CameraGpuCtsActivity(env))
diff --git a/tests/tests/graphics/jni/android_graphics_cts_ANativeWindowTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_ANativeWindowTest.cpp
index 788c66e..74f33cd 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_ANativeWindowTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_ANativeWindowTest.cpp
@@ -18,9 +18,10 @@
 #define LOG_TAG "ANativeWindowTest"
 
 #include <array>
-#include <jni.h>
+
 #include <android/native_window.h>
 #include <android/native_window_jni.h>
+#include <jni.h>
 
 namespace {
 
@@ -33,8 +34,34 @@
     ANativeWindow_release(window);
 }
 
-const std::array<JNINativeMethod, 1> JNI_METHODS = {{
+jint setBuffersDataSpace(JNIEnv* env, jclass, jobject jSurface, jint dataSpace) {
+    ANativeWindow* window = nullptr;
+    if (jSurface) {
+        window = ANativeWindow_fromSurface(env, jSurface);
+    }
+    int error = ANativeWindow_setBuffersDataSpace(window, dataSpace);
+    if (error != 0) {
+        return error;
+    }
+    ANativeWindow_Buffer mappedBuffer;
+    ANativeWindow_lock(window, &mappedBuffer, nullptr);
+    ANativeWindow_unlockAndPost(window);
+    ANativeWindow_release(window);
+    return error;
+}
+
+jint getBuffersDataSpace(JNIEnv* env, jclass, jobject jSurface) {
+    ANativeWindow* window = nullptr;
+    if (jSurface) {
+        window = ANativeWindow_fromSurface(env, jSurface);
+    }
+    return ANativeWindow_getBuffersDataSpace(window);
+}
+
+const std::array<JNINativeMethod, 3> JNI_METHODS = {{
     { "nPushBufferWithTransform", "(Landroid/view/Surface;I)V", (void*)pushBufferWithTransform },
+    { "nSetBuffersDataSpace", "(Landroid/view/Surface;I)I", (void*)setBuffersDataSpace },
+    { "nGetBuffersDataSpace", "(Landroid/view/Surface;)I", (void*)getBuffersDataSpace },
 }};
 
 }
diff --git a/tests/tests/graphics/jni/android_graphics_cts_ASurfaceTextureTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_ASurfaceTextureTest.cpp
new file mode 100644
index 0000000..3a86418
--- /dev/null
+++ b/tests/tests/graphics/jni/android_graphics_cts_ASurfaceTextureTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "ASurfaceTextureTest"
+
+#include <dlfcn.h>
+#include <array>
+#include <jni.h>
+#include <android/native_window.h>
+#include <android/surface_texture.h>
+#include <android/surface_texture_jni.h>
+
+static void fail(JNIEnv* env, const char* format, ...) {
+    va_list args;
+
+    va_start(args, format);
+    char *msg;
+    vasprintf(&msg, format, args);
+    va_end(args);
+
+    jclass exClass;
+    const char *className = "java/lang/AssertionError";
+    exClass = env->FindClass(className);
+    env->ThrowNew(exClass, msg);
+    free(msg);
+}
+
+#define ASSERT(condition, format, args...) \
+        if (!(condition)) { \
+            fail(env, format, ## args); \
+            return; \
+        }
+
+// ------------------------------------------------------------------------------------------------
+
+static void basicTests(JNIEnv* env, jclass, jobject surfaceTexture) {
+
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_fromSurfaceTexture"),
+      "couldn't dlsym ASurfaceTexture_fromSurfaceTexture");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_release"),
+      "couldn't dlsym ASurfaceTexture_release");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_acquireANativeWindow"),
+      "couldn't dlsym ASurfaceTexture_acquireANativeWindow");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_attachToGLContext"),
+      "couldn't dlsym ASurfaceTexture_attachToGLContext");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_detachFromGLContext"),
+      "couldn't dlsym ASurfaceTexture_detachFromGLContext");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_updateTexImage"),
+      "couldn't dlsym ASurfaceTexture_updateTexImage");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_getTransformMatrix"),
+      "couldn't dlsym ASurfaceTexture_getTransformMatrix");
+  ASSERT(dlsym(RTLD_DEFAULT, "ASurfaceTexture_getTimestamp"),
+      "couldn't dlsym ASurfaceTexture_getTimestamp");
+
+  ASurfaceTexture* ast = ASurfaceTexture_fromSurfaceTexture(env, surfaceTexture);
+  ASSERT(ast, "ASurfaceTexture_fromSurfaceTexture failed")
+
+  ANativeWindow* win = ASurfaceTexture_acquireANativeWindow(ast);
+  ASSERT(win, "ASurfaceTexture_acquireANativeWindow returned nullptr")
+
+  ASSERT(!ASurfaceTexture_attachToGLContext(ast, 1234), "ASurfaceTexture_attachToGLContext failed");
+  ASSERT( ASurfaceTexture_attachToGLContext(ast, 2000), "ASurfaceTexture_attachToGLContext (2nd) should have failed");
+
+  ARect bounds = {0, 0, 640, 480};
+  ANativeWindow_Buffer outBuffer;
+  int err = ANativeWindow_lock(win, &outBuffer, &bounds);
+  ASSERT(!err, "ANativeWindow_lock failed");
+  ASSERT(outBuffer.width == 640, "locked buffer width is wrong")
+  ASSERT(outBuffer.height == 480, "locked buffer height is wrong")
+
+  // this pushes a buffer
+  ANativeWindow_unlockAndPost(win);
+
+  ASSERT(!ASurfaceTexture_updateTexImage(ast), "ASurfaceTexture_updateTexImage (1st) failed");
+  ASSERT(!ASurfaceTexture_updateTexImage(ast), "ASurfaceTexture_updateTexImage (2st) failed");
+
+  ASurfaceTexture_detachFromGLContext(ast);
+  ANativeWindow_release(win);
+  ASurfaceTexture_release(ast);
+}
+
+// ------------------------------------------------------------------------------------------------
+
+static const std::array<JNINativeMethod, 1> JNI_METHODS = {{
+    { "nBasicTests", "(Landroid/graphics/SurfaceTexture;)V", (void*)basicTests },
+}};
+
+int register_android_graphics_cts_ASurfaceTextureTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/graphics/cts/ASurfaceTextureTest");
+    return env->RegisterNatives(clazz, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/graphics/jni/android_graphics_cts_VulkanFeaturesTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_VulkanFeaturesTest.cpp
index 7018a34..a60e0ac 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_VulkanFeaturesTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_VulkanFeaturesTest.cpp
@@ -15,113 +15,16 @@
  *
  */
 
-#define LOG_TAG "VulkanFeaturesTest"
-
 #include <android/log.h>
 #include <jni.h>
 #include <vkjson.h>
 
-#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__)
-
 namespace {
 
-const char* kDesiredInstanceExtensions[] = {
-    "VK_KHR_get_physical_device_properties2",
-};
-
-VkResult getVkJSON(std::string& vkjson) {
-    VkResult result;
-
-    uint32_t available_extensions_count = 0;
-    std::vector<VkExtensionProperties> available_extensions;
-    result = vkEnumerateInstanceExtensionProperties(nullptr /* layerName */,
-            &available_extensions_count, nullptr);
-    if (result != VK_SUCCESS) {
-        ALOGE("vkEnumerateInstanceExtensionProperties failed: %d", result);
-        return result;
-    }
-    do {
-        available_extensions.resize(available_extensions_count);
-        result = vkEnumerateInstanceExtensionProperties(nullptr /* layerName */,
-                &available_extensions_count, available_extensions.data());
-        if (result < 0) {
-            ALOGE("vkEnumerateInstanceExtensionProperties failed: %d", result);
-            return result;
-        }
-    } while (result == VK_INCOMPLETE);
-    available_extensions.resize(available_extensions_count);
-
-    std::vector<const char*> enable_extensions;
-    for (auto name : kDesiredInstanceExtensions) {
-        if (std::find_if(available_extensions.cbegin(), available_extensions.cend(),
-                [name](const VkExtensionProperties& properties) {
-                    return strcmp(name, properties.extensionName) == 0;
-                })
-                != available_extensions.cend()) {
-            enable_extensions.push_back(name);
-        }
-    }
-
-    const VkApplicationInfo app_info = {
-        VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr,
-        "VulkanFeaturesTest", 0,    /* app name, version */
-        "vkjson", 0,                /* engine name, version */
-        VK_API_VERSION_1_0,
-    };
-    const VkInstanceCreateInfo instance_info = {
-        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, nullptr,
-        0,              /* flags */
-        &app_info,
-        0, nullptr,     /* layers */
-        static_cast<uint32_t>(enable_extensions.size()),
-        enable_extensions.data(),
-    };
-    VkInstance instance;
-    result = vkCreateInstance(&instance_info, nullptr, &instance);
-    if (result != VK_SUCCESS) {
-        ALOGE("vkCreateInstance failed: %d", result);
-        return result;
-    }
-
-    uint32_t ngpu = 0;
-    result = vkEnumeratePhysicalDevices(instance, &ngpu, nullptr);
-    if (result != VK_SUCCESS) {
-        ALOGE("vkEnumeratePhysicalDevices failed: %d", result);
-        vkDestroyInstance(instance, nullptr);
-        return result;
-    }
-    std::vector<VkPhysicalDevice> gpus(ngpu, VK_NULL_HANDLE);
-    result = vkEnumeratePhysicalDevices(instance, &ngpu, gpus.data());
-    if (result != VK_SUCCESS) {
-        ALOGE("vkEnumeratePhysicalDevices failed: %d", result);
-        vkDestroyInstance(instance, nullptr);
-        return result;
-    }
-
-    vkjson.assign("[\n");
-    for (size_t i = 0, n = gpus.size(); i < n; i++) {
-        auto props = VkJsonGetDevice(instance, gpus[i],
-                instance_info.enabledExtensionCount, instance_info.ppEnabledExtensionNames);
-        vkjson.append(VkJsonDeviceToJson(props));
-        if (i < n - 1)
-            vkjson.append(",\n");
-    }
-    vkjson.append("]");
-
-    vkDestroyInstance(instance, nullptr);
-
-    return VK_SUCCESS;
-}
-
 jstring android_graphics_cts_VulkanFeaturesTest_nativeGetVkJSON(JNIEnv* env,
     jclass /*clazz*/)
 {
-    std::string vkjson;
-    if (getVkJSON(vkjson) < 0)
-        return nullptr;
+    std::string vkjson(VkJsonInstanceToJson(VkJsonGetInstance()));
     return env->NewStringUTF(vkjson.c_str());
 }
 
diff --git a/tests/tests/graphics/res/drawable-nodpi/ninepatch_nodpi.9.png b/tests/tests/graphics/res/drawable-nodpi/ninepatch_nodpi.9.png
new file mode 100644
index 0000000..24019d8
--- /dev/null
+++ b/tests/tests/graphics/res/drawable-nodpi/ninepatch_nodpi.9.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
index 38339b7..32a7516 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
index a22f678..9ba7c00 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
index be487d1..6cafa84 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
index 943fce5..b0b6eb5 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
index b46363e..9f60a31 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
index 01c445c..3d14630 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
index 739eea1..87ffcc1 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
index 4617f62..859df7e 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
index 4d6b84d..78ef42b 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
index c6540e8..29aee85 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
index 491e73e..1c13c30 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
index e335a92..f2cb106 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
index 57f2ae3..34e0571 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
index e74c181..c9702c9 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
index 7450751..b0af7d4 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
index d010d79..e779718 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
index 5ada060..7ba7191 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
index 5af7090..37a0d21 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
index 24b9662..b011284 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
index c9677a6..9e92c8a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
index 8882a7a..0262e57 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
index 143ce3e..1520397 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
index 9a5efd2..727bb48 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
index 2edc3c7..d68ab90 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
index 9822bc2..2830840 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
index d12b142..b5d25be 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
index 3936c89..923347d 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
index c5d06f6..74c30fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
index 3936c89..923347d 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
index c5d06f6..74c30fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
index 2bf7882..c202ffc 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
index 4141d6f..ed31e47 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
index 1212fb3..a32de90 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
index 0717399..26cbe2a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
index 505aa2e..9aeeeef 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
index 9b53e94..468a1c3 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
index a5d4d33..e4bc055 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
index 0d8ded1..bb185f2 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
index 3da7969..02901b6 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/animatedimagedrawable.xml b/tests/tests/graphics/res/drawable/animatedimagedrawable.xml
new file mode 100644
index 0000000..b4133c4
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animatedimagedrawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics.drawable.AnimatedImageDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/animated" />
+
diff --git a/tests/tests/graphics/res/drawable/animatedimagedrawable_automirrored.xml b/tests/tests/graphics/res/drawable/animatedimagedrawable_automirrored.xml
new file mode 100644
index 0000000..3d5b0cf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animatedimagedrawable_automirrored.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.
+ -->
+
+<animated-image xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/animated"
+    android:autoMirrored="true"
+    />
+
diff --git a/tests/tests/graphics/res/drawable/animatedimagedrawable_class.xml b/tests/tests/graphics/res/drawable/animatedimagedrawable_class.xml
new file mode 100644
index 0000000..0adc48e
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animatedimagedrawable_class.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.
+ -->
+
+<drawable xmlns:android="http://schemas.android.com/apk/res/android"
+    class="android.graphics.drawable.AnimatedImageDrawable"
+    android:src="@drawable/animated" />
diff --git a/tests/tests/graphics/res/drawable/animatedimagedrawable_nosrc.xml b/tests/tests/graphics/res/drawable/animatedimagedrawable_nosrc.xml
new file mode 100644
index 0000000..0f7ac8f
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animatedimagedrawable_nosrc.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.
+ -->
+
+<animated-image xmlns:android="http://schemas.android.com/apk/res/android"
+    />
+
diff --git a/tests/tests/graphics/res/drawable/animatedimagedrawable_tag.xml b/tests/tests/graphics/res/drawable/animatedimagedrawable_tag.xml
new file mode 100644
index 0000000..4b66d64
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animatedimagedrawable_tag.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.
+ -->
+
+<animated-image xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/animated" />
+
diff --git a/tests/tests/graphics/res/drawable/color_wheel.ico b/tests/tests/graphics/res/drawable/color_wheel.ico
new file mode 100644
index 0000000..fdfa381
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/color_wheel.ico
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/google_chrome.ico b/tests/tests/graphics/res/drawable/google_chrome.ico
new file mode 100644
index 0000000..7af91ee
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/google_chrome.ico
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_1.jpg b/tests/tests/graphics/res/drawable/orientation_1.jpg
new file mode 100644
index 0000000..9c64ef2
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_1.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_2.jpg b/tests/tests/graphics/res/drawable/orientation_2.jpg
new file mode 100644
index 0000000..735ff0b
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_2.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_3.jpg b/tests/tests/graphics/res/drawable/orientation_3.jpg
new file mode 100644
index 0000000..8ac6c13
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_3.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_4.jpg b/tests/tests/graphics/res/drawable/orientation_4.jpg
new file mode 100644
index 0000000..b6a7bad
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_4.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_5.jpg b/tests/tests/graphics/res/drawable/orientation_5.jpg
new file mode 100644
index 0000000..5e34905
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_5.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_6.jpg b/tests/tests/graphics/res/drawable/orientation_6.jpg
new file mode 100644
index 0000000..3a934a3
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_6.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_7.jpg b/tests/tests/graphics/res/drawable/orientation_7.jpg
new file mode 100644
index 0000000..5984912
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_7.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/orientation_8.jpg b/tests/tests/graphics/res/drawable/orientation_8.jpg
new file mode 100644
index 0000000..51e6d36
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/orientation_8.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation1.webp b/tests/tests/graphics/res/drawable/webp_orientation1.webp
new file mode 100644
index 0000000..7ab537e
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation1.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation2.webp b/tests/tests/graphics/res/drawable/webp_orientation2.webp
new file mode 100644
index 0000000..e0757a2
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation2.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation3.webp b/tests/tests/graphics/res/drawable/webp_orientation3.webp
new file mode 100644
index 0000000..3e29dd8
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation3.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation4.webp b/tests/tests/graphics/res/drawable/webp_orientation4.webp
new file mode 100644
index 0000000..164bab4
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation4.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation5.webp b/tests/tests/graphics/res/drawable/webp_orientation5.webp
new file mode 100644
index 0000000..ccc3b60
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation5.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation6.webp b/tests/tests/graphics/res/drawable/webp_orientation6.webp
new file mode 100644
index 0000000..e6cf6666
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation6.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation7.webp b/tests/tests/graphics/res/drawable/webp_orientation7.webp
new file mode 100644
index 0000000..e454958
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation7.webp
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/webp_orientation8.webp b/tests/tests/graphics/res/drawable/webp_orientation8.webp
new file mode 100644
index 0000000..ba11961
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/webp_orientation8.webp
Binary files differ
diff --git a/tests/tests/graphics/res/font/a3em.ttf b/tests/tests/graphics/res/font/a3em.ttf
new file mode 100644
index 0000000..a601ce2
--- /dev/null
+++ b/tests/tests/graphics/res/font/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/b3em.ttf b/tests/tests/graphics/res/font/b3em.ttf
new file mode 100644
index 0000000..63948a2
--- /dev/null
+++ b/tests/tests/graphics/res/font/b3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/c3em.ttf b/tests/tests/graphics/res/font/c3em.ttf
new file mode 100644
index 0000000..badc3e2
--- /dev/null
+++ b/tests/tests/graphics/res/font/c3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/multistyle_family.xml b/tests/tests/graphics/res/font/multistyle_family.xml
new file mode 100644
index 0000000..2522e72
--- /dev/null
+++ b/tests/tests/graphics/res/font/multistyle_family.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:font="@font/a3em" android:fontWeight="400" android:fontStyle="normal" />
+    <font android:font="@font/b3em" android:fontWeight="400" android:fontStyle="italic" />
+    <font android:font="@font/c3em" android:fontWeight="700" android:fontStyle="italic" />
+</font-family>
diff --git a/tests/tests/graphics/res/font/multiweight_family.xml b/tests/tests/graphics/res/font/multiweight_family.xml
new file mode 100644
index 0000000..2ed0490
--- /dev/null
+++ b/tests/tests/graphics/res/font/multiweight_family.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:font="@font/a3em" android:fontWeight="100" android:fontStyle="normal" />
+    <font android:font="@font/b3em" android:fontWeight="400" android:fontStyle="normal" />
+    <font android:font="@font/c3em" android:fontWeight="700" android:fontStyle="normal" />
+</font-family>
diff --git a/tests/tests/graphics/res/layout/animated_image_layout.xml b/tests/tests/graphics/res/layout/animated_image_layout.xml
new file mode 100644
index 0000000..73db92a
--- /dev/null
+++ b/tests/tests/graphics/res/layout/animated_image_layout.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.
+-->
+
+<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="wrap_content"
+    android:orientation="vertical"
+    >
+        <ImageView
+            android:id="@+id/animated_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            />
+</LinearLayout>
diff --git a/tests/tests/graphics/res/raw/basi6a16.png b/tests/tests/graphics/res/raw/basi6a16.png
new file mode 100755
index 0000000..4181533
--- /dev/null
+++ b/tests/tests/graphics/res/raw/basi6a16.png
Binary files differ
diff --git a/tests/tests/graphics/res/xml/file_paths.xml b/tests/tests/graphics/res/xml/file_paths.xml
new file mode 100644
index 0000000..8ec4caa
--- /dev/null
+++ b/tests/tests/graphics/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="images" path="images/" />
+</paths>
+
diff --git a/tests/tests/graphics/src/android/graphics/cts/ANativeWindowTest.java b/tests/tests/graphics/src/android/graphics/cts/ANativeWindowTest.java
index e67b5a4..f7cf694 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ANativeWindowTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ANativeWindowTest.java
@@ -192,6 +192,32 @@
         }
     }
 
+    @Test
+    public void testSetBuffersDataSpace() {
+        final int DATASPACE_SRGB = 142671872;
+        final int DATASPACE_UNKNOWN = 123;
+
+        int[] texId = new int[1];
+        GLES20.glGenTextures(1, texId, 0);
+
+        SurfaceTexture consumer = new SurfaceTexture(texId[0]);
+        consumer.setDefaultBufferSize(16, 16);
+        Surface surface = new Surface(consumer);
+
+        assertEquals(nGetBuffersDataSpace(surface), 0);
+        assertEquals(nSetBuffersDataSpace(surface, DATASPACE_SRGB), 0);
+        assertEquals(nGetBuffersDataSpace(surface), DATASPACE_SRGB);
+
+        assertEquals(nSetBuffersDataSpace(null, DATASPACE_SRGB), -22);
+        assertEquals(nGetBuffersDataSpace(null), -22);
+        assertEquals(nGetBuffersDataSpace(surface), DATASPACE_SRGB);
+
+        // set an unsupported data space should return a error code,
+        // the original data space shouldn't change.
+        assertEquals(nSetBuffersDataSpace(surface, DATASPACE_UNKNOWN), -22);
+        assertEquals(nGetBuffersDataSpace(surface), DATASPACE_SRGB);
+    }
+
     // Multiply 4x4 matrices result = a*b. result can be the same as either a or b,
     // allowing for result *= b. Another 4x4 matrix tmp must be provided as scratch space.
     private void matrixMultiply(float[] result, float[] a, float[] b, float[] tmp) {
@@ -219,5 +245,6 @@
     }
 
     private static native void nPushBufferWithTransform(Surface surface, int transform);
-
+    private static native int nSetBuffersDataSpace(Surface surface, int dataSpace);
+    private static native int nGetBuffersDataSpace(Surface surface);
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/ASurfaceTextureTest.java b/tests/tests/graphics/src/android/graphics/cts/ASurfaceTextureTest.java
new file mode 100644
index 0000000..b4e6ed6
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ASurfaceTextureTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.graphics.cts;
+
+import static android.opengl.EGL14.*;
+
+import android.graphics.Canvas;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.support.test.filters.SmallTest;
+import android.util.Log;
+
+import android.view.Surface;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+@SmallTest
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ASurfaceTextureTest {
+
+  static {
+    System.loadLibrary("ctsgraphics_jni");
+  }
+
+  private static final String TAG = ANativeWindowTest.class.getSimpleName();
+  private static final boolean DEBUG = false;
+
+  private EGLDisplay mEglDisplay = EGL_NO_DISPLAY;
+  private EGLConfig mEglConfig = null;
+  private EGLSurface mEglPbuffer = EGL_NO_SURFACE;
+  private EGLContext mEglContext = EGL_NO_CONTEXT;
+
+  @Before
+  public void setup() throws Throwable {
+    mEglDisplay = EGL14.eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (mEglDisplay == EGL_NO_DISPLAY) {
+      throw new RuntimeException("no EGL display");
+    }
+    int[] major = new int[1];
+    int[] minor = new int[1];
+    if (!EGL14.eglInitialize(mEglDisplay, major, 0, minor, 0)) {
+      throw new RuntimeException("error in eglInitialize");
+    }
+
+    // If we could rely on having EGL_KHR_surfaceless_context and EGL_KHR_context_no_config, we
+    // wouldn't have to create a config or pbuffer at all.
+
+    int[] numConfigs = new int[1];
+    EGLConfig[] configs = new EGLConfig[1];
+    if (!EGL14.eglChooseConfig(mEglDisplay,
+        new int[] {
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+            EGL_NONE},
+        0, configs, 0, 1, numConfigs, 0)) {
+      throw new RuntimeException("eglChooseConfig failed");
+    }
+    mEglConfig = configs[0];
+
+    mEglPbuffer = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig,
+        new int[] {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}, 0);
+    if (mEglPbuffer == EGL_NO_SURFACE) {
+      throw new RuntimeException("eglCreatePbufferSurface failed");
+    }
+
+    mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
+        new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}, 0);
+    if (mEglContext == EGL_NO_CONTEXT) {
+      throw new RuntimeException("eglCreateContext failed");
+    }
+
+    if (!EGL14.eglMakeCurrent(mEglDisplay, mEglPbuffer, mEglPbuffer, mEglContext)) {
+      throw new RuntimeException("eglMakeCurrent failed");
+    }
+  }
+
+  @Test
+  public void testBasic() {
+    // create a detached SurfaceTexture
+    SurfaceTexture consumer = new SurfaceTexture(false);
+    consumer.setDefaultBufferSize(640, 480);
+    nBasicTests(consumer);
+  }
+
+  private static native void nBasicTests(SurfaceTexture consumer);
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 29aefbe..1aec304 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -1125,12 +1125,15 @@
         } catch(RuntimeException e){
         }
 
+        p.recycle();
         // normal case
         p = Parcel.obtain();
         mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
         mBitmap.writeToParcel(p, 0);
         p.setDataPosition(0);
         assertTrue(mBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p)));
+
+        p.recycle();
     }
 
     @Test
@@ -1141,6 +1144,8 @@
         p.setDataPosition(0);
         Bitmap expectedBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot);
         assertTrue(expectedBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p)));
+
+        p.recycle();
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java b/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
index b315bc9..0fb5978 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
@@ -35,6 +35,7 @@
 public class BlurMaskFilterTest {
     private static final int OFFSET = 10;
     private static final int RADIUS = 5;
+    private static final int CHECK_RADIUS = 8;
     private static final int BITMAP_WIDTH = 100;
     private static final int BITMAP_HEIGHT = 100;
     private static final int CENTER = BITMAP_HEIGHT / 2;
@@ -51,13 +52,13 @@
         canvas.drawRect(CENTER - OFFSET, CENTER - OFFSET, CENTER + OFFSET, CENTER + OFFSET, paint);
         for (int x = 0; x < CENTER; x++) {
             for (int y = 0; y < CENTER; y++) {
-                if (x < CENTER - OFFSET - RADIUS || y < CENTER - OFFSET - RADIUS) {
+                if (x < CENTER - OFFSET - CHECK_RADIUS || y < CENTER - OFFSET - CHECK_RADIUS) {
                     // check that color didn't bleed (much) beyond radius
                     verifyQuadrants(Color.TRANSPARENT, b, x, y, 5);
                 } else if (x > CENTER - OFFSET + RADIUS && y > CENTER - OFFSET + RADIUS) {
                     // check that color didn't wash out (much) in the center
-                    verifyQuadrants(Color.RED, b, x, y, 5);
-                } else {
+                    verifyQuadrants(Color.RED, b, x, y, 8);
+                } else if (x > CENTER - OFFSET - RADIUS && y > CENTER - OFFSET - RADIUS) {
                     // check blur zone, color should remain, alpha varies
                     verifyQuadrants(Color.RED, b, x, y, 255);
                 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
index b36c23a..36df8a8 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
@@ -20,6 +20,7 @@
 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;
@@ -1428,6 +1429,12 @@
         // 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)
@@ -2123,13 +2130,35 @@
         assertEquals(DisplayMetrics.DENSITY_HIGH, mCanvas.getDensity());
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testDrawHwBitmapInSwCanvas() {
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawHwBitmap_inSwCanvas() {
         Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        mCanvas.drawBitmap(hwBitmap, 0, 0, null);
+        mCanvas.drawBitmap(hwBitmap, 0, 0, null); // we verify this specific call should IAE
     }
 
-    @Test(expected = IllegalStateException.class)
+    @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,
@@ -2142,7 +2171,7 @@
         mCanvas.drawRect(0, 0, 10, 10, p);
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test(expected = IllegalArgumentException.class)
     public void testHwBitmapShaderInSwCanvas2() {
         Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
         BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
@@ -2224,7 +2253,7 @@
             // Verify that the pixel is now max red.
             Assert.assertEquals(0xFFFF0000, canvasBitmap.getPixel(0, 0));
         } catch (IOException e) {
-            Assert.fail();
+            fail();
         }
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 1e65caf..7ae151e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -18,15 +18,20 @@
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.TypedValue;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ColorTest {
@@ -64,14 +69,52 @@
                 { 0xff000000, android.R.color.widget_edittext_dark },
         };
 
+        List<Integer> expectedColorStateLists = Arrays.asList(
+                android.R.color.primary_text_dark,
+                android.R.color.primary_text_dark_nodisable,
+                android.R.color.primary_text_light,
+                android.R.color.primary_text_light_nodisable,
+                android.R.color.secondary_text_dark,
+                android.R.color.secondary_text_dark_nodisable,
+                android.R.color.secondary_text_light,
+                android.R.color.secondary_text_light_nodisable,
+                android.R.color.tab_indicator_text,
+                android.R.color.tertiary_text_dark,
+                android.R.color.tertiary_text_light,
+                android.R.color.widget_edittext_dark
+        );
+
         Resources resources = getInstrumentation().getTargetContext().getResources();
         for (int[] pair : colors) {
-            int value = resources.getColor(pair[1], null);
-            assertEquals("Color = " + Integer.toHexString(value) + ", "
-                            + Integer.toHexString(pair[0]) + " expected",
-                    pair[0],
-                    value);
+            final int resourceId = pair[1];
+            final int expectedColor = pair[0];
 
+            // validate color from getColor
+            int observedColor = resources.getColor(resourceId, null);
+            assertEquals("Color = " + Integer.toHexString(observedColor) + ", "
+                            + Integer.toHexString(expectedColor) + " expected",
+                    expectedColor,
+                    observedColor);
+
+            // validate color from getValue
+            TypedValue value = new TypedValue();
+            resources.getValue(resourceId, value, true);
+
+            // colors shouldn't depend on config changes
+            assertEquals(0, value.changingConfigurations);
+
+            if (expectedColorStateLists.contains(resourceId)) {
+                // ColorStateLists are strings
+                assertEquals("CSLs should be strings", TypedValue.TYPE_STRING, value.type);
+            } else {
+                // colors should be raw ints
+                assertTrue("Type should be int",
+                        value.type >= TypedValue.TYPE_FIRST_INT
+                        && value.type <= TypedValue.TYPE_LAST_INT);
+
+                // Validate color from getValue
+                assertEquals("Color should be expected value", expectedColor, value.data);
+            }
         }
         assertEquals("Test no longer in sync with colors in android.R.color",
                 colors.length,
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
new file mode 100644
index 0000000..12e57470
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -0,0 +1,1635 @@
+/*
+ * 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.graphics.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
+import android.graphics.PixelFormat;
+import android.graphics.PostProcessor;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.content.FileProvider;
+import android.util.Size;
+import android.util.TypedValue;
+
+import com.android.compatibility.common.util.BitmapUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+public class ImageDecoderTest {
+    private Resources mRes;
+    private ContentResolver mContentResolver;
+
+    private static final class Record {
+        public final int resId;
+        public final int width;
+        public final int height;
+        public final String mimeType;
+
+        public Record(int resId, int width, int height, String mimeType) {
+            this.resId    = resId;
+            this.width    = width;
+            this.height   = height;
+            this.mimeType = mimeType;
+        }
+    }
+
+    private static final Record[] RECORDS = new Record[] {
+        new Record(R.drawable.baseline_jpeg, 1280, 960, "image/jpeg"),
+        new Record(R.drawable.png_test, 640, 480, "image/png"),
+        new Record(R.drawable.gif_test, 320, 240, "image/gif"),
+        new Record(R.drawable.bmp_test, 320, 240, "image/bmp"),
+        new Record(R.drawable.webp_test, 640, 480, "image/webp"),
+        new Record(R.drawable.google_chrome, 256, 256, "image/x-ico"),
+        new Record(R.drawable.color_wheel, 128, 128, "image/x-ico"),
+    };
+
+    // offset is how many bytes to offset the beginning of the image.
+    // extra is how many bytes to append at the end.
+    private byte[] getAsByteArray(int resId, int offset, int extra) {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        writeToStream(output, resId, offset, extra);
+        return output.toByteArray();
+    }
+
+    private void writeToStream(OutputStream output, int resId, int offset, int extra) {
+        InputStream input = mRes.openRawResource(resId);
+        byte[] buffer = new byte[4096];
+        int bytesRead;
+        try {
+            for (int i = 0; i < offset; ++i) {
+                output.write(0);
+            }
+
+            while ((bytesRead = input.read(buffer)) != -1) {
+                output.write(buffer, 0, bytesRead);
+            }
+
+            for (int i = 0; i < extra; ++i) {
+                output.write(0);
+            }
+
+            input.close();
+        } catch (IOException e) {
+            fail();
+        }
+    }
+
+    private byte[] getAsByteArray(int resId) {
+        return getAsByteArray(resId, 0, 0);
+    }
+
+    private ByteBuffer getAsByteBufferWrap(int resId) {
+        byte[] buffer = getAsByteArray(resId);
+        return ByteBuffer.wrap(buffer);
+    }
+
+    private ByteBuffer getAsDirectByteBuffer(int resId) {
+        byte[] buffer = getAsByteArray(resId);
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(buffer.length);
+        byteBuffer.put(buffer);
+        byteBuffer.position(0);
+        return byteBuffer;
+    }
+
+    private ByteBuffer getAsReadOnlyByteBuffer(int resId) {
+        return getAsByteBufferWrap(resId).asReadOnlyBuffer();
+    }
+
+    private File getAsFile(int resId) {
+        File file = null;
+        try {
+            Context context = InstrumentationRegistry.getTargetContext();
+            File dir = new File(context.getFilesDir(), "images");
+            dir.mkdirs();
+            file = new File(dir, "test_file" + resId);
+            if (!file.createNewFile()) {
+                if (file.exists()) {
+                    return file;
+                }
+                fail("Failed to create new File!");
+            }
+
+            FileOutputStream output = new FileOutputStream(file);
+            writeToStream(output, resId, 0, 0);
+            output.close();
+
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+            return null;
+        }
+        return file;
+    }
+
+    private Uri getAsFileUri(int resId) {
+        return Uri.fromFile(getAsFile(resId));
+    }
+
+    private Uri getAsContentUri(int resId) {
+        Context context = InstrumentationRegistry.getTargetContext();
+        return FileProvider.getUriForFile(context,
+                "android.graphics.cts.fileprovider", getAsFile(resId));
+    }
+
+    private Uri getAsResourceUri(int resId) {
+        return new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .authority(mRes.getResourcePackageName(resId))
+                .appendPath(mRes.getResourceTypeName(resId))
+                .appendPath(mRes.getResourceEntryName(resId))
+                .build();
+    }
+
+    private interface SourceCreator extends IntFunction<ImageDecoder.Source> {};
+
+    private SourceCreator mCreators[] = new SourceCreator[] {
+        resId -> ImageDecoder.createSource(getAsByteBufferWrap(resId)),
+        resId -> ImageDecoder.createSource(getAsDirectByteBuffer(resId)),
+        resId -> ImageDecoder.createSource(getAsReadOnlyByteBuffer(resId)),
+        resId -> ImageDecoder.createSource(getAsFile(resId)),
+    };
+
+    private interface UriCreator extends IntFunction<Uri> {};
+
+    @Test
+    public void testUris() {
+        UriCreator creators[] = new UriCreator[] {
+            resId -> getAsResourceUri(resId),
+            resId -> getAsFileUri(resId),
+            resId -> getAsContentUri(resId),
+        };
+        for (Record record : RECORDS) {
+            int resId = record.resId;
+            String name = mRes.getResourceEntryName(resId);
+            for (UriCreator f : creators) {
+                ImageDecoder.Source src = null;
+                Uri uri = f.apply(resId);
+                String fullName = name + ": " + uri.toString();
+                src = ImageDecoder.createSource(mContentResolver, uri);
+
+                assertNotNull("failed to create Source for " + fullName, src);
+                try {
+                    Drawable d = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        decoder.setOnPartialImageListener((e, source) -> {
+                            fail("error for image " + fullName + ":\n" + e);
+                            return false;
+                        });
+                    });
+                    assertNotNull("failed to create drawable for " + fullName, d);
+                } catch (IOException e) {
+                    fail("exception for image " + fullName + ":\n" + e);
+                }
+            }
+        }
+    }
+
+    @Before
+    public void setup() {
+        mRes = InstrumentationRegistry.getTargetContext().getResources();
+        mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+    }
+
+    @Test
+    public void testInfo() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public int mWidth;
+            public int mHeight;
+            public String mMimeType;
+
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                mWidth  = info.getSize().getWidth();
+                mHeight = info.getSize().getHeight();
+                mMimeType = info.getMimeType();
+            }
+        };
+        Listener l = new Listener();
+
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                ImageDecoder.Source src = f.apply(record.resId);
+                assertNotNull(src);
+                try {
+                    ImageDecoder.decodeDrawable(src, l);
+                    assertEquals(record.width,  l.mWidth);
+                    assertEquals(record.height, l.mHeight);
+                    assertEquals(record.mimeType, l.mMimeType);
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testDecodeDrawable() {
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                ImageDecoder.Source src = f.apply(record.resId);
+                assertNotNull(src);
+
+                try {
+                    Drawable drawable = ImageDecoder.decodeDrawable(src);
+                    assertNotNull(drawable);
+                    assertEquals(record.width,  drawable.getIntrinsicWidth());
+                    assertEquals(record.height, drawable.getIntrinsicHeight());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testDecodeBitmap() {
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                ImageDecoder.Source src = f.apply(record.resId);
+                assertNotNull(src);
+
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src);
+                    assertNotNull(bm);
+                    assertEquals(record.width, bm.getWidth());
+                    assertEquals(record.height, bm.getHeight());
+                    assertFalse(bm.isMutable());
+                    // FIXME: This may change for small resources, etc.
+                    assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testSetBogusAllocator() {
+        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        try {
+            ImageDecoder.decodeBitmap(src, (decoder, info, s) -> decoder.setAllocator(15));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testSetAllocatorDecodeBitmap() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public int allocator;
+            public boolean doCrop;
+            public boolean doScale;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setAllocator(allocator);
+                if (doScale) {
+                    decoder.setResize(2);
+                }
+                if (doCrop) {
+                    decoder.setCrop(new Rect(1, 1, info.getSize().getWidth()  / 2 - 1,
+                                                   info.getSize().getHeight() / 2 - 1));
+                }
+            }
+        };
+        Listener l = new Listener();
+
+        int allocators[] = new int[] {
+            ImageDecoder.ALLOCATOR_DEFAULT,
+            ImageDecoder.ALLOCATOR_SOFTWARE,
+            ImageDecoder.ALLOCATOR_SHARED_MEMORY,
+            ImageDecoder.ALLOCATOR_HARDWARE,
+        };
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (int allocator : allocators) {
+                    for (boolean doCrop : trueFalse) {
+                        for (boolean doScale : trueFalse) {
+                            l.doCrop = doCrop;
+                            l.doScale = doScale;
+                            l.allocator = allocator;
+                            ImageDecoder.Source src = f.apply(record.resId);
+                            assertNotNull(src);
+
+                            Bitmap bm = null;
+                            try {
+                               bm = ImageDecoder.decodeBitmap(src, l);
+                            } catch (IOException e) {
+                                fail("Failed " + getAsResourceUri(record.resId) +
+                                        " with exception " + e);
+                            }
+                            assertNotNull(bm);
+
+                            switch (allocator) {
+                                case ImageDecoder.ALLOCATOR_SOFTWARE:
+                                // TODO: Once Bitmap provides access to its
+                                // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
+                                // worked.
+                                case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
+                                    assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+
+                                    if (!doScale && !doCrop) {
+                                        Bitmap reference = BitmapFactory.decodeResource(mRes,
+                                                record.resId, null);
+                                        assertNotNull(reference);
+                                        BitmapUtils.compareBitmaps(bm, reference);
+                                    }
+                                    break;
+                                default:
+                                    String name = getAsResourceUri(record.resId).toString();
+                                    assertEquals("image " + name + "; allocator: " + allocator,
+                                                 Bitmap.Config.HARDWARE, bm.getConfig());
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testUnpremul() {
+        int[] resIds = new int[] { R.drawable.png_test, R.drawable.alpha };
+        boolean[] hasAlpha = new boolean[] { false,     true };
+        for (int i = 0; i < resIds.length; ++i) {
+            for (SourceCreator f : mCreators) {
+                // Normal decode
+                ImageDecoder.Source src = f.apply(resIds[i]);
+                assertNotNull(src);
+
+                try {
+                    Bitmap normal = ImageDecoder.decodeBitmap(src);
+                    assertNotNull(normal);
+                    assertEquals(normal.hasAlpha(), hasAlpha[i]);
+                    assertEquals(normal.isPremultiplied(), hasAlpha[i]);
+
+                    // Require unpremul
+                    src = f.apply(resIds[i]);
+                    assertNotNull(src);
+
+                    Bitmap unpremul = ImageDecoder.decodeBitmap(src,
+                            (decoder, info, s) -> decoder.setRequireUnpremultiplied(true));
+                    assertNotNull(unpremul);
+                    assertEquals(unpremul.hasAlpha(), hasAlpha[i]);
+                    assertFalse(unpremul.isPremultiplied());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPostProcessor() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean requireSoftware;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (requireSoftware) {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+                decoder.setPostProcessor((canvas) -> {
+                    canvas.drawColor(Color.BLACK);
+                    return PixelFormat.OPAQUE;
+                });
+            }
+        };
+        Listener l = new Listener();
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (boolean requireSoftware : trueFalse) {
+                    l.requireSoftware = requireSoftware;
+                    ImageDecoder.Source src = f.apply(record.resId);
+                    assertNotNull(src);
+
+                    Bitmap bitmap = null;
+                    try {
+                        bitmap = ImageDecoder.decodeBitmap(src, l);
+                    } catch (IOException e) {
+                        fail("Failed with exception " + e);
+                    }
+                    assertNotNull(bitmap);
+                    assertFalse(bitmap.isMutable());
+                    if (requireSoftware) {
+                        assertNotEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
+                        for (int x = 0; x < bitmap.getWidth(); ++x) {
+                            for (int y = 0; y < bitmap.getHeight(); ++y) {
+                                int color = bitmap.getPixel(x, y);
+                                assertEquals("pixel at (" + x + ", " + y + ") does not match!",
+                                        color, Color.BLACK);
+                            }
+                        }
+                    } else {
+                        assertEquals(bitmap.getConfig(), Bitmap.Config.HARDWARE);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testNinepatchWithDensityNone() {
+        TypedValue value = new TypedValue();
+        InputStream is = mRes.openRawResource(R.drawable.ninepatch_nodpi, value);
+        // This does not call ImageDecoder directly because this entry point is not public.
+        Drawable dr = Drawable.createFromResourceStream(mRes, value, is, null, null);
+        assertNotNull(dr);
+        assertEquals(5, dr.getIntrinsicWidth());
+        assertEquals(5, dr.getIntrinsicHeight());
+    }
+
+    @Test
+    public void testPostProcessorOverridesNinepatch() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean requireSoftware;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (requireSoftware) {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+                decoder.setPostProcessor((c) -> PixelFormat.UNKNOWN);
+            }
+        };
+        Listener l = new Listener();
+        int resIds[] = new int[] { R.drawable.ninepatch_0,
+                                   R.drawable.ninepatch_1 };
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (int resId : resIds) {
+            for (SourceCreator f : mCreators) {
+                for (boolean requireSoftware : trueFalse) {
+                    l.requireSoftware = requireSoftware;
+                    ImageDecoder.Source src = f.apply(resId);
+                    try {
+                        Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                        assertFalse(drawable instanceof NinePatchDrawable);
+
+                        src = f.apply(resId);
+                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                        assertNull(bm.getNinePatchChunk());
+                    } catch (IOException e) {
+                        fail("Failed with exception " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPostProcessorAndMadeOpaque() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean requireSoftware;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (requireSoftware) {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+                decoder.setPostProcessor((c) -> PixelFormat.OPAQUE);
+            }
+        };
+        Listener l = new Listener();
+        boolean trueFalse[] = new boolean[] { true, false };
+        int resIds[] = new int[] { R.drawable.alpha, R.drawable.google_logo_2 };
+        for (int resId : resIds) {
+            for (SourceCreator f : mCreators) {
+                for (boolean requireSoftware : trueFalse) {
+                    l.requireSoftware = requireSoftware;
+                    ImageDecoder.Source src = f.apply(resId);
+                    try {
+                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                        assertFalse(bm.hasAlpha());
+                        assertFalse(bm.isPremultiplied());
+                    } catch (IOException e) {
+                        fail("Failed with exception " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPostProcessorAndAddedTransparency() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean requireSoftware;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (requireSoftware) {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+                decoder.setPostProcessor((c) -> PixelFormat.TRANSLUCENT);
+            }
+        };
+        Listener l = new Listener();
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (boolean requireSoftware : trueFalse) {
+                    l.requireSoftware = requireSoftware;
+                    ImageDecoder.Source src = f.apply(record.resId);
+                    try {
+                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                        assertTrue(bm.hasAlpha());
+                        assertTrue(bm.isPremultiplied());
+                    } catch (IOException e) {
+                        fail("Failed with exception " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testPostProcessorTRANSPARENT() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setPostProcessor((c) -> PixelFormat.TRANSPARENT);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testPostProcessorInvalidReturn() {
+        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setPostProcessor((c) -> 42);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testPostProcessorAndUnpremul() {
+        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setRequireUnpremultiplied(true);
+                decoder.setPostProcessor((c) -> PixelFormat.UNKNOWN);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testPostProcessorAndScale() {
+        class PostProcessorWithSize implements PostProcessor {
+            public int width;
+            public int height;
+            @Override
+            public int onPostProcess(Canvas canvas) {
+                assertEquals(this.width,  width);
+                assertEquals(this.height, height);
+                return PixelFormat.UNKNOWN;
+            };
+        };
+        final PostProcessorWithSize pp = new PostProcessorWithSize();
+        for (Record record : RECORDS) {
+            pp.width =  record.width  / 2;
+            pp.height = record.height / 2;
+            for (SourceCreator f : mCreators) {
+                ImageDecoder.Source src = f.apply(record.resId);
+                try {
+                    Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        decoder.setResize(pp.width, pp.height);
+                        decoder.setPostProcessor(pp);
+                    });
+                    assertEquals(pp.width,  drawable.getIntrinsicWidth());
+                    assertEquals(pp.height, drawable.getIntrinsicHeight());
+                } catch (IOException e) {
+                    fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testGetSampledSize() {
+        class SampleListener implements ImageDecoder.OnHeaderDecodedListener {
+            public Size dimensions;
+            public int sampleSize;
+            public boolean useSampleSizeDirectly;
+
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (useSampleSizeDirectly) {
+                    decoder.setResize(sampleSize);
+                } else {
+                    dimensions = decoder.getSampledSize(sampleSize);
+                    decoder.setResize(dimensions.getWidth(), dimensions.getHeight());
+                }
+            }
+        };
+
+        SampleListener l = new SampleListener();
+
+        int[] sampleSizes = new int[] { 1, 2, 3, 4, 6, 8, 16, 32, 64 };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (int j = 0; j < sampleSizes.length; j++) {
+                    l.sampleSize = sampleSizes[j];
+                    l.useSampleSizeDirectly = false;
+                    ImageDecoder.Source src = f.apply(record.resId);
+
+                    try {
+                        Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                        assertEquals(l.dimensions.getWidth(),  drawable.getIntrinsicWidth());
+                        assertEquals(l.dimensions.getHeight(), drawable.getIntrinsicHeight());
+
+                        l.useSampleSizeDirectly = true;
+                        src = f.apply(record.resId);
+                        drawable = ImageDecoder.decodeDrawable(src, l);
+                        assertEquals(l.dimensions.getWidth(),  drawable.getIntrinsicWidth());
+                        assertEquals(l.dimensions.getHeight(), drawable.getIntrinsicHeight());
+                    } catch (IOException e) {
+                        fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testLargeSampleSize() {
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                ImageDecoder.Source src = f.apply(record.resId);
+                try {
+                    ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        Size dimensions = decoder.getSampledSize(info.getSize().getWidth());
+                        assertEquals(dimensions.getWidth(), 1);
+
+                        dimensions = decoder.getSampledSize(info.getSize().getWidth() + 5);
+                        assertEquals(dimensions.getWidth(), 1);
+
+                        dimensions = decoder.getSampledSize(info.getSize().getWidth() * 2);
+                        assertEquals(dimensions.getWidth(), 1);
+                    });
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testOnPartialImage() {
+        class PartialImageCallback implements ImageDecoder.OnPartialImageListener {
+            public boolean wasCalled;
+            public boolean returnDrawable;
+            @Override
+            public boolean onPartialImage(int error, ImageDecoder.Source src) {
+                wasCalled = true;
+                assertEquals(ImageDecoder.ERROR_SOURCE_INCOMPLETE, error);
+                return returnDrawable;
+            }
+        };
+        final PartialImageCallback callback = new PartialImageCallback();
+        boolean abortDecode[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            byte[] bytes = getAsByteArray(record.resId);
+            int truncatedLength = bytes.length / 2;
+            if (record.mimeType == "image/x-ico") {
+                // FIXME (scroggo): SkIcoCodec currently does not support incomplete images.
+                continue;
+            }
+            for (boolean abort : abortDecode) {
+                ImageDecoder.Source src = ImageDecoder.createSource(
+                        ByteBuffer.wrap(bytes, 0, truncatedLength));
+                callback.wasCalled = false;
+                callback.returnDrawable = !abort;
+                try {
+                    Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        decoder.setOnPartialImageListener(callback);
+                    });
+                    assertFalse(abort);
+                    assertNotNull(drawable);
+                    assertEquals(record.width,  drawable.getIntrinsicWidth());
+                    assertEquals(record.height, drawable.getIntrinsicHeight());
+                } catch (IOException e) {
+                    assertTrue(abort);
+                }
+                assertTrue(callback.wasCalled);
+            }
+
+            // null listener behaves as if onPartialImage returned false.
+            ImageDecoder.Source src = ImageDecoder.createSource(
+                    ByteBuffer.wrap(bytes, 0, truncatedLength));
+            try {
+                ImageDecoder.decodeDrawable(src);
+                fail("Should have thrown an exception!");
+            } catch (ImageDecoder.IncompleteException incomplete) {
+                // This is the correct behavior.
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
+            }
+        }
+    }
+
+    @Test
+    public void testCorruptException() {
+        class PartialImageCallback implements ImageDecoder.OnPartialImageListener {
+            public boolean wasCalled = false;
+            @Override
+            public boolean onPartialImage(int error, ImageDecoder.Source src) {
+                wasCalled = true;
+                assertEquals(ImageDecoder.ERROR_SOURCE_ERROR, error);
+                return true;
+            }
+        };
+        final PartialImageCallback callback = new PartialImageCallback();
+        byte[] bytes = getAsByteArray(R.drawable.png_test);
+        // The four bytes starting with byte 40,000 represent the CRC. Changing
+        // them will cause the decode to fail.
+        for (int i = 0; i < 4; ++i) {
+            bytes[40000 + i] = 'X';
+        }
+        ImageDecoder.Source src = ImageDecoder.createSource(ByteBuffer.wrap(bytes));
+        callback.wasCalled = false;
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setOnPartialImageListener(callback);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+        assertTrue(callback.wasCalled);
+    }
+
+    private static class DummyException extends RuntimeException {};
+
+    @Test
+    public void  testPartialImageThrowException() {
+        byte[] bytes = getAsByteArray(R.drawable.png_test);
+        ImageDecoder.Source src = ImageDecoder.createSource(
+                ByteBuffer.wrap(bytes, 0, bytes.length / 2));
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setOnPartialImageListener((e, source) -> {
+                    throw new DummyException();
+                });
+            });
+            fail("Should have thrown an exception");
+        } catch (DummyException dummy) {
+            // This is correct.
+        } catch (Throwable t) {
+            fail("Should have thrown DummyException - threw " + t + " instead");
+        }
+    }
+
+    @Test
+    public void testMutable() {
+        int allocators[] = new int[] { ImageDecoder.ALLOCATOR_DEFAULT,
+                                       ImageDecoder.ALLOCATOR_SOFTWARE,
+                                       ImageDecoder.ALLOCATOR_SHARED_MEMORY };
+        class HeaderListener implements ImageDecoder.OnHeaderDecodedListener {
+            int allocator;
+            boolean postProcess;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder,
+                                        ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setMutable(true);
+                decoder.setAllocator(allocator);
+                if (postProcess) {
+                    decoder.setPostProcessor((c) -> PixelFormat.UNKNOWN);
+                }
+            }
+        };
+        HeaderListener l = new HeaderListener();
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (boolean postProcess : trueFalse) {
+                    for (int allocator : allocators) {
+                        l.allocator = allocator;
+                        l.postProcess = postProcess;
+
+                        ImageDecoder.Source src = f.apply(record.resId);
+                        try {
+                            Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                            assertTrue(bm.isMutable());
+                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+                        } catch (IOException e) {
+                            fail("Failed with exception " + e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testMutableHardware() {
+        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        try {
+            ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setMutable(true);
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testMutableDrawable() {
+        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setMutable(true);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    private interface EmptyByteBufferCreator {
+        public ByteBuffer apply();
+    };
+
+    @Test
+    public void testEmptyByteBuffer() {
+        class Direct implements EmptyByteBufferCreator {
+            @Override
+            public ByteBuffer apply() {
+                return ByteBuffer.allocateDirect(0);
+            }
+        };
+        class Wrap implements EmptyByteBufferCreator {
+            @Override
+            public ByteBuffer apply() {
+                byte[] bytes = new byte[0];
+                return ByteBuffer.wrap(bytes);
+            }
+        };
+        class ReadOnly implements EmptyByteBufferCreator {
+            @Override
+            public ByteBuffer apply() {
+                byte[] bytes = new byte[0];
+                return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+            }
+        };
+        EmptyByteBufferCreator creators[] = new EmptyByteBufferCreator[] {
+            new Direct(), new Wrap(), new ReadOnly() };
+        for (EmptyByteBufferCreator creator : creators) {
+            try {
+                ImageDecoder.decodeDrawable(
+                        ImageDecoder.createSource(creator.apply()));
+                fail("This should have thrown an exception");
+            } catch (IOException e) {
+                // This is correct.
+            }
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testZeroSampleSize() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> decoder.getSampledSize(0));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testNegativeSampleSize() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> decoder.getSampledSize(-2));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testResize() {
+        class ResizeListener implements ImageDecoder.OnHeaderDecodedListener {
+            public int width;
+            public int height;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setResize(width, height);
+            }
+        };
+        ResizeListener l = new ResizeListener();
+
+        float[] scales = new float[] { .0625f, .125f, .25f, .5f, .75f, 1.1f, 2.0f };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (int j = 0; j < scales.length; ++j) {
+                    l.width  = (int) (scales[j] * record.width);
+                    l.height = (int) (scales[j] * record.height);
+
+                    ImageDecoder.Source src = f.apply(record.resId);
+
+                    try {
+                        Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                        assertEquals(l.width,  drawable.getIntrinsicWidth());
+                        assertEquals(l.height, drawable.getIntrinsicHeight());
+
+                        src = f.apply(record.resId);
+                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                        assertEquals(l.width,  bm.getWidth());
+                        assertEquals(l.height, bm.getHeight());
+                    } catch (IOException e) {
+                        fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
+                    }
+                }
+
+                try {
+                    // Arbitrary square.
+                    l.width  = 50;
+                    l.height = 50;
+                    ImageDecoder.Source src = f.apply(record.resId);
+                    Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                    assertEquals(50,  drawable.getIntrinsicWidth());
+                    assertEquals(50, drawable.getIntrinsicHeight());
+
+                    // Swap width and height, for different scales.
+                    l.height = record.width;
+                    l.width  = record.height;
+                    src = f.apply(record.resId);
+                    drawable = ImageDecoder.decodeDrawable(src, l);
+                    assertEquals(record.height, drawable.getIntrinsicWidth());
+                    assertEquals(record.width,  drawable.getIntrinsicHeight());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testResizeWebp() {
+        // libwebp supports unpremultiplied for downscaled output
+        class ResizeListener implements ImageDecoder.OnHeaderDecodedListener {
+            public int width;
+            public int height;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setResize(width, height);
+                decoder.setRequireUnpremultiplied(true);
+            }
+        };
+        ResizeListener l = new ResizeListener();
+
+        float[] scales = new float[] { .0625f, .125f, .25f, .5f, .75f };
+        for (SourceCreator f : mCreators) {
+            for (int j = 0; j < scales.length; ++j) {
+                l.width  = (int) (scales[j] * 240);
+                l.height = (int) (scales[j] *  87);
+
+                ImageDecoder.Source src = f.apply(R.drawable.google_logo_2);
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                    assertEquals(l.width,  bm.getWidth());
+                    assertEquals(l.height, bm.getHeight());
+                    assertTrue(bm.hasAlpha());
+                    assertFalse(bm.isPremultiplied());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testResizeWebpLarger() {
+        // libwebp does not upscale, so there is no way to get unpremul.
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.google_logo_2);
+        try {
+            ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setResize(info.getSize().getWidth() * 2, info.getSize().getHeight() * 2);
+                decoder.setRequireUnpremultiplied(true);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testResizeUnpremul() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.alpha);
+        try {
+            ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                // Choose a width and height that cannot be achieved with sampling.
+                Size dims = decoder.getSampledSize(2);
+                decoder.setResize(dims.getWidth() + 3, dims.getHeight() + 3);
+                decoder.setRequireUnpremultiplied(true);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testCrop() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean doScale;
+            public boolean requireSoftware;
+            public Rect cropRect;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                int width  = info.getSize().getWidth();
+                int height = info.getSize().getHeight();
+                if (doScale) {
+                    width  /= 2;
+                    height /= 2;
+                    decoder.setResize(width, height);
+                }
+                // Crop to the middle:
+                int quarterWidth  = width  / 4;
+                int quarterHeight = height / 4;
+                cropRect = new Rect(quarterWidth, quarterHeight,
+                        quarterWidth * 3, quarterHeight * 3);
+                decoder.setCrop(cropRect);
+
+                if (requireSoftware) {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+            }
+        };
+        Listener l = new Listener();
+        boolean trueFalse[] = new boolean[] { true, false };
+        for (Record record : RECORDS) {
+            for (SourceCreator f : mCreators) {
+                for (boolean doScale : trueFalse) {
+                    l.doScale = doScale;
+                    for (boolean requireSoftware : trueFalse) {
+                        l.requireSoftware = requireSoftware;
+                        ImageDecoder.Source src = f.apply(record.resId);
+
+                        try {
+                            Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                            assertEquals(l.cropRect.width(),  drawable.getIntrinsicWidth());
+                            assertEquals(l.cropRect.height(), drawable.getIntrinsicHeight());
+                        } catch (IOException e) {
+                            fail("Failed " + getAsResourceUri(record.resId) +
+                                    " with exception " + e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testResizeZeroX() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setResize(0, info.getSize().getHeight()));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testResizeZeroY() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setResize(info.getSize().getWidth(), 0));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testResizeNegativeX() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setResize(-10, info.getSize().getHeight()));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testResizeNegativeY() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setResize(info.getSize().getWidth(), -10));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testStoreImageDecoder() {
+        class CachingCallback implements ImageDecoder.OnHeaderDecodedListener {
+            ImageDecoder cachedDecoder;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                cachedDecoder = decoder;
+            }
+        };
+        CachingCallback l = new CachingCallback();
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, l);
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+        l.cachedDecoder.getSampledSize(2);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testDecodeUnpremulDrawable() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setRequireUnpremultiplied(true));
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testCropNegativeLeft() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setCrop(new Rect(-1, 0, info.getSize().getWidth(),
+                                                info.getSize().getHeight()));
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testCropNegativeTop() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setCrop(new Rect(0, -1, info.getSize().getWidth(),
+                                                info.getSize().getHeight()));
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testCropTooWide() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setCrop(new Rect(1, 0, info.getSize().getWidth() + 1,
+                                               info.getSize().getHeight()));
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testCropTooTall() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setCrop(new Rect(0, 1, info.getSize().getWidth(),
+                                               info.getSize().getHeight() + 1));
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testCropResize() {
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setResize(info.getSize().getWidth() / 2, info.getSize().getHeight() / 2);
+                decoder.setCrop(new Rect(0, 0, info.getSize().getWidth(),
+                                               info.getSize().getHeight()));
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testAlphaMaskNonGray() {
+        // It is safe to call setAsAlphaMask on a non-gray image.
+        SourceCreator f = mCreators[0];
+        ImageDecoder.Source src = f.apply(R.drawable.png_test);
+        assertNotNull(src);
+        try {
+            Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setAsAlphaMask(true);
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+            assertNotNull(bm);
+            assertNotEquals(Bitmap.Config.ALPHA_8, bm.getConfig());
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testAlphaMaskPlusHardware() {
+        SourceCreator f = mCreators[0];
+        ImageDecoder.Source src = f.apply(R.drawable.png_test);
+        assertNotNull(src);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setAsAlphaMask(true);
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
+            });
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+    }
+
+    @Test
+    public void testAlphaMask() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            boolean doCrop;
+            boolean doScale;
+            boolean doPostProcess;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setAsAlphaMask(true);
+                if (doScale) {
+                    decoder.setResize(info.getSize().getWidth() / 2,
+                                      info.getSize().getHeight() / 2);
+                }
+                if (doCrop) {
+                    decoder.setCrop(new Rect(0, 0, info.getSize().getWidth() / 4,
+                                                   info.getSize().getHeight() / 4));
+                }
+                if (doPostProcess) {
+                    decoder.setPostProcessor((c) -> {
+                        c.drawColor(Color.BLACK);
+                        return PixelFormat.UNKNOWN;
+                    });
+                }
+            }
+        };
+        Listener l = new Listener();
+        // Both of these are encoded as single channel gray images.
+        int resIds[] = new int[] { R.drawable.grayscale_png, R.drawable.grayscale_jpg };
+        boolean trueFalse[] = new boolean[] { true, false };
+        SourceCreator f = mCreators[0];
+        for (int resId : resIds) {
+            // By default, this will decode to HARDWARE
+            ImageDecoder.Source src = f.apply(resId);
+            try {
+                Bitmap bm  = ImageDecoder.decodeBitmap(src);
+                assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
+            }
+
+            // Now set alpha mask, which is incompatible with HARDWARE
+            for (boolean doCrop : trueFalse) {
+                for (boolean doScale : trueFalse) {
+                    for (boolean doPostProcess : trueFalse) {
+                        l.doCrop = doCrop;
+                        l.doScale = doScale;
+                        l.doPostProcess = doPostProcess;
+                        src = f.apply(resId);
+                        try {
+                            Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+                        } catch (IOException e) {
+                            fail("Failed with exception " + e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testConserveMemoryPlusHardware() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            int allocator;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setConserveMemory(true);
+                decoder.setAllocator(allocator);
+            }
+        };
+        Listener l = new Listener();
+        int resIds[] = new int[] { R.drawable.png_test, R.raw.basi6a16 };
+        // Though png_test is opaque, using HARDWARE will require 8888, so we
+        // do not decode to 565. basi6a16 will still downconvert from F16 to
+        // 8888.
+        boolean hardwareOverrides[] = new boolean[] { true, false };
+        int[] allocators = new int[] { ImageDecoder.ALLOCATOR_HARDWARE,
+                                       ImageDecoder.ALLOCATOR_SOFTWARE,
+                                       ImageDecoder.ALLOCATOR_DEFAULT,
+                                       ImageDecoder.ALLOCATOR_SHARED_MEMORY };
+        SourceCreator f = mCreators[0];
+        for (int i = 0; i < resIds.length; ++i) {
+            Bitmap normal = null;
+            try {
+                normal = ImageDecoder.decodeBitmap(f.apply(resIds[i]));
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
+            }
+            assertNotNull(normal);
+            int normalByteCount = normal.getAllocationByteCount();
+            for (int allocator : allocators) {
+                l.allocator = allocator;
+                Bitmap test = null;
+                try {
+                   test = ImageDecoder.decodeBitmap(f.apply(resIds[i]), l);
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+                assertNotNull(test);
+                int byteCount = test.getAllocationByteCount();
+                if ((allocator == ImageDecoder.ALLOCATOR_HARDWARE ||
+                     allocator == ImageDecoder.ALLOCATOR_DEFAULT)
+                    && hardwareOverrides[i]) {
+                    assertEquals(normalByteCount, byteCount);
+                } else {
+                    assertTrue(byteCount < normalByteCount);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testConserveMemory() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            boolean doPostProcess;
+            boolean preferRamOverQuality;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (preferRamOverQuality) {
+                    decoder.setConserveMemory(true);
+                }
+                if (doPostProcess) {
+                    decoder.setPostProcessor((c) -> {
+                        c.drawColor(Color.BLACK);
+                        return PixelFormat.TRANSLUCENT;
+                    });
+                }
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            }
+        };
+        Listener l = new Listener();
+        // All of these images are opaque, so they can save RAM with
+        // setConserveMemory.
+        int resIds[] = new int[] { R.drawable.png_test, R.drawable.baseline_jpeg,
+                                   // 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 };
+        // 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.
+        boolean postProcessCancels[] = new boolean[] { true, true, false };
+        boolean trueFalse[] = new boolean[] { true, false };
+        SourceCreator f = mCreators[0];
+        for (int i = 0; i < resIds.length; ++i) {
+            int resId = resIds[i];
+            l.doPostProcess = false;
+            l.preferRamOverQuality = false;
+            Bitmap normal = null;
+            try {
+                normal = ImageDecoder.decodeBitmap(f.apply(resId), l);
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
+            }
+            int normalByteCount = normal.getAllocationByteCount();
+            for (boolean doPostProcess : trueFalse) {
+                l.doPostProcess = doPostProcess;
+                l.preferRamOverQuality = true;
+                Bitmap saveRamOverQuality = null;
+                try {
+                    saveRamOverQuality = ImageDecoder.decodeBitmap(f.apply(resId), l);
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+                int saveByteCount = saveRamOverQuality.getAllocationByteCount();
+                if (doPostProcess && postProcessCancels[i]) {
+                    // Promoted to normal.
+                    assertEquals(normalByteCount, saveByteCount);
+                } else {
+                    assertTrue(saveByteCount < normalByteCount);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRespectOrientation() {
+        // These 8 images test the 8 EXIF orientations. If the orientation is
+        // respected, they all have the same dimensions: 100 x 80.
+        // They are also identical (after adjusting), so compare them.
+        Bitmap reference = null;
+        for (int resId : new int[] { R.drawable.orientation_1,
+                                     R.drawable.orientation_2,
+                                     R.drawable.orientation_3,
+                                     R.drawable.orientation_4,
+                                     R.drawable.orientation_5,
+                                     R.drawable.orientation_6,
+                                     R.drawable.orientation_7,
+                                     R.drawable.orientation_8,
+                                     R.drawable.webp_orientation1,
+                                     R.drawable.webp_orientation2,
+                                     R.drawable.webp_orientation3,
+                                     R.drawable.webp_orientation4,
+                                     R.drawable.webp_orientation5,
+                                     R.drawable.webp_orientation6,
+                                     R.drawable.webp_orientation7,
+                                     R.drawable.webp_orientation8,
+        }) {
+            if (resId == R.drawable.webp_orientation1) {
+                // The webp files may not look exactly the same as the jpegs.
+                // Recreate the reference.
+                reference = null;
+            }
+            Uri uri = getAsResourceUri(resId);
+            ImageDecoder.Source src = ImageDecoder.createSource(mContentResolver, uri);
+            try {
+                Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                    // Use software allocator so we can compare.
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                });
+                assertNotNull(bm);
+                assertEquals(100, bm.getWidth());
+                assertEquals(80,  bm.getHeight());
+
+                if (reference == null) {
+                    reference = bm;
+                } else {
+                    BitmapUtils.compareBitmaps(bm, reference);
+                }
+            } catch (IOException e) {
+                fail("Decoding " + uri.toString() + " yielded " + e);
+            }
+        }
+    }
+
+    @Test(expected=IOException.class)
+    public void testZeroLengthByteBuffer() throws IOException {
+        Drawable drawable = ImageDecoder.decodeDrawable(
+            ImageDecoder.createSource(ByteBuffer.wrap(new byte[10], 0, 0)));
+        fail("should not have reached here!");
+    }
+
+    private interface ByteBufferSupplier extends Supplier<ByteBuffer> {};
+
+    @Test
+    public void testOffsetByteArray() {
+        for (Record record : RECORDS) {
+            int offset = 10;
+            int extra = 15;
+            byte[] array = getAsByteArray(record.resId, offset, extra);
+            int length = array.length - extra - offset;
+            // Used for SourceCreators that set both a position and an offset.
+            int myOffset = 3;
+            int myPosition = 7;
+            assertEquals(offset, myOffset + myPosition);
+
+            ByteBufferSupplier[] suppliers = new ByteBufferSupplier[] {
+                    // Internally, this gives the buffer a position, but not an offset.
+                    () -> ByteBuffer.wrap(array, offset, length),
+                    // Same, but make it readOnly to ensure that we test the
+                    // ByteBufferSource rather than the ByteArraySource.
+                    () -> ByteBuffer.wrap(array, offset, length).asReadOnlyBuffer(),
+                    () -> {
+                        // slice() to give the buffer an offset.
+                        ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+                        buf.position(offset);
+                        return buf.slice();
+                    },
+                    () -> {
+                        // Same, but make it readOnly to ensure that we test the
+                        // ByteBufferSource rather than the ByteArraySource.
+                        ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+                        buf.position(offset);
+                        return buf.slice().asReadOnlyBuffer();
+                    },
+                    () -> {
+                        // Use both a position and an offset.
+                        ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                            array.length - extra - myOffset);
+                        buf = buf.slice();
+                        buf.position(myPosition);
+                        return buf;
+                    },
+                    () -> {
+                        // Same, as readOnly.
+                        ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                                array.length - extra - myOffset);
+                        buf = buf.slice();
+                        buf.position(myPosition);
+                        return buf.asReadOnlyBuffer();
+                    },
+                    () -> {
+                        // Direct ByteBuffer with a position.
+                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                        buf.put(array);
+                        buf.position(offset);
+                        return buf;
+                    },
+                    () -> {
+                        // Sliced direct ByteBuffer, for an offset.
+                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                        buf.put(array);
+                        buf.position(offset);
+                        return buf.slice();
+                    },
+                    () -> {
+                        // Direct ByteBuffer with position and offset.
+                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                        buf.put(array);
+                        buf.position(myOffset);
+                        buf = buf.slice();
+                        buf.position(myPosition);
+                        return buf;
+                    },
+            };
+            for (int i = 0; i < suppliers.length; ++i) {
+                ByteBuffer buffer = suppliers[i].get();
+                final int position = buffer.position();
+                ImageDecoder.Source src = ImageDecoder.createSource(buffer);
+                try {
+                    Drawable drawable = ImageDecoder.decodeDrawable(src);
+                    assertNotNull(drawable);
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+                assertEquals("Mismatch for supplier " + i,
+                        position, buffer.position());
+            }
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 0d70947..1cc86a3 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -18,7 +18,6 @@
 
 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;
 
@@ -39,7 +38,6 @@
 import android.graphics.Shader;
 import android.graphics.Typeface;
 import android.graphics.Xfermode;
-import android.graphics.fonts.FontVariationAxis;
 import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -1642,4 +1640,82 @@
         p.setElegantTextHeight(false);
         assertFalse(p.isElegantTextHeight());
     }
+
+    @Test
+    public void testEqualsForTextMeasurment() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_textSize() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        p1.setTextSize(p2.getTextSize() * 2.0f);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setTextSize(p2.getTextSize());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_textSkew() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        p1.setTextSkewX(p2.getTextSkewX() + 2.0f);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setTextSkewX(p2.getTextSkewX());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_textScale() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        p1.setTextScaleX(p2.getTextScaleX() * 2.0f);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setTextScaleX(p2.getTextScaleX());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_letterSpacing() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        p1.setLetterSpacing(p2.getLetterSpacing() + 2.0f);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setLetterSpacing(p2.getLetterSpacing());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_localeList() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        LocaleList enUS = LocaleList.forLanguageTags("en-US");
+        LocaleList jaJP = LocaleList.forLanguageTags("ja-JP");
+        LocaleList differentLocale = p2.getTextLocales().equals(enUS) ? jaJP : enUS;
+        p1.setTextLocales(differentLocale);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setTextLocales(p2.getTextLocales());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
+
+    @Test
+    public void testEqualsForTextMeasurment_typeface() {
+        Paint p1 = new Paint();
+        Paint p2 = new Paint();
+
+        p1.setTypeface(Typeface.DEFAULT);
+        p2.setTypeface(Typeface.SERIF);
+        assertFalse(p1.equalsForTextMeasurement(p2));
+        p1.setTypeface(p2.getTypeface());
+        assertTrue(p1.equalsForTextMeasurement(p2));
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
index 017ccb7..6b41b4b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
@@ -150,6 +150,13 @@
         verifyBitmap(bitmap);
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testBeginRecordingTwice() {
+        Picture picture = new Picture();
+        picture.beginRecording(10, 10);
+        picture.beginRecording(10, 10);
+    }
+
     private void verifySize(Picture picture) {
         assertEquals(TEST_WIDTH, picture.getWidth());
         assertEquals(TEST_HEIGHT, picture.getHeight());
diff --git a/tests/tests/graphics/src/android/graphics/cts/PointFTest.java b/tests/tests/graphics/src/android/graphics/cts/PointFTest.java
index eedcb6f..5d2ec5b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PointFTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PointFTest.java
@@ -111,5 +111,7 @@
         mPointF.readFromParcel(p);
         assertEquals(10.0f, mPointF.x, 0.0f);
         assertEquals(20.0f, mPointF.y, 0.0f);
+
+        p.recycle();
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/RectFTest.java b/tests/tests/graphics/src/android/graphics/cts/RectFTest.java
index 2759d1e..10ca0a6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RectFTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RectFTest.java
@@ -504,5 +504,7 @@
         assertNotNull(rectIn.toString());
 
         assertEquals(0, rectIn.describeContents());
+
+        out.recycle();
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/RectTest.java b/tests/tests/graphics/src/android/graphics/cts/RectTest.java
index cb12cb5..d968af2 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RectTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RectTest.java
@@ -487,6 +487,8 @@
         assertEquals(2, mRect.top);
         assertEquals(3, mRect.right);
         assertEquals(4, mRect.bottom);
+
+        p.recycle();
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/RegionTest.java b/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
index 0eff6cf..f7084cb 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
@@ -1466,6 +1466,7 @@
         p.setDataPosition(0);
         Region dst = Region.CREATOR.createFromParcel(p);
         assertTrue(dst.isEmpty());
+        p.recycle();
 
         // test reading/writing a single rect parcel
         p = Parcel.obtain();
@@ -1478,6 +1479,7 @@
         assertEquals(oriRect.left, dst.getBounds().left);
         assertEquals(oriRect.bottom, dst.getBounds().bottom);
         assertEquals(oriRect.right, dst.getBounds().right);
+        p.recycle();
 
         // test reading/writing a multiple rect parcel
         p = Parcel.obtain();
@@ -1495,6 +1497,8 @@
         assertEquals(0, dst.getBounds().left);
         assertEquals(15, dst.getBounds().bottom);
         assertEquals(15, dst.getBounds().right);
+
+        p.recycle();
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 04d5564..c1d7fdf 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -43,6 +43,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Locale;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -52,6 +53,25 @@
     private static final String DEFAULT = (String)null;
     private static final String INVALID = "invalid-family-name";
 
+    private static final float GLYPH_1EM_WIDTH;
+    private static final float GLYPH_3EM_WIDTH;
+
+    private static float measureText(String text, Typeface typeface) {
+        final Paint paint = new Paint();
+        // Fix the locale so that fix the locale based fallback.
+        paint.setTextLocale(Locale.US);
+        paint.setTypeface(typeface);
+        return paint.measureText(text);
+    }
+
+    static {
+        // 3em.ttf supports "a", "b", "c". The width of "a" is 3em, others are 1em.
+        final Context ctx = InstrumentationRegistry.getTargetContext();
+        final Typeface typeface = ctx.getResources().getFont(R.font.a3em);
+        GLYPH_3EM_WIDTH = measureText("a", typeface);
+        GLYPH_1EM_WIDTH = measureText("b", typeface);
+    }
+
     // list of family names to try when attempting to find a typeface with a given style
     private static final String[] FAMILIES =
             { (String) null, "monospace", "serif", "sans-serif", "cursive", "arial", "times" };
@@ -231,11 +251,9 @@
     public void testInvalidCmapFont() {
         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont.ttf");
         assertNotNull(typeface);
-        Paint p = new Paint();
         final String testString = "abcde";
-        float widthDefaultTypeface = p.measureText(testString);
-        p.setTypeface(typeface);
-        float widthCustomTypeface = p.measureText(testString);
+        float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
+        float widthCustomTypeface = measureText(testString, typeface);
         assertEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
     }
 
@@ -243,11 +261,9 @@
     public void testInvalidCmapFont2() {
         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont2.ttf");
         assertNotNull(typeface);
-        Paint p = new Paint();
         final String testString = "abcde";
-        float widthDefaultTypeface = p.measureText(testString);
-        p.setTypeface(typeface);
-        float widthCustomTypeface = p.measureText(testString);
+        float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
+        float widthCustomTypeface = measureText(testString, typeface);
         assertEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
     }
 
@@ -275,11 +291,9 @@
         for (final String file : INVALID_CMAP_FONTS) {
             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
             assertNotNull(typeface);
-            final Paint p = new Paint();
             final String testString = "\u0100\u0400";
-            final float widthDefaultTypeface = p.measureText(testString);
-            p.setTypeface(typeface);
-            final float widthCustomTypeface = p.measureText(testString);
+            final float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
+            final float widthCustomTypeface = measureText(testString, typeface);
             assertEquals(widthDefaultTypeface, widthCustomTypeface, 0.0f);
         }
 
@@ -292,30 +306,28 @@
         for (final String file : INVALID_CMAP_VS_FONTS) {
             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
             assertNotNull(typeface);
-            final Paint p = new Paint();
             final String testString = "\u0100\uFE00\u0400\uFE00";
-            final float widthDefaultTypeface = p.measureText(testString);
-            p.setTypeface(typeface);
-            final float widthCustomTypeface = p.measureText(testString);
+            final float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
+            final float widthCustomTypeface = measureText(testString, typeface);
             assertEquals(widthDefaultTypeface, widthCustomTypeface, 0.0f);
         }
     }
 
     @Test
     public void testCreateFromAsset_cachesTypeface() {
-        Typeface typeface1 = Typeface.createFromAsset(mContext.getAssets(), "bombfont2.ttf");
+        Typeface typeface1 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
         assertNotNull(typeface1);
 
-        Typeface typeface2 = Typeface.createFromAsset(mContext.getAssets(), "bombfont2.ttf");
+        Typeface typeface2 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
         assertNotNull(typeface2);
         assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
 
-        Typeface typeface3 = Typeface.createFromAsset(mContext.getAssets(), "bombfont.ttf");
+        Typeface typeface3 = Typeface.createFromAsset(mContext.getAssets(), "samplefont2.ttf");
         assertNotNull(typeface3);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface3);
 
-        Typeface typeface4 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
+        Typeface typeface4 = Typeface.createFromAsset(mContext.getAssets(), "samplefont3.ttf");
         assertNotNull(typeface4);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface4);
@@ -498,11 +510,9 @@
         for (String fontPath : fontPaths) {
             Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), fontPath);
             assertNotNull(typeface);
-            Paint p = new Paint();
             final String testString = "a";
-            float widthDefaultTypeface = p.measureText(testString);
-            p.setTypeface(typeface);
-            float widthCustomTypeface = p.measureText(testString);
+            float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
+            float widthCustomTypeface = measureText(testString, typeface);
             // The width of the glyph "a" from above fonts are 2em.
             // So the width should be different from the default one.
             assertNotEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
@@ -521,6 +531,7 @@
         final String testString = "WWWWWWWWWWWWWWWWWWWWW";
 
         final Paint p = new Paint();
+        p.setTextLocale(Locale.US);
         p.setTextSize(128);
 
         p.setTypeface(regularTypeface);
@@ -529,6 +540,144 @@
         p.setTypeface(blackTypeface);
         final float widthFromBlack = p.measureText(testString);
 
-        assertTrue(Math.abs(widthFromRegular - widthFromBlack) > 1.0f);
+        assertNotEquals(widthFromRegular, widthFromBlack, 1.0f);
+    }
+
+    @Test
+    public void testTypefaceCreate_withExactWeight() {
+        // multiweight_family has following fonts.
+        // - a3em.ttf with weight = 100, style=normal configuration.
+        //   This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
+        // - b3em.ttf with weight = 400, style=normal configuration.
+        //   This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
+        // - c3em.ttf with weight = 700, style=normal configuration.
+        //   This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
+        final Typeface family = mContext.getResources().getFont(R.font.multiweight_family);
+        assertNotNull(family);
+
+        // By default, the font which weight is 400 is selected.
+        assertEquals(GLYPH_1EM_WIDTH, measureText("a", family), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, measureText("b", family), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("c", family), 0f);
+
+        // Draw with the font which weight is 100.
+        final Typeface thinFamily = Typeface.create(family, 100 /* weight */, false /* italic */);
+        assertEquals(GLYPH_3EM_WIDTH, measureText("a", thinFamily), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("b", thinFamily), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("c", thinFamily), 0f);
+
+        // Draw with the font which weight is 700.
+        final Typeface boldFamily = Typeface.create(family, 700 /* weight */, false /* italic */);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("a", boldFamily), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("b", boldFamily), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, measureText("c", boldFamily), 0f);
+    }
+
+    @Test
+    public void testTypefaceCreate_withExactStyle() {
+        // multiweight_family has following fonts.
+        // - a3em.ttf with weight = 400, style=normal configuration.
+        //   This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
+        // - b3em.ttf with weight = 400, style=italic configuration.
+        //   This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
+        // - c3em.ttf with weight = 700, style=italic configuration.
+        //   This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
+        final Typeface family = mContext.getResources().getFont(R.font.multistyle_family);
+        assertNotNull(family);
+
+        // By default, the normal style font which weight is 400 is selected.
+        assertEquals(GLYPH_3EM_WIDTH, measureText("a", family), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("b", family), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("c", family), 0f);
+
+        // Draw with the italic font.
+        final Typeface italicFamily = Typeface.create(family, 400 /* weight */, true /* italic */);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("a", italicFamily), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, measureText("b", italicFamily), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("c", italicFamily), 0f);
+
+        // Draw with the italic font which weigth is 700.
+        final Typeface boldItalicFamily =
+                Typeface.create(family, 700 /* weight */, true /* italic */);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("a", boldItalicFamily), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, measureText("b", boldItalicFamily), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, measureText("c", boldItalicFamily), 0f);
+    }
+
+    @Test
+    public void testFontVariationSettings() {
+        // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
+        // width of the given wght axis. For example, if 300 is given as the wght value to the font,
+        // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
+        // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
+        // No other axes are supported.
+
+        final AssetManager am = mContext.getAssets();
+        final Paint paint = new Paint();
+        paint.setTextSize(100);  // Make 1em = 100px
+
+        // By default, WeightEqualsEmVariableFont has 0 'wght' value.
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf").build());
+        assertEquals(0.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 100").build());
+        assertEquals(10.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 300").build());
+        assertEquals(30.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 800").build());
+        assertEquals(80.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 550").build());
+        assertEquals(55.0f, paint.measureText("a"), 0.0f);
+    }
+
+    @Test
+    public void testFontVariationSettings_UnsupportedAxes() {
+        // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
+        // width of the given wght axis. For example, if 300 is given as the wght value to the font,
+        // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
+        // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
+        // No other axes are supported.
+
+        final AssetManager am = mContext.getAssets();
+        final Paint paint = new Paint();
+        paint.setTextSize(100);  // Make 1em = 100px
+
+        // Unsupported axes do not affect the result.
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 300, 'wdth' 10").build());
+        assertEquals(30.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wdth' 10, 'wght' 300").build());
+        assertEquals(30.0f, paint.measureText("a"), 0.0f);
+    }
+
+    @Test
+    public void testFontVariationSettings_OutOfRangeValue() {
+        // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
+        // width of the given wght axis. For example, if 300 is given as the wght value to the font,
+        // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
+        // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
+        // No other axes are supported.
+
+        final AssetManager am = mContext.getAssets();
+        final Paint paint = new Paint();
+        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")
+                .setFontVariationSettings("'wght' -100").build());
+        assertEquals(0.0f, paint.measureText("a"), 0.0f);
+
+        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+                .setFontVariationSettings("'wght' 1300").build());
+        assertEquals(100.0f, paint.measureText("a"), 0.0f);
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index e012d0e..2dbebd7 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -55,12 +55,20 @@
     // Require patch version 3 for Vulkan 1.0: It was the first publicly available version,
     // and there was an important bugfix relative to 1.0.2.
     private static final int VULKAN_1_0 = 0x00400003; // 1.0.3
+    private static final int VULKAN_1_1 = 0x00401000; // 1.1.0
+
+    private static final String VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME =
+            "VK_ANDROID_external_memory_android_hardware_buffer";
+    private static final int VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION = 2;
+    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 PackageManager mPm;
     private FeatureInfo mVulkanHardwareLevel = null;
     private FeatureInfo mVulkanHardwareVersion = null;
     private FeatureInfo mVulkanHardwareCompute = null;
     private JSONObject mVulkanDevices[];
+    private JSONObject mBestDevice = null;
 
     @Before
     public void setup() throws Throwable {
@@ -88,6 +96,7 @@
         }
 
         mVulkanDevices = getVulkanDevices();
+        mBestDevice = getBestDevice();
     }
 
     @Test
@@ -132,27 +141,9 @@
                        mVulkanHardwareCompute.version == 0);
         }
 
-        JSONObject bestDevice = null;
-        int bestDeviceLevel = -1;
-        int bestComputeLevel = -1;
-        int bestDeviceVersion = -1;
-        for (JSONObject device : mVulkanDevices) {
-            int level = determineHardwareLevel(device);
-            int compute = determineHardwareCompute(device);
-            int version = determineHardwareVersion(device);
-            if (DEBUG) {
-                Log.d(TAG, device.getJSONObject("properties").getString("deviceName") +
-                    ": level=" + level + " compute=" + compute +
-                    " version=0x" + Integer.toHexString(version));
-            }
-            if (level >= bestDeviceLevel && compute >= bestComputeLevel &&
-                    version >= bestDeviceVersion) {
-                bestDevice = device;
-                bestDeviceLevel = level;
-                bestComputeLevel = compute;
-                bestDeviceVersion = version;
-            }
-        }
+        int bestDeviceLevel = determineHardwareLevel(mBestDevice);
+        int bestComputeLevel = determineHardwareCompute(mBestDevice);
+        int bestDeviceVersion = determineHardwareVersion(mBestDevice);
 
         assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL +
             " version " + mVulkanHardwareLevel.version + " doesn't match best physical device " +
@@ -177,6 +168,30 @@
     }
 
     @Test
+    public void testVulkan1_1Requirements() throws JSONException {
+        if (mVulkanHardwareVersion == null || mVulkanHardwareVersion.version < VULKAN_1_1)
+            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,
+                    "externalSemaphoreFeatures", 0x3 /* importable + exportable */));
+        assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external fences",
+                hasHandleType(mBestDevice.getJSONArray("externalFenceProperties"),
+                    VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT,
+                    "externalFenceFeatures", 0x3 /* importable + exportable */));
+        assertTrue("Devices with Vulkan 1.1 must support sampler YCbCr conversion",
+                mBestDevice.getJSONObject("samplerYcbcrConversionFeatures")
+                           .getInt("samplerYcbcrConversion") != 0);
+    }
+
+    @Test
     public void testVulkanVersionForVrHighPerformance() {
         if (!mPm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE))
             return;
@@ -187,6 +202,31 @@
             mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0);
     }
 
+    private JSONObject getBestDevice() throws JSONException {
+        JSONObject bestDevice = null;
+        int bestDeviceLevel = -1;
+        int bestComputeLevel = -1;
+        int bestDeviceVersion = -1;
+        for (JSONObject device : mVulkanDevices) {
+            int level = determineHardwareLevel(device);
+            int compute = determineHardwareCompute(device);
+            int version = determineHardwareVersion(device);
+            if (DEBUG) {
+                Log.d(TAG, device.getJSONObject("properties").getString("deviceName") +
+                    ": level=" + level + " compute=" + compute +
+                    " version=0x" + Integer.toHexString(version));
+            }
+            if (level >= bestDeviceLevel && compute >= bestComputeLevel &&
+                    version >= bestDeviceVersion) {
+                bestDevice = device;
+                bestDeviceLevel = level;
+                bestComputeLevel = compute;
+                bestDeviceVersion = version;
+            }
+        }
+        return bestDevice;
+    }
+
     private int determineHardwareLevel(JSONObject device) throws JSONException {
         JSONObject features = device.getJSONObject("features");
         boolean textureCompressionETC2 = features.getInt("textureCompressionETC2") != 0;
@@ -221,8 +261,9 @@
     }
 
     private int determineHardwareCompute(JSONObject device) throws JSONException {
-        JSONObject variablePointersFeatures = device.getJSONObject("variablePointersFeaturesKHR");
-        boolean variablePointers = variablePointersFeatures.getInt("variablePointers") != 0;
+        boolean variablePointers = device.getJSONObject("VK_KHR_variable_pointers")
+                                         .getJSONObject("variablePointerFeaturesKHR")
+                                         .getInt("variablePointers") != 0;
         JSONObject limits = device.getJSONObject("properties").getJSONObject("limits");
         int maxPerStageDescriptorStorageBuffers = limits.getInt("maxPerStageDescriptorStorageBuffers");
         if (DEBUG) {
@@ -261,6 +302,7 @@
         // patch versions.
         final int[] ALLOWED_HARDWARE_VERSIONS = {
             VULKAN_1_0,
+            VULKAN_1_1,
         };
         for (int expected : ALLOWED_HARDWARE_VERSIONS) {
             if (actual == expected) {
@@ -270,10 +312,35 @@
         return false;
     }
 
+    private boolean hasExtension(JSONObject device, String name, int minVersion)
+            throws JSONException {
+        JSONArray extensions = device.getJSONArray("extensions");
+        for (int i = 0; i < extensions.length(); i++) {
+            JSONObject ext = extensions.getJSONObject(i);
+            if (ext.getString("extensionName").equals(name) &&
+                    ext.getInt("specVersion") >= minVersion)
+                return true;
+        }
+        return false;
+    }
+
+    private boolean hasHandleType(JSONArray handleTypes, int type,
+            String featuresName, int requiredFeatures) throws JSONException {
+        for (int i = 0; i < handleTypes.length(); i++) {
+            JSONArray typeRecord = handleTypes.getJSONArray(i);
+            if (typeRecord.getInt(0) == type) {
+                JSONObject typeInfo = typeRecord.getJSONObject(1);
+                if ((typeInfo.getInt(featuresName) & requiredFeatures) == requiredFeatures)
+                    return true;
+            }
+        }
+        return false;
+    }
+
     private static native String nativeGetVkJSON();
 
     private JSONObject[] getVulkanDevices() throws JSONException, UnsupportedEncodingException {
-        JSONArray vkjson = new JSONArray(nativeGetVkJSON());
+        JSONArray vkjson = (new JSONObject(nativeGetVkJSON())).getJSONArray("devices");
         JSONObject[] devices = new JSONObject[vkjson.length()];
         for (int i = 0; i < vkjson.length(); i++) {
             devices[i] = vkjson.getJSONObject(i);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/Animatable2Callback.java b/tests/tests/graphics/src/android/graphics/drawable/cts/Animatable2Callback.java
new file mode 100644
index 0000000..5659ac2
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/Animatable2Callback.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.Drawable;
+
+// Now this class can not only listen to the events, but also synchronize the key events,
+// logging the event timestamp, and centralize some assertions.
+public class Animatable2Callback extends Animatable2.AnimationCallback {
+    private static final long MAX_START_TIMEOUT_MS = 5000;
+
+    private boolean mStarted = false;
+    private boolean mEnded = false;
+
+    private long mStartNs = Long.MAX_VALUE;
+    private long mEndNs = Long.MIN_VALUE;
+
+    // Use this lock to make sure the onAnimationEnd() has been called.
+    // Each sub test should have its own lock.
+    private final Object mEndLock = new Object();
+
+    // Use this lock to make sure the test thread know when the AVD.start() has been called.
+    // Each sub test should have its own lock.
+    private final Object mStartLock = new Object();
+
+    public boolean waitForEnd(long timeoutMs) throws InterruptedException {
+        synchronized (mEndLock) {
+            if (!mEnded) {
+                // Return immediately if the AVD has already ended.
+                mEndLock.wait(timeoutMs);
+            }
+            return mEnded;
+        }
+    }
+
+    public boolean waitForStart() throws InterruptedException {
+        synchronized(mStartLock) {
+            if (!mStarted) {
+                // Return immediately if the AVD has already started.
+                mStartLock.wait(MAX_START_TIMEOUT_MS);
+            }
+            return mStarted;
+        }
+    }
+
+    @Override
+    public void onAnimationStart(Drawable drawable) {
+        mStartNs = System.nanoTime();
+        synchronized(mStartLock) {
+            mStarted = true;
+            mStartLock.notify();
+        }
+    }
+
+    @Override
+    public void onAnimationEnd(Drawable drawable) {
+        mEndNs = System.nanoTime();
+        synchronized (mEndLock) {
+            mEnded = true;
+            mEndLock.notify();
+        }
+    }
+
+    public boolean endIsCalled() {
+        synchronized (mEndLock) {
+            return mEnded;
+        }
+    }
+
+    public void assertStarted(boolean started) {
+        assertEquals(started, mStarted);
+    }
+
+    public void assertEnded(boolean ended) {
+        assertEquals(ended, mEnded);
+    }
+
+    public void assertAVDRuntime(long min, long max) {
+        assertTrue(mStartNs != Long.MAX_VALUE);
+        assertTrue(mEndNs != Long.MIN_VALUE);
+        long durationNs = mEndNs - mStartNs;
+        assertTrue("current duration " + durationNs + " should be within " +
+                   min + "," + max, durationNs <= max && durationNs >= min);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
new file mode 100644
index 0000000..537b774
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
+import android.graphics.LightingColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.cts.R;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.ImageView;
+
+import com.android.compatibility.common.util.BitmapUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.function.BiFunction;
+
+@RunWith(AndroidJUnit4.class)
+public class AnimatedImageDrawableTest {
+    private Resources mRes;
+    private ContentResolver mContentResolver;
+
+    private static final int RES_ID = R.drawable.animated;
+    private static final int WIDTH = 278;
+    private static final int HEIGHT = 183;
+    private static final int NUM_FRAMES = 4;
+    private static final int FRAME_DURATION = 250; // in milliseconds
+    private static final int DURATION = NUM_FRAMES * FRAME_DURATION;
+    private static final int LAYOUT = R.layout.animated_image_layout;
+    private static final int IMAGE_ID = R.id.animated_image;
+    @Rule
+    public ActivityTestRule<DrawableStubActivity> mActivityRule =
+            new ActivityTestRule<DrawableStubActivity>(DrawableStubActivity.class);
+    private Activity mActivity;
+
+    private Uri getAsResourceUri(int resId) {
+        return new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+            .authority(mRes.getResourcePackageName(resId))
+            .appendPath(mRes.getResourceTypeName(resId))
+            .appendPath(mRes.getResourceEntryName(resId))
+            .build();
+    }
+
+    @Before
+    public void setup() {
+        mRes = InstrumentationRegistry.getTargetContext().getResources();
+        mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testEmptyConstructor() {
+        new AnimatedImageDrawable();
+    }
+
+    private AnimatedImageDrawable createFromImageDecoder(int resId) {
+        Uri uri = null;
+        try {
+            uri = getAsResourceUri(resId);
+            ImageDecoder.Source source = ImageDecoder.createSource(mContentResolver, uri);
+            Drawable drawable = ImageDecoder.decodeDrawable(source);
+            assertTrue(drawable instanceof AnimatedImageDrawable);
+            return (AnimatedImageDrawable) drawable;
+        } catch (IOException e) {
+            fail("failed to create image from " + uri);
+            return null;
+        }
+    }
+
+    @Test
+    public void testDecodeAnimatedImageDrawable() {
+        Drawable drawable = createFromImageDecoder(RES_ID);
+        assertEquals(WIDTH,  drawable.getIntrinsicWidth());
+        assertEquals(HEIGHT, drawable.getIntrinsicHeight());
+    }
+
+    private static class Callback extends Animatable2Callback {
+        private final Drawable mDrawable;
+
+        public Callback(Drawable d) {
+            mDrawable = d;
+        }
+
+        @Override
+        public void onAnimationStart(Drawable drawable) {
+            assertNotNull(drawable);
+            assertEquals(mDrawable, drawable);
+            super.onAnimationStart(drawable);
+        }
+
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            assertNotNull(drawable);
+            assertEquals(mDrawable, drawable);
+            super.onAnimationEnd(drawable);
+        }
+    };
+
+    @Test(expected=IllegalStateException.class)
+    public void testRegisterWithoutLooper() {
+        AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
+
+        // registerAnimationCallback must be run on a thread with a Looper,
+        // which the test thread does not have.
+        Callback cb = new Callback(drawable);
+        drawable.registerAnimationCallback(cb);
+    }
+
+    @Test
+    public void testRegisterCallback() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
+
+        mActivityRule.runOnUiThread(() -> {
+            // Register a callback.
+            Callback cb = new Callback(drawable);
+            drawable.registerAnimationCallback(cb);
+            assertTrue(drawable.unregisterAnimationCallback(cb));
+
+            // Now that it has been removed, it cannot be removed again.
+            assertFalse(drawable.unregisterAnimationCallback(cb));
+        });
+    }
+
+    @Test
+    public void testClearCallbacks() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
+
+        Callback[] callbacks = new Callback[] {
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+            new Callback(drawable),
+        };
+
+        mActivityRule.runOnUiThread(() -> {
+            for (Callback cb : callbacks) {
+                drawable.registerAnimationCallback(cb);
+            }
+        });
+
+        drawable.clearAnimationCallbacks();
+
+        for (Callback cb : callbacks) {
+            // It has already been removed.
+            assertFalse(drawable.unregisterAnimationCallback(cb));
+        }
+    }
+
+    /**
+     *  Helper for attaching drawable to the view system.
+     *
+     *  Necessary for the drawable to animate.
+     *
+     *  Must be called from UI thread.
+     */
+    private void setContentView(AnimatedImageDrawable drawable) {
+        mActivity.setContentView(LAYOUT);
+        ImageView imageView = (ImageView) mActivity.findViewById(IMAGE_ID);
+        imageView.setImageDrawable(drawable);
+    }
+
+    @Test
+    public void testUnregisterCallback() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
+
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+
+            drawable.registerAnimationCallback(cb);
+            assertTrue(drawable.unregisterAnimationCallback(cb));
+            drawable.setLoopCount(0);
+            drawable.start();
+        });
+
+        cb.waitForStart();
+        cb.assertStarted(false);
+
+        cb.waitForEnd(DURATION * 2);
+        cb.assertEnded(false);
+    }
+
+    @Test
+    public void testLifeCycle() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+
+            drawable.registerAnimationCallback(cb);
+        });
+
+        assertFalse(drawable.isRunning());
+        cb.assertStarted(false);
+        cb.assertEnded(false);
+
+        mActivityRule.runOnUiThread(() -> {
+            drawable.start();
+            assertTrue(drawable.isRunning());
+        });
+        cb.waitForStart();
+        cb.assertStarted(true);
+
+        // Only run the animation one time.
+        drawable.setLoopCount(0);
+
+        // Extra time, to wait for the message to post.
+        cb.waitForEnd(DURATION * 2);
+        cb.assertEnded(true);
+        assertFalse(drawable.isRunning());
+    }
+
+    @Test
+    public void testLifeCycleSoftware() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+
+        Bitmap bm = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bm);
+
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            drawable.registerAnimationCallback(cb);
+            drawable.draw(canvas);
+        });
+
+        assertFalse(drawable.isRunning());
+        cb.assertStarted(false);
+        cb.assertEnded(false);
+
+        mActivityRule.runOnUiThread(() -> {
+            drawable.start();
+            assertTrue(drawable.isRunning());
+            drawable.draw(canvas);
+        });
+        cb.waitForStart();
+        cb.assertStarted(true);
+
+        // Only run the animation one time.
+        drawable.setLoopCount(0);
+
+        // The drawable will prevent skipping frames, so we actually have to
+        // draw each frame. (Start with 1, since we already drew frame 0.)
+        for (int i = 1; i < NUM_FRAMES; i++) {
+            cb.waitForEnd(FRAME_DURATION);
+            cb.assertEnded(false);
+            mActivityRule.runOnUiThread(() -> {
+                assertTrue(drawable.isRunning());
+                drawable.draw(canvas);
+            });
+        }
+
+        cb.waitForEnd(FRAME_DURATION);
+        assertFalse(drawable.isRunning());
+        cb.assertEnded(true);
+    }
+
+    @Test
+    public void testAddCallbackAfterStart() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+
+            drawable.setLoopCount(0);
+            drawable.start();
+            drawable.registerAnimationCallback(cb);
+        });
+
+        cb.waitForEnd(DURATION * 2);
+        cb.assertEnded(true);
+    }
+
+    @Test
+    public void testStop() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+
+            drawable.registerAnimationCallback(cb);
+
+            drawable.start();
+            assertTrue(drawable.isRunning());
+
+            drawable.stop();
+            assertFalse(drawable.isRunning());
+        });
+
+        // Duration may be overkill, but we need to wait for the message
+        // to post.
+        cb.waitForEnd(DURATION);
+        cb.assertStarted(true);
+        cb.assertEnded(true);
+    }
+
+    @Test
+    public void testLoopCounts() throws Throwable {
+        for (int loopCount : new int[] { 3, 5, 7, 16 }) {
+            AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+            assertEquals(AnimatedImageDrawable.LOOP_INFINITE, drawable.getLoopCount());
+
+            Callback cb = new Callback(drawable);
+            mActivityRule.runOnUiThread(() -> {
+                setContentView(drawable);
+
+                drawable.registerAnimationCallback(cb);
+                drawable.setLoopCount(loopCount);
+                assertEquals(loopCount, drawable.getLoopCount());
+                drawable.start();
+            });
+
+            // The animation runs loopCount + 1 total times.
+            cb.waitForEnd(DURATION * loopCount);
+            cb.assertEnded(false);
+
+            cb.waitForEnd(DURATION * 2);
+            cb.assertEnded(true);
+
+            drawable.setLoopCount(AnimatedImageDrawable.LOOP_INFINITE);
+            assertEquals(AnimatedImageDrawable.LOOP_INFINITE, drawable.getLoopCount());
+        }
+    }
+
+    @Test
+    public void testLoopCountInfinite() throws Throwable {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+
+            drawable.registerAnimationCallback(cb);
+            drawable.setLoopCount(AnimatedImageDrawable.LOOP_INFINITE);
+            drawable.start();
+        });
+
+        // There is no way to truly test infinite, but let it run for a long
+        // time and verify that it's still running.
+        cb.waitForEnd(DURATION * 30);
+        cb.assertEnded(false);
+        assertTrue(drawable.isRunning());
+    }
+
+    @Test
+    public void testGetOpacity() {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        assertEquals(PixelFormat.TRANSLUCENT, drawable.getOpacity());
+    }
+
+    @Test
+    public void testColorFilter() {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+
+        ColorFilter filter = new LightingColorFilter(0, Color.RED);
+        drawable.setColorFilter(filter);
+        assertEquals(filter, drawable.getColorFilter());
+
+        Bitmap actual = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        {
+            Canvas canvas = new Canvas(actual);
+            drawable.draw(canvas);
+        }
+
+        for (int i = 0; i < actual.getWidth(); ++i) {
+            for (int j = 0; j < actual.getHeight(); ++j) {
+                int color = actual.getPixel(i, j);
+                // The LightingColorFilter does not affect the transparent pixels,
+                // so all pixels should either remain transparent or turn red.
+                if (color != Color.RED && color != Color.TRANSPARENT) {
+                    fail("pixel at " + i + ", " + j + " does not match expected. "
+                            + "expected: " + Color.RED + " OR " + Color.TRANSPARENT
+                            + " actual: " + color);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPostProcess() {
+        // Compare post processing a Rect in the middle of the (not-animating)
+        // image with drawing manually. They should be exactly the same.
+        BiFunction<Integer, Integer, Rect> rectCreator = (width, height) -> {
+            int quarterWidth  = width  / 4;
+            int quarterHeight = height / 4;
+            return new Rect(quarterWidth, quarterHeight,
+                    3 * quarterWidth, 3 * quarterHeight);
+        };
+
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        Bitmap expected = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+        Paint paint = new Paint();
+        paint.setColor(Color.RED);
+
+        {
+            Rect r = rectCreator.apply(drawable.getIntrinsicWidth(),
+                                       drawable.getIntrinsicHeight());
+            Canvas canvas = new Canvas(expected);
+            drawable.draw(canvas);
+
+            for (int i = r.left; i < r.right; ++i) {
+                for (int j = r.top; j < r.bottom; ++j) {
+                    assertNotEquals(Color.RED, expected.getPixel(i, j));
+                }
+            }
+
+            canvas.drawRect(r, paint);
+
+            for (int i = r.left; i < r.right; ++i) {
+                for (int j = r.top; j < r.bottom; ++j) {
+                    assertEquals(Color.RED, expected.getPixel(i, j));
+                }
+            }
+        }
+
+
+        AnimatedImageDrawable testDrawable = null;
+        Uri uri = null;
+        try {
+            uri = getAsResourceUri(RES_ID);
+            ImageDecoder.Source source = ImageDecoder.createSource(mContentResolver, uri);
+            Drawable dr = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+                decoder.setPostProcessor((canvas) -> {
+                    canvas.drawRect(rectCreator.apply(canvas.getWidth(),
+                                                      canvas.getHeight()), paint);
+                    return PixelFormat.TRANSLUCENT;
+                });
+            });
+            assertTrue(dr instanceof AnimatedImageDrawable);
+            testDrawable = (AnimatedImageDrawable) dr;
+        } catch (IOException e) {
+            fail("failed to create image from " + uri);
+        }
+
+        Bitmap actual = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+        {
+            Canvas canvas = new Canvas(actual);
+            testDrawable.draw(canvas);
+        }
+
+        BitmapUtils.compareBitmaps(expected, actual);
+    }
+
+    @Test
+    public void testCreateFromXml() throws XmlPullParserException, IOException {
+        XmlPullParser parser = mRes.getXml(R.drawable.animatedimagedrawable_tag);
+        Drawable drawable = Drawable.createFromXml(mRes, parser);
+        assertNotNull(drawable);
+        assertTrue(drawable instanceof AnimatedImageDrawable);
+    }
+
+    @Test
+    public void testCreateFromXmlClass() throws XmlPullParserException, IOException {
+        XmlPullParser parser = mRes.getXml(R.drawable.animatedimagedrawable);
+        Drawable drawable = Drawable.createFromXml(mRes, parser);
+        assertNotNull(drawable);
+        assertTrue(drawable instanceof AnimatedImageDrawable);
+    }
+
+    @Test
+    public void testCreateFromXmlClassAttribute() throws XmlPullParserException, IOException {
+        XmlPullParser parser = mRes.getXml(R.drawable.animatedimagedrawable_class);
+        Drawable drawable = Drawable.createFromXml(mRes, parser);
+        assertNotNull(drawable);
+        assertTrue(drawable instanceof AnimatedImageDrawable);
+    }
+
+    @Test(expected=XmlPullParserException.class)
+    public void testMissingSrcInflate() throws XmlPullParserException, IOException  {
+        XmlPullParser parser = mRes.getXml(R.drawable.animatedimagedrawable_nosrc);
+        Drawable drawable = Drawable.createFromXml(mRes, parser);
+    }
+
+    @Test
+    public void testAutoMirrored() {
+        AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
+        assertFalse(drawable.isAutoMirrored());
+
+        drawable.setAutoMirrored(true);
+        assertTrue(drawable.isAutoMirrored());
+
+        drawable.setAutoMirrored(false);
+        assertFalse(drawable.isAutoMirrored());
+    }
+
+    @Test
+    public void testAutoMirroredFromXml() throws XmlPullParserException, IOException {
+        XmlPullParser parser = mRes.getXml(R.drawable.animatedimagedrawable_tag);
+        Drawable drawable = Drawable.createFromXml(mRes, parser);
+        assertNotNull(drawable);
+        assertTrue(drawable instanceof AnimatedImageDrawable);
+        assertFalse(drawable.isAutoMirrored());
+
+        parser = mRes.getXml(R.drawable.animatedimagedrawable_automirrored);
+        drawable = Drawable.createFromXml(mRes, parser);
+        assertNotNull(drawable);
+        assertTrue(drawable instanceof AnimatedImageDrawable);
+        assertTrue(drawable.isAutoMirrored());
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
index 2e986cb..8fad002 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
@@ -200,15 +200,17 @@
             Arrays.fill(origWidth, max);
         }
 
+        // NOTE: instrinsic W/H may already be a scaled and truncated value computed from the
+        // underlying asset (e.g. bitmap) so account for this previously applied scaling/truncation
+        // by applying a small delta to the asserts.
+
         // Set density to half of original.
         DrawableTestUtils.setResourcesDensity(res, densityDpi / 2);
         final StateListDrawable halfDrawable =
                 (StateListDrawable) cs.newDrawable(res);
-        // NOTE: densityDpi may not be an even number, so account for *actual* scaling in asserts
-        final float approxHalf = (float)(densityDpi / 2) / densityDpi;
         for (int i = 0; i < count; i++) {
             halfDrawable.selectDrawable(i);
-            assertEquals(Math.round(origWidth[i] * approxHalf), halfDrawable.getIntrinsicWidth());
+            assertEquals(Math.round(origWidth[i] * 0.5f), halfDrawable.getIntrinsicWidth(), 1);
         }
 
         // Set density to double original.
@@ -217,7 +219,7 @@
                 (StateListDrawable) cs.newDrawable(res);
         for (int i = 0; i < count; i++) {
             doubleDrawable.selectDrawable(i);
-            assertEquals(origWidth[i] * 2, doubleDrawable.getIntrinsicWidth());
+            assertEquals(origWidth[i] * 2, doubleDrawable.getIntrinsicWidth(), 1);
         }
 
         // Restore original configuration and metrics.
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 17edc3f..6ef16a3 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
@@ -118,8 +118,7 @@
 
     @Test
     public void testAnimationOnLayer() throws Throwable {
-        final AnimatedVectorDrawableTest.MyCallback callback
-                = new AnimatedVectorDrawableTest.MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // Can't simply use final here, b/c it needs to be initialized and referred later in UI
         // thread.
         final ImageView[] imageView = new ImageView[1];
@@ -231,8 +230,7 @@
     @Test
     public void testEmptyAnimatorSet() throws Throwable {
         int resId = R.drawable.avd_empty_animator;
-        final AnimatedVectorDrawableTest.MyCallback callback =
-                new AnimatedVectorDrawableTest.MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         final AnimatedVectorDrawable d1 =
                 (AnimatedVectorDrawable) mResources.getDrawable(resId);
         d1.registerAnimationCallback(callback);
@@ -284,8 +282,7 @@
 
     @Test
     public void testInfiniteAVD() throws Throwable {
-        final AnimatedVectorDrawableTest.MyCallback callback
-                = new AnimatedVectorDrawableTest.MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // Can't simply use final here, b/c it needs to be initialized and referred later in UI
         // thread.
         final ImageView[] imageView = new ImageView[1];
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 990a8fb..f399cf6 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -57,7 +57,6 @@
     private static final int IMAGE_WIDTH = 64;
     private static final int IMAGE_HEIGHT = 64;
     private static final long MAX_TIMEOUT_MS = 1000;
-    private static final long MAX_START_TIMEOUT_MS = 5000;
     private static final int MS_TO_NS = 1000000;
 
     @Rule
@@ -202,7 +201,7 @@
 
     @Test
     public void testReset() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
@@ -217,7 +216,7 @@
 
     @Test
     public void testStop() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
@@ -231,7 +230,7 @@
 
     @Test
     public void testAddCallbackBeforeStart() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
             mActivity.setContentView(mLayoutId);
@@ -248,7 +247,7 @@
 
     @Test
     public void testAddCallbackAfterTrigger() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
             mActivity.setContentView(mLayoutId);
@@ -269,7 +268,7 @@
 
     @Test
     public void testAddCallbackAfterStart() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
             mActivity.setContentView(mLayoutId);
@@ -289,7 +288,7 @@
 
     @Test
     public void testRemoveCallback() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
             mActivity.setContentView(mLayoutId);
@@ -308,7 +307,7 @@
 
     @Test
     public void testClearCallback() throws Throwable {
-        final MyCallback callback = new MyCallback();
+        final Animatable2Callback callback = new Animatable2Callback();
 
         // The AVD has a duration as 100ms.
         mActivityRule.runOnUiThread(() -> {
@@ -328,7 +327,7 @@
 
     // The time out is expected when the listener is removed successfully.
     // Such that we don't get the end event.
-    static void waitForAVDStop(MyCallback callback, long timeout) {
+    static void waitForAVDStop(Animatable2Callback callback, long timeout) {
         try {
             callback.waitForEnd(timeout);
         } catch (InterruptedException e) {
@@ -336,82 +335,4 @@
             fail("We should not see the AVD run this long time!");
         }
     }
-
-    // Now this class can not only listen to the events, but also synchronize the key events,
-    // logging the event timestamp, and centralize some assertions.
-    static class MyCallback extends Animatable2.AnimationCallback {
-        private boolean mStarted = false;
-        private boolean mEnded = false;
-
-        private long mStartNs = Long.MAX_VALUE;
-        private long mEndNs = Long.MIN_VALUE;
-
-        // Use this lock to make sure the onAnimationEnd() has been called.
-        // Each sub test should have its own lock.
-        private final Object mEndLock = new Object();
-
-        // Use this lock to make sure the test thread know when the AVD.start() has been called.
-        // Each sub test should have its own lock.
-        private final Object mStartLock = new Object();
-
-        public boolean waitForEnd(long timeoutMs) throws InterruptedException {
-            synchronized (mEndLock) {
-                if (!mEnded) {
-                    // Return immediately if the AVD has already ended.
-                    mEndLock.wait(timeoutMs);
-                }
-                return mEnded;
-            }
-        }
-
-        public boolean waitForStart() throws InterruptedException {
-            synchronized(mStartLock) {
-                if (!mStarted) {
-                    // Return immediately if the AVD has already started.
-                    mStartLock.wait(MAX_START_TIMEOUT_MS);
-                }
-                return mStarted;
-            }
-        }
-
-        @Override
-        public void onAnimationStart(Drawable drawable) {
-            mStartNs = System.nanoTime();
-            synchronized(mStartLock) {
-                mStarted = true;
-                mStartLock.notify();
-            }
-        }
-
-        @Override
-        public void onAnimationEnd(Drawable drawable) {
-            mEndNs = System.nanoTime();
-            synchronized (mEndLock) {
-                mEnded = true;
-                mEndLock.notify();
-            }
-        }
-
-        public boolean endIsCalled() {
-            synchronized (mEndLock) {
-                return mEnded;
-            }
-        }
-
-        public void assertStarted(boolean started) {
-            assertEquals(started, mStarted);
-        }
-
-        public void assertEnded(boolean ended) {
-            assertEquals(ended, mEnded);
-        }
-
-        public void assertAVDRuntime(long min, long max) {
-            assertTrue(mStartNs != Long.MAX_VALUE);
-            assertTrue(mEndNs != Long.MIN_VALUE);
-            long durationNs = mEndNs - mStartNs;
-            assertTrue("current duration " + durationNs + " should be within " +
-                    min + "," + max, durationNs <= max && durationNs >= min);
-        }
-    }
 }
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 eddcecc..536c16f 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
@@ -357,8 +357,8 @@
     @Test
     public void testGetIntrinsicSize() {
         BitmapDrawable bitmapDrawable = new BitmapDrawable();
-        assertEquals(0, bitmapDrawable.getIntrinsicWidth());
-        assertEquals(0, bitmapDrawable.getIntrinsicHeight());
+        assertEquals(-1, bitmapDrawable.getIntrinsicWidth());
+        assertEquals(-1, bitmapDrawable.getIntrinsicHeight());
 
         Bitmap bitmap = Bitmap.createBitmap(200, 300, Config.RGB_565);
         bitmapDrawable = new BitmapDrawable(bitmap);
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 13cc2e0..3c26f39 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java
@@ -213,6 +213,39 @@
         }
     }
 
+    private Boolean isClosed = new Boolean(false);
+
+    @Test
+    public void testCreateFromStream2() throws IOException {
+        FileInputStream inputStream = null;
+        File imageFile = null;
+        synchronized (isClosed) {
+            isClosed = false;
+            try {
+                imageFile = new File(mContext.getFilesDir(), "tempimage.jpg");
+                writeSampleImage(imageFile);
+
+                inputStream = new FileInputStream(imageFile) {
+                    @Override
+                    public void close() throws IOException {
+                        super.close();
+                        isClosed = true;
+                    }
+                };
+                assertNotNull(Drawable.createFromStream(inputStream, "Sample"));
+            } finally {
+                if (null != inputStream) {
+		    // verify that the stream was not closed
+                    assertFalse(isClosed);
+                    inputStream.close();
+                }
+                if (imageFile.exists()) {
+                    assertTrue(imageFile.delete());
+                }
+            }
+        }
+    }
+
     @Test
     public void testCreateFromResourceStream1() throws IOException {
         FileInputStream inputEmptyStream = null;
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
index e58b402..c248085 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
@@ -16,6 +16,7 @@
 
 package android.graphics.drawable.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -132,6 +133,52 @@
         verify(mockRunnable, times(1)).run();
     }
 
+    @Test
+    public void testBitmapIcon_getType() {
+        Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888));
+        assertEquals(Icon.TYPE_BITMAP, icon.getType());
+    }
+
+    @Test
+    public void testDataIcon_getType() {
+        byte[] data = new byte[4];
+        data[0] = data[1] = data[2] = data[3] = (byte) 255;
+        Icon icon = Icon.createWithData(data, 0, 4);
+        assertEquals(Icon.TYPE_DATA, icon.getType());
+    }
+
+    @Test
+    public void testFileIcon_getType() throws IOException {
+        File file = new File(mActivity.getFilesDir(), "testimage.jpg");
+        try {
+            writeSampleImage(file);
+            assertTrue(file.exists());
+            String filePath = file.toURI().getPath();
+
+            Icon icon = Icon.createWithFilePath(file.getPath());
+            assertEquals(Icon.TYPE_URI, icon.getType());
+            assertEquals(filePath, icon.getUri().getPath());
+
+            icon = Icon.createWithContentUri(Uri.fromFile(file));
+            assertEquals(Icon.TYPE_URI, icon.getType());
+            assertEquals(filePath, icon.getUri().getPath());
+
+            icon = Icon.createWithContentUri(file.toURI().toString());
+            assertEquals(Icon.TYPE_URI, icon.getType());
+            assertEquals(filePath, icon.getUri().getPath());
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testResourceIcon_getType() {
+        Icon icon = Icon.createWithResource("com.android.cts.testpkg", R.drawable.bmp_test);
+        assertEquals(Icon.TYPE_RESOURCE, icon.getType());
+        assertEquals("com.android.cts.testpkg", icon.getResPackage());
+        assertEquals(R.drawable.bmp_test, icon.getResId());
+    }
+
     private void writeSampleImage(File imagefile) throws IOException {
         try (InputStream source = mActivity.getResources().openRawResource(R.drawable.testimage);
              OutputStream target = new FileOutputStream(imagefile)) {
@@ -161,5 +208,7 @@
 
         // loading drawable synchronously.
         assertNotNull(icon.loadDrawable(mActivity));
+
+        parcel.recycle();
     }
 }
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 6499594..c96a1ca 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
@@ -225,25 +225,28 @@
             origWidth1 = tempWidth1;
         }
 
+        // NOTE: instrinsic W/H may already be a scaled and truncated value computed from the
+        // underlying asset (e.g. bitmap) so account for this previously applied scaling/truncation
+        // by applying a small delta to the asserts.
+
         // Set density to half of original.
         DrawableTestUtils.setResourcesDensity(res, densityDpi / 2);
         final StateListDrawable halfDrawable =
                 (StateListDrawable) preloadedConstantState.newDrawable(res);
-        // NOTE: densityDpi may not be an even number, so account for *actual* scaling in asserts
-        final float approxHalf = (float)(densityDpi / 2) / densityDpi;
         halfDrawable.selectDrawable(0);
-        assertEquals(Math.round(origWidth0 * approxHalf), halfDrawable.getIntrinsicWidth());
+        assertEquals(Math.round(origWidth0 * 0.5f), halfDrawable.getIntrinsicWidth(), 1);
         halfDrawable.selectDrawable(1);
-        assertEquals(Math.round(origWidth1 * approxHalf), halfDrawable.getIntrinsicWidth());
+        assertEquals(Math.round(origWidth1 * 0.5f), halfDrawable.getIntrinsicWidth(), 1);
 
         // Set density to double original.
+        // NOTE: densityDpi may not be an even number, so account for *actual* scaling in asserts
         DrawableTestUtils.setResourcesDensity(res, densityDpi * 2);
         final StateListDrawable doubleDrawable =
                 (StateListDrawable) preloadedConstantState.newDrawable(res);
         doubleDrawable.selectDrawable(0);
-        assertEquals(origWidth0 * 2, doubleDrawable.getIntrinsicWidth());
+        assertEquals(origWidth0 * 2, doubleDrawable.getIntrinsicWidth(), 1);
         doubleDrawable.selectDrawable(1);
-        assertEquals(origWidth1 * 2, doubleDrawable.getIntrinsicWidth());
+        assertEquals(origWidth1 * 2, doubleDrawable.getIntrinsicWidth(), 1);
 
         // Restore original configuration and metrics.
         DrawableTestUtils.setResourcesDensity(res, densityDpi);
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 4632c1c..4e8aaa7 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
@@ -34,8 +34,7 @@
     ctstestrunner \
     mockito-target-minus-junit4 \
     platform-test-annotations \
-    ub-uiautomator \
-    legacy-android-test
+    ub-uiautomator
 
 LOCAL_JNI_SHARED_LIBRARIES := libctshardware_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/hardware/AndroidTest.xml b/tests/tests/hardware/AndroidTest.xml
index 6dbda37..f1fa92d 100644
--- a/tests/tests/hardware/AndroidTest.xml
+++ b/tests/tests/hardware/AndroidTest.xml
@@ -13,6 +13,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Hardware test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java b/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
index 75d7b7c..8968442 100644
--- a/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
@@ -23,26 +23,79 @@
 import java.util.GregorianCalendar;
 
 public class GeomagneticFieldTest extends AndroidTestCase {
-    // Chengdu: Latitude 30d 40' 12", Longitude 104d 3' 36"
-    private static final float LATITUDE_OF_CHENGDU = 30.67f;
-    private static final float LONGITUDE_OF_CHENGDU = 104.06f;
-    private static final float ALTITUDE_OF_CHENGDU = 500f;
-    private static final long TEST_TIME = new GregorianCalendar(2010, 5, 1).getTimeInMillis();
+    private static final float DECLINATION_THRESHOLD = 0.1f;
+    private static final float INCLINATION_THRESHOLD = 0.1f;
+    private static final float FIELD_STRENGTH_THRESHOLD = 100;
 
     @Presubmit
     public void testGeomagneticField() {
-        GeomagneticField geomagneticField = new GeomagneticField(LATITUDE_OF_CHENGDU,
-                LONGITUDE_OF_CHENGDU, ALTITUDE_OF_CHENGDU, TEST_TIME);
+        // Reference values calculated from NOAA online calculator for WMM 2015
+        // https://www.ngdc.noaa.gov/geomag-web/#igrfwmm
+        TestDataPoint testPoints[] = new TestDataPoint[] {
+            // Mountain View, CA, USA on 2017/1/1
+            new TestDataPoint(37.386f, -122.083f, 32, 2017, 1, 1,
+                    13.4589f, 60.9542f, 48168.0f),
+            // Chengdu, China on 2017/8/8
+            new TestDataPoint(30.658f, 103.935f, 500f, 2017, 8, 8,
+                    -1.9784f, 47.9723f, 50717.3f),
+            // Sao Paulo, Brazil on 2018/12/25
+            new TestDataPoint(-23.682f, -46.875f, 760f, 2018, 12, 25,
+                    -21.3130f, -37.9940f, 22832.3f),
+            // Boston, MA, USA on 2019/2/10
+            new TestDataPoint(42.313f, -71.127f, 43f, 2019, 2, 10,
+                    -14.5391f, 66.9693f, 51815.1f),
+            // Cape Town, South Africa on 2019/5/1
+            new TestDataPoint(-33.913f, 18.095f, 100f, 2019, 5, 1,
+                    -25.2454f, -65.8887f, 25369.2f),
+            // Sydney, Australia on 2020/1/1
+            new TestDataPoint(-33.847f, 150.791f, 19f, 2020, 1, 1,
+                    12.4469f, -64.3443f, 57087.9f)
+        };
 
-        // Reference values calculated from NOAA online calculator for WMM 2010,
-        // and cross-checked in Matlab. The expected deltas are proportional to the
-        // magnitude of each value.
-        assertEquals(-1.867f, geomagneticField.getDeclination(), 0.1f);
-        assertEquals(47.133f, geomagneticField.getInclination(), 1.0f);
-        assertEquals(50375.6f, geomagneticField.getFieldStrength(), 100.0f);
-        assertEquals(34269.3f, geomagneticField.getHorizontalStrength(), 100.0f);
-        assertEquals(34251.2f, geomagneticField.getX(), 100.0f);
-        assertEquals(-1113.2f, geomagneticField.getY(), 10.0f);
-        assertEquals(36923.1f, geomagneticField.getZ(), 100.0f);
+        for (TestDataPoint t : testPoints) {
+            GeomagneticField field =
+                    new GeomagneticField(t.latitude, t.longitude, t.altitude, t.epochTimeMillis);
+            assertEquals(t.declinationDegree,  field.getDeclination(), DECLINATION_THRESHOLD);
+            assertEquals(t.inclinationDegree,  field.getInclination(), INCLINATION_THRESHOLD);
+            assertEquals(t.fieldStrengthNanoTesla, field.getFieldStrength(),
+                    FIELD_STRENGTH_THRESHOLD);
+
+            float horizontalFieldStrengthNanoTesla = (float)(
+                    Math.cos(Math.toRadians(t.inclinationDegree)) * t.fieldStrengthNanoTesla);
+            assertEquals(horizontalFieldStrengthNanoTesla, field.getHorizontalStrength(),
+                    FIELD_STRENGTH_THRESHOLD);
+
+            float verticalFieldStrengthNanoTesla = (float)(
+                    Math.sin(Math.toRadians(t.inclinationDegree)) * t.fieldStrengthNanoTesla);
+            assertEquals(verticalFieldStrengthNanoTesla, field.getZ(), FIELD_STRENGTH_THRESHOLD);
+
+            float declinationDegree = (float)(
+                    Math.toDegrees(Math.atan2(field.getY(), field.getX())));
+            assertEquals(t.declinationDegree, declinationDegree, DECLINATION_THRESHOLD);
+            assertEquals(horizontalFieldStrengthNanoTesla,
+                    Math.sqrt(field.getX() * field.getX() + field.getY() * field.getY()),
+                    FIELD_STRENGTH_THRESHOLD);
+        }
+    }
+
+    private class TestDataPoint {
+        public final float latitude;
+        public final float longitude;
+        public final float altitude;
+        public final long epochTimeMillis;
+        public final float declinationDegree;
+        public final float inclinationDegree;
+        public final float fieldStrengthNanoTesla;
+
+        TestDataPoint(float lat, float lng, float alt, int year, int month, int day,
+                float dec, float inc, float strength) {
+            latitude = lat;
+            longitude = lng;
+            altitude = alt;
+            epochTimeMillis = new GregorianCalendar(year, month, day).getTimeInMillis();
+            declinationDegree = dec;
+            inclinationDegree = inc;
+            fieldStrengthNanoTesla = strength;
+        }
     }
 }
diff --git a/tests/tests/incident/Android.mk b/tests/tests/incident/Android.mk
deleted file mode 100644
index d19a452..0000000
--- a/tests/tests/incident/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-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_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsIncidentTestCases
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-#LOCAL_SDK_VERSION := current
-LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        ctstestrunner
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/incident/AndroidManifest.xml b/tests/tests/incident/AndroidManifest.xml
deleted file mode 100644
index 5b7631c..0000000
--- a/tests/tests/incident/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.incident.cts">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".Whatever" />
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.os.incident.cts"
-                     android:label="CTS tests of android.os incident reporting">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
diff --git a/tests/tests/incident/AndroidTest.xml b/tests/tests/incident/AndroidTest.xml
deleted file mode 100644
index e42da3d..0000000
--- a/tests/tests/incident/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Configuration for Incident Tests">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="metrics" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsIncidentTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.os.incident.cts" />
-        <option name="runtime-hint" value="5s" />
-    </test>
-</configuration>
diff --git a/tests/tests/incident/src/android/os/cts/IncidentSettingFormatTest.java b/tests/tests/incident/src/android/os/cts/IncidentSettingFormatTest.java
deleted file mode 100644
index 57d26a1..0000000
--- a/tests/tests/incident/src/android/os/cts/IncidentSettingFormatTest.java
+++ /dev/null
@@ -1,99 +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.os.incident.cts;
-
-import android.os.IncidentReportArgs;
-
-import junit.framework.TestCase;
-import org.junit.Assert;
-
-/**
- * Test the parsing of the string IncidentReportArgs format.
- */
-public class IncidentSettingFormatTest extends TestCase {
-    private static final String TAG = "IncidentSettingFormatTest";
-
-    public void testNull() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(null);
-        assertNull(args);
-    }
-
-    public void testEmpty() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting("");
-        assertNull(args);
-    }
-
-    public void testSpaces() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" \r\n\t");
-        assertNull(args);
-    }
-
-    public void testDisabled() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" disabled ");
-        assertNull(args);
-    }
-
-    public void testAll() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" all ");
-        assertTrue(args.isAll());
-    }
-
-    public void testNone() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" none ");
-        assertFalse(args.isAll());
-        assertEquals(0, args.sectionCount());
-    }
-
-    public void testOne() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" 1 ");
-        assertFalse(args.isAll());
-        assertEquals(1, args.sectionCount());
-        assertTrue(args.containsSection(1));
-    }
-
-    public void testSeveral() throws Exception {
-        final IncidentReportArgs args = IncidentReportArgs.parseSetting(" 1, 2, , 3,4,5, 6, 78, ");
-        assertFalse(args.isAll());
-        assertEquals(7, args.sectionCount());
-        assertTrue(args.containsSection(1));
-        assertTrue(args.containsSection(2));
-        assertTrue(args.containsSection(3));
-        assertTrue(args.containsSection(4));
-        assertTrue(args.containsSection(5));
-        assertTrue(args.containsSection(6));
-        assertTrue(args.containsSection(78));
-    }
-
-    public void testZero() throws Exception {
-        try {
-            final IncidentReportArgs args = IncidentReportArgs.parseSetting(" 0 ");
-            throw new RuntimeException("parseSetting(\" 0 \") should fail with"
-                    + " IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-        }
-    }
-
-    public void testNegative() throws Exception {
-        try {
-            final IncidentReportArgs args = IncidentReportArgs.parseSetting(" -1 ");
-            throw new RuntimeException("parseSetting(\" -1 \") should fail with"
-                    + " IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-        }
-    }
-
-}
diff --git a/tests/tests/incident/src/android/os/cts/IncidentTests.java b/tests/tests/incident/src/android/os/cts/IncidentTests.java
deleted file mode 100644
index 0718738..0000000
--- a/tests/tests/incident/src/android/os/cts/IncidentTests.java
+++ /dev/null
@@ -1,29 +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.os.incident.cts;
-
-import junit.framework.TestSuite;
-
-public class IncidentTests {
-    public static TestSuite suite() {
-        TestSuite suite = new TestSuite(IncidentTests.class.getName());
-
-        suite.addTestSuite(IncidentSettingFormatTest.class);
-
-        return suite;
-    }
-}
diff --git a/tests/tests/jni/AndroidTest.xml b/tests/tests/jni/AndroidTest.xml
index 84cf235..e4ab2dd 100644
--- a/tests/tests/jni/AndroidTest.xml
+++ b/tests/tests/jni/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS JNI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/jni_vendor/AndroidTest.xml b/tests/tests/jni_vendor/AndroidTest.xml
index 65afd6b..b606559 100644
--- a/tests/tests/jni_vendor/AndroidTest.xml
+++ b/tests/tests/jni_vendor/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Vendor's JNI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/jvmti/attaching/AndroidTest.xml b/tests/tests/jvmti/attaching/AndroidTest.xml
index 1b4ed05..5723956 100644
--- a/tests/tests/jvmti/attaching/AndroidTest.xml
+++ b/tests/tests/jvmti/attaching/AndroidTest.xml
@@ -17,6 +17,7 @@
   -->
 
 <configuration description="Config for CTS jvmti attaching test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/keystore/Android.mk b/tests/tests/keystore/Android.mk
index 834a63c..c741a04 100644
--- a/tests/tests/keystore/Android.mk
+++ b/tests/tests/keystore/Android.mk
@@ -32,7 +32,7 @@
         ctstestrunner \
         guava \
         junit \
-        legacy-android-test
+        cts-security-test-support-library
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -49,7 +49,8 @@
 # Uncomment when b/13282254 is fixed.
 # LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/keystore/AndroidTest.xml b/tests/tests/keystore/AndroidTest.xml
index 87913ed..e2b4986 100644
--- a/tests/tests/keystore/AndroidTest.xml
+++ b/tests/tests/keystore/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Keystore test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java b/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java
deleted file mode 100644
index 294fda8..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java
+++ /dev/null
@@ -1,83 +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.keystore.cts;
-
-import com.android.org.bouncycastle.asn1.ASN1Encodable;
-import com.android.org.bouncycastle.asn1.ASN1Sequence;
-
-import java.security.cert.CertificateParsingException;
-
-import java.io.UnsupportedEncodingException;
-
-public class AttestationPackageInfo implements java.lang.Comparable<AttestationPackageInfo> {
-    private static final int PACKAGE_NAME_INDEX = 0;
-    private static final int VERSION_INDEX = 1;
-
-    private final String packageName;
-    private final int version;
-
-    public AttestationPackageInfo(String packageName, int version) {
-        this.packageName = packageName;
-        this.version = version;
-    }
-
-    public AttestationPackageInfo(ASN1Encodable asn1Encodable) throws CertificateParsingException {
-        if (!(asn1Encodable instanceof ASN1Sequence)) {
-            throw new CertificateParsingException(
-                    "Expected sequence for AttestationPackageInfo, found "
-                            + asn1Encodable.getClass().getName());
-        }
-
-        ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
-        try {
-            packageName = Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(
-                    sequence.getObjectAt(PACKAGE_NAME_INDEX));
-        } catch (UnsupportedEncodingException e) {
-            throw new CertificateParsingException(
-                    "Converting octet stream to String triggered an UnsupportedEncodingException",
-                    e);
-        }
-        version = Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERSION_INDEX));
-    }
-
-    public String getPackageName() {
-        return packageName;
-    }
-
-    public int getVersion() {
-        return version;
-    }
-
-    @Override
-    public String toString() {
-        return new StringBuilder().append("Package name: ").append(getPackageName())
-                .append("\nVersion: " + getVersion()).toString();
-    }
-
-    @Override
-    public int compareTo(AttestationPackageInfo other) {
-        int res = packageName.compareTo(other.packageName);
-        if (res != 0) return res;
-        res = Integer.compare(version, other.version);
-        if (res != 0) return res;
-        return res;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        return (o instanceof AttestationPackageInfo)
-                && (0 == compareTo((AttestationPackageInfo) o));
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AuthorizationList.java b/tests/tests/keystore/src/android/keystore/cts/AuthorizationList.java
deleted file mode 100644
index d488b26..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AuthorizationList.java
+++ /dev/null
@@ -1,581 +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.keystore.cts;
-
-import static com.google.common.base.Functions.forMap;
-import static com.google.common.collect.Collections2.transform;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-import android.security.keystore.KeyProperties;
-import android.util.Log;
-
-import com.android.org.bouncycastle.asn1.ASN1Encodable;
-import com.android.org.bouncycastle.asn1.ASN1Primitive;
-import com.android.org.bouncycastle.asn1.ASN1Sequence;
-import com.android.org.bouncycastle.asn1.ASN1SequenceParser;
-import com.android.org.bouncycastle.asn1.ASN1TaggedObject;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-
-import java.io.IOException;
-import java.security.cert.CertificateParsingException;
-import java.text.DateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-
-public class AuthorizationList {
-    // Algorithm values.
-    public static final int KM_ALGORITHM_RSA = 1;
-    public static final int KM_ALGORITHM_EC = 3;
-
-    // EC Curves
-    public static final int KM_EC_CURVE_P224 = 0;
-    public static final int KM_EC_CURVE_P256 = 1;
-    public static final int KM_EC_CURVE_P384 = 2;
-    public static final int KM_EC_CURVE_P521 = 3;
-
-    // Padding modes.
-    public static final int KM_PAD_NONE = 1;
-    public static final int KM_PAD_RSA_OAEP = 2;
-    public static final int KM_PAD_RSA_PSS = 3;
-    public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
-    public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
-
-    // Digest modes.
-    public static final int KM_DIGEST_NONE = 0;
-    public static final int KM_DIGEST_MD5 = 1;
-    public static final int KM_DIGEST_SHA1 = 2;
-    public static final int KM_DIGEST_SHA_2_224 = 3;
-    public static final int KM_DIGEST_SHA_2_256 = 4;
-    public static final int KM_DIGEST_SHA_2_384 = 5;
-    public static final int KM_DIGEST_SHA_2_512 = 6;
-
-    // Key origins.
-    public static final int KM_ORIGIN_GENERATED = 0;
-    public static final int KM_ORIGIN_IMPORTED = 2;
-    public static final int KM_ORIGIN_UNKNOWN = 3;
-
-    // Operation Purposes.
-    public static final int KM_PURPOSE_ENCRYPT = 0;
-    public static final int KM_PURPOSE_DECRYPT = 1;
-    public static final int KM_PURPOSE_SIGN = 2;
-    public static final int KM_PURPOSE_VERIFY = 3;
-
-    // User authenticators.
-    public static final int HW_AUTH_PASSWORD = 1 << 0;
-    public static final int HW_AUTH_FINGERPRINT = 1 << 1;
-
-    // Keymaster tag classes
-    private static final int KM_ENUM = 1 << 28;
-    private static final int KM_ENUM_REP = 2 << 28;
-    private static final int KM_UINT = 3 << 28;
-    private static final int KM_ULONG = 5 << 28;
-    private static final int KM_DATE = 6 << 28;
-    private static final int KM_BOOL = 7 << 28;
-    private static final int KM_BYTES = 9 << 28;
-
-    // Tag class removal mask
-    private static final int KEYMASTER_TAG_TYPE_MASK = 0x0FFFFFFF;
-
-    // Keymaster tags
-    private static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
-    private static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
-    private static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
-    private static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
-    private static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
-    private static final int KM_TAG_EC_CURVE = KM_ENUM | 10;
-    private static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
-    private static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
-    private static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
-    private static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
-    private static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
-    private static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
-    private static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
-    private static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
-    private static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
-    private static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
-    private static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
-    private static final int KM_TAG_ORIGIN = KM_ENUM | 702;
-    private static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
-    private static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
-    private static final int KM_TAG_OS_VERSION = KM_UINT | 705;
-    private static final int KM_TAG_OS_PATCHLEVEL = KM_UINT | 706;
-    private static final int KM_TAG_ATTESTATION_APPLICATION_ID = KM_BYTES | 709;
-
-    // Map for converting padding values to strings
-    private static final ImmutableMap<Integer, String> paddingMap = ImmutableMap
-            .<Integer, String> builder()
-            .put(KM_PAD_NONE, "NONE")
-            .put(KM_PAD_RSA_OAEP, "OAEP")
-            .put(KM_PAD_RSA_PSS, "PSS")
-            .put(KM_PAD_RSA_PKCS1_1_5_ENCRYPT, "PKCS1 ENCRYPT")
-            .put(KM_PAD_RSA_PKCS1_1_5_SIGN, "PKCS1 SIGN")
-            .build();
-
-    // Map for converting digest values to strings
-    private static final ImmutableMap<Integer, String> digestMap = ImmutableMap
-            .<Integer, String> builder()
-            .put(KM_DIGEST_NONE, "NONE")
-            .put(KM_DIGEST_MD5, "MD5")
-            .put(KM_DIGEST_SHA1, "SHA1")
-            .put(KM_DIGEST_SHA_2_224, "SHA224")
-            .put(KM_DIGEST_SHA_2_256, "SHA256")
-            .put(KM_DIGEST_SHA_2_384, "SHA384")
-            .put(KM_DIGEST_SHA_2_512, "SHA512")
-            .build();
-
-    // Map for converting purpose values to strings
-    private static final ImmutableMap<Integer, String> purposeMap = ImmutableMap
-            .<Integer, String> builder()
-            .put(KM_PURPOSE_DECRYPT, "DECRYPT")
-            .put(KM_PURPOSE_ENCRYPT, "ENCRYPT")
-            .put(KM_PURPOSE_SIGN, "SIGN")
-            .put(KM_PURPOSE_VERIFY, "VERIFY")
-            .build();
-
-    private Set<Integer> purposes;
-    private Integer algorithm;
-    private Integer keySize;
-    private Set<Integer> digests;
-    private Set<Integer> paddingModes;
-    private Integer ecCurve;
-    private Long rsaPublicExponent;
-    private Date activeDateTime;
-    private Date originationExpireDateTime;
-    private Date usageExpireDateTime;
-    private boolean noAuthRequired;
-    private Integer userAuthType;
-    private Integer authTimeout;
-    private boolean allowWhileOnBody;
-    private boolean allApplications;
-    private byte[] applicationId;
-    private Date creationDateTime;
-    private Integer origin;
-    private boolean rollbackResistant;
-    private RootOfTrust rootOfTrust;
-    private Integer osVersion;
-    private Integer osPatchLevel;
-    private AttestationApplicationId attestationApplicationId;
-
-    public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
-        if (!(sequence instanceof ASN1Sequence)) {
-            throw new CertificateParsingException("Expected sequence for authorization list, found "
-                    + sequence.getClass().getName());
-        }
-
-        ASN1SequenceParser parser = ((ASN1Sequence) sequence).parser();
-        ASN1TaggedObject entry = parseAsn1TaggedObject(parser);
-        for (; entry != null; entry = parseAsn1TaggedObject(parser)) {
-            int tag = entry.getTagNo();
-            ASN1Primitive value = entry.getObject();
-            Log.i("Attestation", "Parsing tag: [" + tag + "], value: [" + value + "]");
-            switch (tag) {
-                default:
-                    throw new CertificateParsingException("Unknown tag " + tag + " found");
-
-                case KM_TAG_PURPOSE & KEYMASTER_TAG_TYPE_MASK:
-                    purposes = Asn1Utils.getIntegersFromAsn1Set(value);
-                    break;
-                case KM_TAG_ALGORITHM & KEYMASTER_TAG_TYPE_MASK:
-                    algorithm = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_KEY_SIZE & KEYMASTER_TAG_TYPE_MASK:
-                    keySize = Asn1Utils.getIntegerFromAsn1(value);
-                    Log.i("Attestation", "Found KEY SIZE, value: " + keySize);
-                    break;
-                case KM_TAG_DIGEST & KEYMASTER_TAG_TYPE_MASK:
-                    digests = Asn1Utils.getIntegersFromAsn1Set(value);
-                    break;
-                case KM_TAG_PADDING & KEYMASTER_TAG_TYPE_MASK:
-                    paddingModes = Asn1Utils.getIntegersFromAsn1Set(value);
-                    break;
-                case KM_TAG_RSA_PUBLIC_EXPONENT & KEYMASTER_TAG_TYPE_MASK:
-                    rsaPublicExponent = Asn1Utils.getLongFromAsn1(value);
-                    break;
-                case KM_TAG_NO_AUTH_REQUIRED & KEYMASTER_TAG_TYPE_MASK:
-                    noAuthRequired = true;
-                    break;
-                case KM_TAG_CREATION_DATETIME & KEYMASTER_TAG_TYPE_MASK:
-                    creationDateTime = Asn1Utils.getDateFromAsn1(value);
-                    break;
-                case KM_TAG_ORIGIN & KEYMASTER_TAG_TYPE_MASK:
-                    origin = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_OS_VERSION & KEYMASTER_TAG_TYPE_MASK:
-                    osVersion = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_OS_PATCHLEVEL & KEYMASTER_TAG_TYPE_MASK:
-                    osPatchLevel = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_ACTIVE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
-                    activeDateTime = Asn1Utils.getDateFromAsn1(value);
-                    break;
-                case KM_TAG_ORIGINATION_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
-                    originationExpireDateTime = Asn1Utils.getDateFromAsn1(value);
-                    break;
-                case KM_TAG_USAGE_EXPIRE_DATETIME & KEYMASTER_TAG_TYPE_MASK:
-                    usageExpireDateTime = Asn1Utils.getDateFromAsn1(value);
-                    break;
-                case KM_TAG_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
-                    applicationId = Asn1Utils.getByteArrayFromAsn1(value);
-                    break;
-                case KM_TAG_ROLLBACK_RESISTANT & KEYMASTER_TAG_TYPE_MASK:
-                    rollbackResistant = true;
-                    break;
-                case KM_TAG_AUTH_TIMEOUT & KEYMASTER_TAG_TYPE_MASK:
-                    authTimeout = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_ALLOW_WHILE_ON_BODY & KEYMASTER_TAG_TYPE_MASK:
-                    allowWhileOnBody = true;
-                    break;
-                case KM_TAG_EC_CURVE & KEYMASTER_TAG_TYPE_MASK:
-                    ecCurve = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_USER_AUTH_TYPE & KEYMASTER_TAG_TYPE_MASK:
-                    userAuthType = Asn1Utils.getIntegerFromAsn1(value);
-                    break;
-                case KM_TAG_ROOT_OF_TRUST & KEYMASTER_TAG_TYPE_MASK:
-                    rootOfTrust = new RootOfTrust(value);
-                    break;
-                case KM_TAG_ATTESTATION_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
-                    attestationApplicationId = new AttestationApplicationId(Asn1Utils
-                            .getAsn1EncodableFromBytes(Asn1Utils.getByteArrayFromAsn1(value)));
-                    break;
-                case KM_TAG_ALL_APPLICATIONS & KEYMASTER_TAG_TYPE_MASK:
-                    allApplications = true;
-                    break;
-            }
-        }
-
-    }
-
-    public static String algorithmToString(int algorithm) {
-        switch (algorithm) {
-            case KM_ALGORITHM_RSA:
-                return "RSA";
-            case KM_ALGORITHM_EC:
-                return "ECDSA";
-            default:
-                return "Unknown";
-        }
-    }
-
-    public static String paddingModesToString(final Set<Integer> paddingModes) {
-        return joinStrings(transform(paddingModes, forMap(paddingMap, "Unknown")));
-    }
-
-    public static String paddingModeToString(int paddingMode) {
-        return forMap(paddingMap, "Unknown").apply(paddingMode);
-    }
-
-    public static String digestsToString(Set<Integer> digests) {
-        return joinStrings(transform(digests, forMap(digestMap, "Unknown")));
-    }
-
-    public static String digestToString(int digest) {
-        return forMap(digestMap, "Unknown").apply(digest);
-    }
-
-    public static String purposesToString(Set<Integer> purposes) {
-        return joinStrings(transform(purposes, forMap(purposeMap, "Unknown")));
-    }
-
-    public static String userAuthTypeToString(int userAuthType) {
-        List<String> types = Lists.newArrayList();
-        if ((userAuthType & HW_AUTH_FINGERPRINT) != 0)
-            types.add("Fingerprint");
-        if ((userAuthType & HW_AUTH_PASSWORD) != 0)
-            types.add("Password");
-        return joinStrings(types);
-    }
-
-    public static String originToString(int origin) {
-        switch (origin) {
-            case KM_ORIGIN_GENERATED:
-                return "Generated";
-            case KM_ORIGIN_IMPORTED:
-                return "Imported";
-            case KM_ORIGIN_UNKNOWN:
-                return "Unknown (KM0)";
-            default:
-                return "Unknown";
-        }
-    }
-
-    private static String joinStrings(Collection<String> collection) {
-        return new StringBuilder()
-                .append("[")
-                .append(Joiner.on(", ").join(collection))
-                .append("]")
-                .toString();
-    }
-
-    private static String formatDate(Date date) {
-        return DateFormat.getDateTimeInstance().format(date);
-    }
-
-    private static ASN1TaggedObject parseAsn1TaggedObject(ASN1SequenceParser parser)
-            throws CertificateParsingException {
-        ASN1Encodable asn1Encodable = parseAsn1Encodable(parser);
-        if (asn1Encodable == null || asn1Encodable instanceof ASN1TaggedObject) {
-            return (ASN1TaggedObject) asn1Encodable;
-        }
-        throw new CertificateParsingException(
-                "Expected tagged object, found " + asn1Encodable.getClass().getName());
-    }
-
-    private static ASN1Encodable parseAsn1Encodable(ASN1SequenceParser parser)
-            throws CertificateParsingException {
-        try {
-            return parser.readObject();
-        } catch (IOException e) {
-            throw new CertificateParsingException("Failed to parse ASN1 sequence", e);
-        }
-    }
-
-    public Set<Integer> getPurposes() {
-        return purposes;
-    }
-
-    public Integer getAlgorithm() {
-        return algorithm;
-    }
-
-    public Integer getKeySize() {
-        return keySize;
-    }
-
-    public Set<Integer> getDigests() {
-        return digests;
-    }
-
-    public Set<Integer> getPaddingModes() {
-        return paddingModes;
-    }
-
-    public Set<String> getPaddingModesAsStrings() throws CertificateParsingException {
-        if (paddingModes == null) {
-            return ImmutableSet.of();
-        }
-
-        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
-        for (int paddingMode : paddingModes) {
-            switch (paddingMode) {
-                case KM_PAD_NONE:
-                    builder.add(KeyProperties.ENCRYPTION_PADDING_NONE);
-                    break;
-                case KM_PAD_RSA_OAEP:
-                    builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
-                    break;
-                case KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
-                    builder.add(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
-                    break;
-                case KM_PAD_RSA_PKCS1_1_5_SIGN:
-                    builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
-                    break;
-                case KM_PAD_RSA_PSS:
-                    builder.add(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
-                    break;
-                default:
-                    throw new CertificateParsingException("Invalid padding mode " + paddingMode);
-            }
-        }
-        return builder.build();
-    }
-
-    public Integer getEcCurve() {
-        return ecCurve;
-    }
-
-    public String ecCurveAsString() {
-        if (ecCurve == null)
-            return "NULL";
-
-        switch (ecCurve) {
-            case KM_EC_CURVE_P224:
-                return "secp224r1";
-            case KM_EC_CURVE_P256:
-                return "secp256r1";
-            case KM_EC_CURVE_P384:
-                return "secp384r1";
-            case KM_EC_CURVE_P521:
-                return "secp521r1";
-            default:
-                return "unknown";
-        }
-    }
-
-    public Long getRsaPublicExponent() {
-        return rsaPublicExponent;
-    }
-
-    public Date getActiveDateTime() {
-        return activeDateTime;
-    }
-
-    public Date getOriginationExpireDateTime() {
-        return originationExpireDateTime;
-    }
-
-    public Date getUsageExpireDateTime() {
-        return usageExpireDateTime;
-    }
-
-    public boolean isNoAuthRequired() {
-        return noAuthRequired;
-    }
-
-    public Integer getUserAuthType() {
-        return userAuthType;
-    }
-
-    public Integer getAuthTimeout() {
-        return authTimeout;
-    }
-
-    public boolean isAllowWhileOnBody() {
-        return allowWhileOnBody;
-    }
-
-    public boolean isAllApplications() {
-        return allApplications;
-    }
-
-    public byte[] getApplicationId() {
-        return applicationId;
-    }
-
-    public Date getCreationDateTime() {
-        return creationDateTime;
-    }
-
-    public Integer getOrigin() {
-        return origin;
-    }
-
-    public boolean isRollbackResistant() {
-        return rollbackResistant;
-    }
-
-    public RootOfTrust getRootOfTrust() {
-        return rootOfTrust;
-    }
-
-    public Integer getOsVersion() {
-        return osVersion;
-    }
-
-    public Integer getOsPatchLevel() {
-        return osPatchLevel;
-    }
-
-    public AttestationApplicationId getAttestationApplicationId() {
-        return attestationApplicationId;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder s = new StringBuilder();
-
-        if (algorithm != null) {
-            s.append("\nAlgorithm: ").append(algorithmToString(algorithm));
-        }
-
-        if (keySize != null) {
-            s.append("\nKeySize: ").append(keySize);
-        }
-
-        if (purposes != null && !purposes.isEmpty()) {
-            s.append("\nPurposes: ").append(purposesToString(purposes));
-        }
-
-        if (digests != null && !digests.isEmpty()) {
-            s.append("\nDigests: ").append(digestsToString(digests));
-        }
-
-        if (paddingModes != null && !paddingModes.isEmpty()) {
-            s.append("\nPadding modes: ").append(paddingModesToString(paddingModes));
-        }
-
-        if (ecCurve != null) {
-            s.append("\nEC Curve: ").append(ecCurveAsString());
-        }
-
-        String label = "\nRSA exponent: ";
-        if (rsaPublicExponent != null) {
-            s.append(label).append(rsaPublicExponent);
-        }
-
-        if (activeDateTime != null) {
-            s.append("\nActive: ").append(formatDate(activeDateTime));
-        }
-
-        if (originationExpireDateTime != null) {
-            s.append("\nOrigination expire: ").append(formatDate(originationExpireDateTime));
-        }
-
-        if (usageExpireDateTime != null) {
-            s.append("\nUsage expire: ").append(formatDate(usageExpireDateTime));
-        }
-
-        if (!noAuthRequired && userAuthType != null) {
-            s.append("\nAuth types: ").append(userAuthTypeToString(userAuthType));
-            if (authTimeout != null) {
-                s.append("\nAuth timeout: ").append(authTimeout);
-            }
-        }
-
-        if (applicationId != null) {
-            s.append("\nApplication ID: ").append(new String(applicationId));
-        }
-
-        if (creationDateTime != null) {
-            s.append("\nCreated: ").append(formatDate(creationDateTime));
-        }
-
-        if (origin != null) {
-            s.append("\nOrigin: ").append(originToString(origin));
-        }
-
-        if (rollbackResistant) {
-            s.append("\nRollback resistant: true");
-        }
-
-        if (rootOfTrust != null) {
-            s.append("\nRoot of Trust:\n");
-            s.append(rootOfTrust);
-        }
-
-        if (osVersion != null) {
-            s.append("\nOS Version: ").append(osVersion);
-        }
-
-        if (osPatchLevel != null) {
-            s.append("\nOS Patchlevel: ").append(osPatchLevel);
-        }
-
-        if (attestationApplicationId != null) {
-            s.append("\nAttestation Application Id:").append(attestationApplicationId);
-        }
-        return s.toString();
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index d901690..0342f08 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -62,6 +62,9 @@
 import android.test.AndroidTestCase;
 import android.util.ArraySet;
 
+import com.android.org.bouncycastle.asn1.x500.X500Name;
+import com.android.org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -70,6 +73,7 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.ProviderException;
+import java.security.PublicKey;
 import java.security.SignatureException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
@@ -422,7 +426,7 @@
 
         try {
             Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
-            verifyCertificateSignatures(certificates);
+            verifyCertificateChain(certificates);
 
             X509Certificate attestationCert = (X509Certificate) certificates[0];
             Attestation attestation = new Attestation(attestationCert);
@@ -476,7 +480,7 @@
 
         try {
             Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
-            verifyCertificateSignatures(certificates);
+            verifyCertificateChain(certificates);
 
             X509Certificate attestationCert = (X509Certificate) certificates[0];
             Attestation attestation = new Attestation(attestationCert);
@@ -876,12 +880,36 @@
         keyPairGenerator.generateKeyPair();
     }
 
-    private void verifyCertificateSignatures(Certificate[] certChain)
+    private void verifyCertificateChain(Certificate[] certChain)
             throws GeneralSecurityException {
         assertNotNull(certChain);
         for (int i = 1; i < certChain.length; ++i) {
             try {
-                certChain[i - 1].verify(certChain[i].getPublicKey());
+                PublicKey pubKey = certChain[i].getPublicKey();
+                certChain[i - 1].verify(pubKey);
+                if (i == certChain.length - 1) {
+                    // Last cert should be self-signed.
+                    certChain[i].verify(pubKey);
+                }
+
+                // Check that issuer in the signed cert matches subject in the signing cert.
+                X509Certificate x509CurrCert = (X509Certificate) certChain[i];
+                X509Certificate x509PrevCert = (X509Certificate) certChain[i - 1];
+                X500Name signingCertSubject =
+                        new JcaX509CertificateHolder(x509CurrCert).getSubject();
+                X500Name signedCertIssuer =
+                        new JcaX509CertificateHolder(x509PrevCert).getIssuer();
+                // Use .toASN1Object().equals() rather than .equals() because .equals() is case
+                // insensitive, and we want to verify an exact match.
+                assertTrue(
+                        signedCertIssuer.toASN1Object().equals(signingCertSubject.toASN1Object()));
+
+                if (i == 1) {
+                    // First cert should have subject "CN=Android Keystore Key".
+                    X500Name signedCertSubject =
+                            new JcaX509CertificateHolder(x509PrevCert).getSubject();
+                    assertEquals(signedCertSubject, new X500Name("CN=Android Keystore Key"));
+                }
             } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
                     | NoSuchProviderException | SignatureException e) {
                 throw new GeneralSecurityException("Failed to verify certificate "
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
index d64e7b7..229b69c 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
@@ -41,6 +41,8 @@
 import java.security.UnrecoverableKeyException;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -1682,8 +1684,15 @@
                 assertEquals(expected.getKey(alias, null),
                              actual.getKey(alias, null));
             } else {
-                assertEquals(expected.getKey(alias, PASSWORD_KEY),
-                             actual.getKey(alias, PASSWORD_KEY));
+                Key actualKey = actual.getKey(alias, PASSWORD_KEY);
+                Key expectedKey = expected.getKey(alias, PASSWORD_KEY);
+                if (actualKey instanceof RSAPrivateKey &&
+                    expectedKey instanceof RSAPrivateKey) {
+                  assertEquals(((RSAPrivateKey)actualKey).getModulus(),
+                               ((RSAPrivateKey)expectedKey).getModulus());
+                } else {
+                  assertEquals(actualKey, expectedKey);
+                }
             }
             assertEquals(expected.getCertificate(alias), actual.getCertificate(alias));
         }
diff --git a/tests/tests/libcorefileio/Android.mk b/tests/tests/libcorefileio/Android.mk
index 2f560ec..c56a3b7 100644
--- a/tests/tests/libcorefileio/Android.mk
+++ b/tests/tests/libcorefileio/Android.mk
@@ -21,7 +21,9 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/libcorefileio/AndroidTest.xml b/tests/tests/libcorefileio/AndroidTest.xml
index c2b230e..074a833 100644
--- a/tests/tests/libcorefileio/AndroidTest.xml
+++ b/tests/tests/libcorefileio/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Legacy Libcore test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/location/Android.mk b/tests/tests/location/Android.mk
index a0b8142..4e4257e 100644
--- a/tests/tests/location/Android.mk
+++ b/tests/tests/location/Android.mk
@@ -26,11 +26,11 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
+LOCAL_JAVA_LIBRARIES := telephony-common android.test.base.stubs
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util ctstestrunner apache-commons-math
 
-LOCAL_SDK_VERSION := test_current
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src/android/location/cts) \
    $(call all-proto-files-under, protos)
 
@@ -50,6 +50,8 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+LOCAL_JAVA_LIBRARIES := telephony-common android.test.base.stubs
+
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner  apache-commons-math
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
@@ -58,10 +60,9 @@
    $(call all-proto-files-under, protos)
 
 LOCAL_PACKAGE_NAME := CtsLocationTestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_JACK_FLAGS := --multi-dex native
 LOCAL_DX_FLAGS := --multi-dex
 
-LOCAL_SDK_VERSION := test_current
-
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/location/AndroidManifest.xml b/tests/tests/location/AndroidManifest.xml
index 1357a38..6e3bbf1 100644
--- a/tests/tests/location/AndroidManifest.xml
+++ b/tests/tests/location/AndroidManifest.xml
@@ -28,9 +28,15 @@
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.location.cts"
                      android:label="CTS tests of android.location">
diff --git a/tests/tests/location/AndroidTest.xml b/tests/tests/location/AndroidTest.xml
index 4f55a93..c69e224 100644
--- a/tests/tests/location/AndroidTest.xml
+++ b/tests/tests/location/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Location test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="location" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/location/src/android/location/cts/AddressTest.java b/tests/tests/location/src/android/location/cts/AddressTest.java
index 411a687..5e44b61 100644
--- a/tests/tests/location/src/android/location/cts/AddressTest.java
+++ b/tests/tests/location/src/android/location/cts/AddressTest.java
@@ -329,6 +329,8 @@
         assertEquals(address.getPhone(), parcel.readString());
         assertEquals(address.getUrl(), parcel.readString());
         assertEquals(address.getExtras(), parcel.readBundle());
+
+        parcel.recycle();
     }
 
     private class MockParcelable implements Parcelable {
diff --git a/tests/tests/location/src/android/location/cts/CriteriaTest.java b/tests/tests/location/src/android/location/cts/CriteriaTest.java
index 422f561..97b4079 100644
--- a/tests/tests/location/src/android/location/cts/CriteriaTest.java
+++ b/tests/tests/location/src/android/location/cts/CriteriaTest.java
@@ -217,5 +217,7 @@
         assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
         assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
         assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
+
+        parcel.recycle();
     }
 }
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
new file mode 100644
index 0000000..d7dbc89
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
@@ -0,0 +1,333 @@
+package android.location.cts;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import 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;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test sending SMS and MMS using {@link android.telephony.SmsManager}.
+ */
+public class EmergencyCallMessageTest extends GnssTestCase {
+
+    private static final String TAG = "EmergencyCallMSGTest";
+
+    private static final 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();
+
+        mRandom = new Random(System.currentTimeMillis());
+        mTelephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    public void testSendSmsMessage() {
+        // this test is only for cts verifier
+        if (!isCtsVerifierTest()) {
+            return;
+        }
+        SmsManager smsManager = SmsManager.getDefault();
+        final String selfNumber = getPhoneNumber(mContext);
+        smsManager.sendTextMessage(selfNumber, null, SMS_MESSAGE_BODY, null, null);
+    }
+
+    public void testSendSmsDataMessage() {
+        // this test is only for cts verifier
+        if (!isCtsVerifierTest()) {
+            return;
+        }
+        SmsManager smsManager = SmsManager.getDefault();
+        final String selfNumber = getPhoneNumber(mContext);
+        smsManager.sendDataMessage(selfNumber, null, DEFAULT_DATA_SMS_PORT,
+            SMS_DATA_MESSAGE_BODY.getBytes(), null, null);
+    }
+
+    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()) {
+            phoneNumber = System.getProperty(PHONE_NUMBER_KEY);
+        }
+        return phoneNumber;
+    }
+
+    private static boolean shouldParseContentDisposition() {
+        return SmsManager
+                .getDefault()
+                .getCarrierConfigValues()
+                .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
+    }
+
+    private static boolean doesSupportMMS() {
+        return SmsManager
+                .getDefault()
+                .getCarrierConfigValues()
+                .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true);
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
new file mode 100644
index 0000000..7ebb04a
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.telephony.TelephonyManager;
+import android.net.wifi.WifiManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is used to test Wifi realted features.
+ */
+public class EmergencyCallWifiTest extends GnssTestCase {
+
+    private final static String TAG = EmergencyCallWifiTest.class.getCanonicalName();
+    private final static String LATCH_NAME = "EmergencyCallWifiTest";
+    private final static String GOOGLE_URL = "www.google.com";
+    private final static int WEB_PORT = 80;
+    private static final int BATCH_SCAN_BSSID_LIMIT = 25;
+    private static final int WIFI_SCAN_TIMEOUT_SEC = 30;
+    private static final long SETTINGS_PERIOD_MS = TimeUnit.SECONDS.toMillis(4);
+    private static final long INTERNET_CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+    private static final int MIN_SCAN_COUNT = 1;
+
+    private WifiManager mWifiManager;
+    private Context mContext;
+    private WifiScanReceiver mWifiScanReceiver;
+    private int mScanCounter = 0;
+    private CountDownLatch mLatch;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = getContext();
+        mWifiManager = (WifiManager) mContext.getSystemService(mContext.WIFI_SERVICE);
+        mWifiScanReceiver = new WifiScanReceiver();
+    }
+
+    public void testWifiScan() throws Exception {
+        mContext.registerReceiver(mWifiScanReceiver, new IntentFilter(
+                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+        mLatch = new CountDownLatch(1);
+        mWifiManager.startScan();
+        Log.d(TAG, "Waiting for wifiScan to complete.");
+        mLatch.await(WIFI_SCAN_TIMEOUT_SEC, TimeUnit.SECONDS);
+        if (mScanCounter < MIN_SCAN_COUNT) {
+            fail(String.format("Expected at least %d scans but only %d scans happened",
+            MIN_SCAN_COUNT, mScanCounter));
+        }
+    }
+
+    public void testWifiConnection() {
+        boolean isReachable =
+            isReachable(GOOGLE_URL, WEB_PORT, (int)INTERNET_CONNECTION_TIMEOUT);
+        assertTrue("Can not connect to google.com."
+         + " Please make sure device has the internet connection", isReachable);
+    }
+
+    private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
+        try {
+            try (Socket soc = new Socket()) {
+                soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
+            }
+            return true;
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    private class WifiScanReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context c, Intent intent) {
+            List <ScanResult> scanResults = mWifiManager.getScanResults();
+            Log.d(TAG, String.format("Got scan results with size %d", scanResults.size()));
+            for (ScanResult result : scanResults) {
+                Log.d(TAG, result.toString());
+            }
+            mScanCounter++;
+            mLatch.countDown();
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GnssClockTest.java b/tests/tests/location/src/android/location/cts/GnssClockTest.java
index b883f74..71e106a 100644
--- a/tests/tests/location/src/android/location/cts/GnssClockTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssClockTest.java
@@ -62,6 +62,7 @@
         parcel.setDataPosition(0);
         GnssClock newClock = GnssClock.CREATOR.createFromParcel(parcel);
         verifyTestValues(newClock);
+        parcel.recycle();
     }
 
     public void testSet() {
diff --git a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
new file mode 100644
index 0000000..160e9c0
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts;
+
+import android.location.LocationManager;
+import android.util.Log;
+
+/**
+ * Test the {@link LocationManager#getGnssYearOfHardware} and
+ * {@link LocationManager#getGnssHardwareModelName} values.
+ */
+public class GnssHardwareInfoTest extends GnssTestCase {
+
+  private static final String TAG = "GnssHardwareInfoTest";
+  private static final int MIN_HARDWARE_YEAR = 2015;
+
+  /**
+   * Minimum plausible descriptive hardware model name length, e.g. "ABC1" for first GNSS version
+   * ever shipped by ABC company.
+   */
+  private static final int MIN_HARDWARE_MODEL_NAME_LENGTH = 4;
+  private static final int MIN_HARDWARE_YEAR_FOR_VALID_STRING = 2018;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    mTestLocationManager = new TestLocationManager(getContext());
+  }
+
+  /**
+   * Verify hardware year is reported as 2015 or higher, with exception that in 2015, some
+   * devies did not implement the underlying HAL API, and thus will report 0.
+   */
+  public void testHardwareYear() throws Exception {
+    int gnssHardwareYear = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
+    // Allow 0 until 2019, as older, upgrading devices may report 0.
+    assertTrue("Hardware year must be 2015 or higher",
+        gnssHardwareYear >= MIN_HARDWARE_YEAR || gnssHardwareYear == 0);
+  }
+
+  /**
+   * Verify GNSS hardware model year is reported as a valid, descriptive value.
+   * Descriptive is limited to a character count, and not the older values.
+   */
+  public void testHardwareModelName() throws Exception {
+    String gnssHardwareModelName =
+        mTestLocationManager.getLocationManager().getGnssHardwareModelName();
+    assertTrue("gnssHardwareModelName must not be null", gnssHardwareModelName != null);
+    assertTrue("gnssHardwareModelName must be descriptive - at least 4 characters long",
+        gnssHardwareModelName.length() >= MIN_HARDWARE_MODEL_NAME_LENGTH);
+
+    if (mTestLocationManager.getLocationManager().getGnssYearOfHardware() >=
+        MIN_HARDWARE_YEAR_FOR_VALID_STRING) {
+      assertFalse("gnssHardwareModelName must be descriptive - not default value",
+          gnssHardwareModelName.contentEquals(
+              LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN));
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
new file mode 100644
index 0000000..cd230d5
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts;
+
+/**
+ * Test the "gps" location output works through various rate changes
+ *
+ * Tests:
+ * 1. Toggle through various rates.
+ * 2. Mix toggling through various rates with start & stop.
+ *
+ * Inspired by bugs 65246279, 65425110
+ */
+
+public class GnssLocationRateChangeTest extends GnssTestCase {
+
+    private static final String TAG = "GnssLocationRateChangeTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 1;
+
+    private TestLocationListener mLocationListenerMain;
+    private TestLocationListener mLocationListenerAfterRateChanges;
+    // Various rates, where underlying GNSS hardware states may enter different modes
+    private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
+    private static final int LOOPS_FOR_STRESS_TEST = 20;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestLocationManager = new TestLocationManager(getContext());
+        // Using separate listeners, so the await trigger for the after-rate-changes listener is
+        // independent of any possible locations that flow during setup, and rate change stress
+        // testing
+        mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListenerMain != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+        }
+        if (mLocationListenerAfterRateChanges != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output
+     * remains responsive after all is done.
+     */
+    public void testVariedRates() throws Exception {
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        for (int timeBetweenLocationsMsec : TBF_MSEC) {
+            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                    timeBetweenLocationsMsec);
+        }
+        mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+     * that may stress the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output remains responsive
+     * after all is done.
+     */
+    public void testVariedRatesOnOff() throws Exception {
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        for (int timeBetweenLocationsMsec : TBF_MSEC) {
+            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                    timeBetweenLocationsMsec);
+            // Also flip the requests on and off quickly
+            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+        }
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+     * in multiple loops to provide additional stress to the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output remains responsive
+     * after all is done.
+     */
+    public void testVariedRatesRepetitive() throws Exception {
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        // Two loops, first without removes, then with removes
+        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+            for (int timeBetweenLocationsMsec : TBF_MSEC) {
+               mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                        timeBetweenLocationsMsec);
+            }
+        }
+        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+            for (int timeBetweenLocationsMsec : TBF_MSEC) {
+                mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                        timeBetweenLocationsMsec);
+                // Also flip the requests on and off quickly
+                mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+            }
+        }
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
index 3411914..d2499a0 100644
--- a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
@@ -16,9 +16,7 @@
 
 package android.location.cts;
 
-import android.location.Criteria;
 import android.location.Location;
-import android.location.LocationManager;
 import android.util.Log;
 
 /**
@@ -133,14 +131,18 @@
         success);
 
     SoftAssert softAssert = new SoftAssert(TAG);
+    // don't check speed of first GNSS location - it may not be ready in some cases
+    boolean checkSpeed = false;
     for (Location location : mLocationListener.getReceivedLocationList()) {
-      checkLocationRegularFields(softAssert, location);
+      checkLocationRegularFields(softAssert, location, checkSpeed);
+      checkSpeed = true;
     }
 
     softAssert.assertAll();
   }
 
-  public static void checkLocationRegularFields(SoftAssert softAssert, Location location) {
+  public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
+                                                boolean checkSpeed) {
     // For the altitude: the unit is meter
     // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
     // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
@@ -177,13 +179,16 @@
     softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
         location.getLatitude() >= -90 && location.getLatitude() <= 90);
 
-    softAssert.assertTrue("All GNSS locations generated by the LocationManager "
-        + "must have speeds.", location.hasSpeed());
+    if (checkSpeed) {
+      softAssert.assertTrue("All but the first GNSS location from LocationManager "
+              + "must have speeds.", location.hasSpeed());
+    }
 
-    // For the speed, during the cts test device shouldn't move faster than 1m/s
+    // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
+    // to 5m/s for possible early fix noise in moderate signal test environments
     if(location.hasSpeed()) {
-      softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 1] m/s",
-          location.getSpeed() >= 0 && location.getSpeed() <= 1);
+      softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
+          location.getSpeed() >= 0 && location.getSpeed() <= 5);
     }
 
   }
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
index 2fc63a8..c7d28c9 100644
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
@@ -82,6 +82,7 @@
         parcel.setDataPosition(0);
         GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
         verifyTestValues(newMeasurement);
+        parcel.recycle();
     }
 
     public void testSet() {
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
index d69be8b..af5e943 100644
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
@@ -134,6 +134,7 @@
         GnssNavigationMessage newMessage =
                 GnssNavigationMessage.CREATOR.createFromParcel(parcel);
         verifyTestValues(newMessage);
+        parcel.recycle();
     }
 
     public void testReset() {
diff --git a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
index ec78ea3..d86d612 100644
--- a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
@@ -39,7 +39,7 @@
   private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
   private static final int MIN_SATELLITES_REQUIREMENT = 4;
   private static final double SECONDS_PER_NANO = 1.0e-9;
-  private static final double POSITION_THRESHOLD_IN_DEGREES = 0.003; // degrees (~= 300 meters)
+
   // GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in time
   // is 65-83 ms, which is 18 ms range.
   // GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
@@ -58,6 +58,12 @@
   // that are the short end of the range.
   private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
 
+  private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
+  private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
+  private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
+  private static final float HORIZONTAL_OFFSET_SIGMA = 3;  // 3 * the ~68%ile level
+  private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
+
   private TestGnssMeasurementListener mMeasurementListener;
   private TestLocationListener mLocationListener;
 
@@ -224,92 +230,173 @@
     }
   }
 
-  /*
- * Use pseudorange calculation library to calculate position then compare to location from
- * Location Manager.
- */
-  @CddTest(requirement="7.3.3")
-  public void testPseudoPosition() throws Exception {
-    // Checks if Gnss hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
-    // From android O, CTS tests should run in the lab with GPS signal.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
-        TAG, MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED, true)) {
-      return;
+    /*
+     * Use pseudorange calculation library to calculate position then compare to location from
+     * Location Manager.
+     */
+    @CddTest(requirement = "7.3.3")
+    public void testPseudoPosition() throws Exception {
+        // Checks if Gnss hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+        // From android O, CTS tests should run in the lab with GPS signal.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+                TAG, MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED, true)) {
+            return;
+        }
+
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener = new TestGnssMeasurementListener(TAG,
+                MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+        boolean success = mLocationListener.await();
+
+        List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
+        assertTrue("Time elapsed without getting enough location fixes."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success && receivedLocationList.size() > 0);
+        Location locationFromApi = receivedLocationList.get(0);
+
+        // Since we are checking the eventCount later, there is no need to check the return value
+        // here.
+        mMeasurementListener.await();
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        int eventCount = events.size();
+        Log.i(TAG, "Number of Gps Event received = " + eventCount);
+        int gnssYearOfHardware = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
+        if (eventCount == 0 && gnssYearOfHardware < MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED) {
+            return;
+        }
+
+        Log.i(TAG, "This is a device from 2016 or later.");
+        assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+                eventCount > 0);
+
+        PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
+                = new PseudorangePositionVelocityFromRealTimeEvents();
+        mPseudorangePositionFromRealTimeEvents.setReferencePosition(
+                (int) (locationFromApi.getLatitude() * 1E7),
+                (int) (locationFromApi.getLongitude() * 1E7),
+                (int) (locationFromApi.getAltitude() * 1E7));
+
+        Log.i(TAG, "Location from Location Manager"
+                + ", Latitude:" + locationFromApi.getLatitude()
+                + ", Longitude:" + locationFromApi.getLongitude()
+                + ", Altitude:" + locationFromApi.getAltitude());
+
+        // Ensure at least some calculated locations have a reasonably low uncertainty
+        boolean someLocationsHaveLowPosUnc = false;
+        boolean someLocationsHaveLowVelUnc = false;
+
+        int totalCalculatedLocationCnt = 0;
+        for (GnssMeasurementsEvent event : events) {
+            // In mMeasurementListener.getEvents() we already filtered out events, at this point
+            // every event will have at least 4 satellites in one constellation.
+            mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
+                    event);
+            double[] calculatedLatLngAlt =
+                    mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
+            // it will return NaN when there is no enough measurements to calculate the position
+            if (Double.isNaN(calculatedLatLngAlt[0])) {
+                continue;
+            } else {
+                totalCalculatedLocationCnt++;
+                Log.i(TAG, "Calculated Location"
+                        + ", Latitude:" + calculatedLatLngAlt[0]
+                        + ", Longitude:" + calculatedLatLngAlt[1]
+                        + ", Altitude:" + calculatedLatLngAlt[2]);
+
+                double[] posVelUncertainties =
+                        mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
+
+                double horizontalPositionUncertaintyMeters =
+                        Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
+                                + posVelUncertainties[1] * posVelUncertainties[1]);
+
+                // Tolerate large offsets, when the device reports a large uncertainty - while also
+                // ensuring (here) that some locations are produced before the test ends
+                // with a reasonably low set of error estimates
+                if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
+                    someLocationsHaveLowPosUnc = true;
+                }
+
+                // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
+                // 68%ile offset (given likely correlated errors) - then this is scaled by
+                // initially 3 sigma to give a high enough tolerance to make the test tolerant
+                // enough of noise to pass reliably.  Floor adds additional robustness in case of
+                // small errors and small error estimates.
+                double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+                        horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
+                                + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
+                        + HORIZONTAL_OFFSET_FLOOR_METERS;
+
+                Location calculatedLocation = new Location("gps");
+                calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
+                calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
+                calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
+
+                double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
+
+                Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
+                        + ", Threshold: " + horizontalOffsetThresholdMeters);
+                assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
+                                + "those reported from Location Manager.  Offset = "
+                                + horizontalOffset + " meters. Threshold = "
+                                + horizontalOffsetThresholdMeters + " meters ",
+                        horizontalOffset < horizontalOffsetThresholdMeters);
+
+                //TODO: Check for the altitude offset
+
+                // This 2D velocity uncertainty is conservatively larger than speed uncertainty
+                // as it also contains the effect of bearing uncertainty at a constant speed
+                double horizontalVelocityUncertaintyMps =
+                        Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
+                                + posVelUncertainties[5] * posVelUncertainties[5]);
+                if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
+                    someLocationsHaveLowVelUnc = true;
+                }
+
+                // Assume 1m/s uncertainty from API, for this test, if not provided
+                float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
+                        ? locationFromApi.getSpeedAccuracyMetersPerSecond()
+                        : HORIZONTAL_OFFSET_FLOOR_MPS;
+
+                // Similar 3-standard deviation plus floor threshold as
+                // horizontalOffsetThresholdMeters above
+                double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+                        horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
+                                + speedUncFromApiMps * speedUncFromApiMps)
+                        + HORIZONTAL_OFFSET_FLOOR_MPS;
+
+                double[] calculatedVelocityEnuMps =
+                        mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
+                double calculatedHorizontalSpeedMps =
+                        Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
+                                + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
+
+                Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
+                        + ", Reported Speed: " + locationFromApi.getSpeed()
+                        + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
+                assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
+                                + " pseudoranges should be close to the speed ("
+                                + locationFromApi.getSpeed() + " m/s) reported from"
+                                + " Location Manager.",
+                        Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
+                                < horizontalSpeedOffsetThresholdMps);
+            }
+        }
+
+        assertTrue("Calculated Location Count should be greater than 0.",
+                totalCalculatedLocationCnt > 0);
+        assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
+                        + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
+                someLocationsHaveLowPosUnc);
+        assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
+                        + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
+                someLocationsHaveLowVelUnc);
     }
-
-    mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-    mMeasurementListener = new TestGnssMeasurementListener(TAG,
-                                     MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
-    mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-    boolean success = mLocationListener.await();
-
-    List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
-    assertTrue("Time elapsed without getting enough location fixes."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success && receivedLocationList.size() > 0);
-    Location locationFromApi = receivedLocationList.get(0);
-
-    // Since we are checking the eventCount later, there is no need to check the return value here.
-    mMeasurementListener.await();
-
-    List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-    int eventCount = events.size();
-    Log.i(TAG, "Number of Gps Event received = " + eventCount);
-    int gnssYearOfHardware = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
-    if (eventCount == 0 && gnssYearOfHardware < MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED) {
-      return;
-    }
-
-    Log.i(TAG, "This is a device from 2016 or later.");
-    assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
-        eventCount > 0);
-
-    PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
-        = new PseudorangePositionVelocityFromRealTimeEvents();
-    mPseudorangePositionFromRealTimeEvents.setReferencePosition(
-        (int) (locationFromApi.getLatitude() * 1E7),
-        (int) (locationFromApi.getLongitude() * 1E7),
-        (int) (locationFromApi.getAltitude()  * 1E7));
-
-    Log.i(TAG, "Location from Location Manager"
-        + ", Latitude:" + locationFromApi.getLatitude()
-        + ", Longitude:" + locationFromApi.getLongitude()
-        + ", Altitude:" + locationFromApi.getAltitude());
-
-
-    int totalCalculatedLocationCnt = 0;
-    for(GnssMeasurementsEvent event : events){
-      // In mMeasurementListener.getEvents() we already filtered out events, at this point every
-      // event will have at least 4 satellites in one constellation.
-      mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(event);
-      double[] calculatedLocation =
-          mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
-      // it will return NaN when there is no enough measurements to calculate the position
-      if (Double.isNaN(calculatedLocation[0])) {
-        continue;
-      }
-      else {
-        totalCalculatedLocationCnt ++;
-        Log.i(TAG, "Calculated Location"
-            + ", Latitude:" + calculatedLocation[0]
-            + ", Longitude:" + calculatedLocation[1]
-            + ", Altitude:" + calculatedLocation[2]);
-
-        assertTrue("Latitude should be close to " + locationFromApi.getLatitude(),
-            Math.abs(calculatedLocation[0] - locationFromApi.getLatitude())
-                < POSITION_THRESHOLD_IN_DEGREES);
-        assertTrue("Longitude should be close to" + locationFromApi.getLongitude(),
-            Math.abs(calculatedLocation[1] - locationFromApi.getLongitude())
-                < POSITION_THRESHOLD_IN_DEGREES);
-        //TODO: Check for the altitude and position uncertainty.
-      }
-    }
-    assertTrue("Calculated Location Count should be greater than 0.",
-        totalCalculatedLocationCnt > 0);
-  }
 }
diff --git a/tests/tests/location/src/android/location/cts/LocationTest.java b/tests/tests/location/src/android/location/cts/LocationTest.java
index 3e291cf..e7cadf2 100644
--- a/tests/tests/location/src/android/location/cts/LocationTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationTest.java
@@ -469,6 +469,8 @@
         parcel.setDataPosition(0);
         Location newLocation = Location.CREATOR.createFromParcel(parcel);
         assertTestLocation(newLocation);
+
+        parcel.recycle();
     }
 
     public void testSettingInjectorService() {
diff --git a/tests/tests/location/src/android/location/cts/MmsPduProvider.java b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
new file mode 100644
index 0000000..76d859e
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * A simple provider to send MMS PDU to platform MMS service
+ */
+public class MmsPduProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        // Not supported
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        // Not supported
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        // Not supported
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // Not supported
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        // Not supported
+        return 0;
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
+        File file = new File(getContext().getCacheDir(), uri.getPath());
+        int mode = (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
+                ParcelFileDescriptor.MODE_WRITE_ONLY
+                        |ParcelFileDescriptor.MODE_TRUNCATE
+                        |ParcelFileDescriptor.MODE_CREATE);
+        return ParcelFileDescriptor.open(file, mode);
+    }
+}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationManager.java b/tests/tests/location/src/android/location/cts/TestLocationManager.java
index 390d450..370999b 100644
--- a/tests/tests/location/src/android/location/cts/TestLocationManager.java
+++ b/tests/tests/location/src/android/location/cts/TestLocationManager.java
@@ -105,11 +105,11 @@
      *
      * @param locationListener location listener for request
      */
-    public void requestLocationUpdates(LocationListener locationListener) {
+    public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
         if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
             Log.i(TAG, "Request Location updates.");
             mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
-                    0 /* minTime*/,
+                    minTimeMsec,
                     0 /* minDistance */,
                     locationListener,
                     Looper.getMainLooper());
@@ -117,6 +117,15 @@
     }
 
     /**
+     * See {@code LocationManager#requestLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestLocationUpdates(LocationListener locationListener) {
+        requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
+    }
+
+    /**
      * See {@code LocationManager#requestNetworkLocationUpdates}.
      *
      * @param locationListener location listener for request
diff --git a/tests/tests/location2/Android.mk b/tests/tests/location2/Android.mk
index 281818c..d8b1ce4 100644
--- a/tests/tests/location2/Android.mk
+++ b/tests/tests/location2/Android.mk
@@ -24,7 +24,9 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/location2/AndroidTest.xml b/tests/tests/location2/AndroidTest.xml
index 3443098..412fd1d 100644
--- a/tests/tests/location2/AndroidTest.xml
+++ b/tests/tests/location2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Location test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="location" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index aab6b26..60e880f 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -51,9 +51,10 @@
     ctstestrunner \
     ctstestserver \
     junit \
-    legacy-android-test \
     ndkaudio \
-    testng
+    testng \
+    truth-prebuilt \
+    mockito-target-minus-junit4
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libaudio_jni \
@@ -71,11 +72,14 @@
 
 LOCAL_PACKAGE_NAME := CtsMediaTestCases
 
-# uncomment when b/13249737 is fixed
+# This test uses private APIs
 #LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
-LOCAL_JAVA_LIBRARIES += android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES += \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+    android.test.runner.stubs
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 482cd6a..44af14d 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -30,7 +30,7 @@
     <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
     <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
 
-    <application>
+    <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/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index f7d06b1..e9ecdd3 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Media test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="images-only" value="true" />
@@ -29,6 +30,8 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.media.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
         <!-- test-timeout unit is ms, value = 30 min -->
         <option name="test-timeout" value="1800000" />
         <option name="runtime-hint" value="4h" />
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
new file mode 100644
index 0000000..f5e2bda
--- /dev/null
+++ b/tests/tests/media/OWNERS
@@ -0,0 +1,5 @@
+rachad@google.com
+elaurent@google.com
+lajos@google.com
+marcone@google.com
+sungsoo@google.com
diff --git a/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp b/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp
index 24714a3..1677631 100644
--- a/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp
+++ b/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp
@@ -17,7 +17,7 @@
 #include <jni.h>
 #include <stdio.h>
 
-extern int register_android_media_cts_NativeClearKeySystemTest(JNIEnv*);
+extern int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv*);
 
 jint JNI_OnLoad(JavaVM *vm, void */*reserved*/) {
     JNIEnv *env = NULL;
@@ -26,7 +26,7 @@
         return JNI_ERR;
     }
 
-    if (register_android_media_cts_NativeClearKeySystemTest(env)) {
+    if (register_android_media_cts_NativeMediaDrmClearkeyTest(env)) {
         return JNI_ERR;
     }
 
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
index 9439ed1..4779d51 100644
--- a/tests/tests/media/libmediandkjni/native-media-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-media-jni.cpp
@@ -35,6 +35,7 @@
 #include "media/NdkMediaExtractor.h"
 #include "media/NdkMediaCodec.h"
 #include "media/NdkMediaCrypto.h"
+#include "media/NdkMediaDataSource.h"
 #include "media/NdkMediaFormat.h"
 #include "media/NdkMediaMuxer.h"
 
@@ -76,7 +77,58 @@
     }
 };
 
+struct FdDataSource {
 
+    FdDataSource(int fd, jlong offset, jlong size)
+        : mFd(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) {
+            return -1;
+        }
+        if (offset + ssize > mSize) {
+            ssize = mSize - offset;
+        }
+        if (lseek(mFd, mOffset + offset, SEEK_SET) < 0) {
+            return -1;
+        }
+        return read(mFd, data, ssize);
+    }
+
+    ssize_t getSize() {
+        return mSize;
+    }
+
+    void close() {
+        ::close(mFd);
+    }
+
+private:
+
+    int mFd;
+    off64_t mOffset;
+    int64_t mSize;
+
+};
+
+static ssize_t FdSourceReadAt(void *userdata, off64_t offset, void *data, size_t size) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    return src->readAt(offset, data, size);
+}
+
+static ssize_t FdSourceGetSize(void *userdata) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    return src->getSize();
+}
+
+static void FdSourceClose(void *userdata) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    src->close();
+}
 
 jobject testExtractor(AMediaExtractor *ex, JNIEnv *env) {
 
@@ -121,7 +173,8 @@
     uint8_t *buf = new uint8_t[bufsize];
     while(true) {
         int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
-        if (n < 0) {
+        ssize_t sampleSize = AMediaExtractor_getSampleSize(ex);
+        if (n < 0 || n != sampleSize) {
             break;
         }
         sizes.add(n);
@@ -219,14 +272,71 @@
     return sum;
 }
 
-extern "C" jobject Java_android_media_cts_NativeDecoderTest_getDecodedDataNative(JNIEnv *env,
-        jclass /*clazz*/, int fd, jlong offset, jlong size) {
-    ALOGV("getDecodedDataNative");
-
+extern "C" jlong Java_android_media_cts_NativeDecoderTest_getExtractorFileDurationNative(
+        JNIEnv * /*env*/, jclass /*clazz*/, int fd, jlong offset, jlong size)
+{
     AMediaExtractor *ex = AMediaExtractor_new();
     int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
     if (err != 0) {
         ALOGE("setDataSource error: %d", err);
+        AMediaExtractor_delete(ex);
+        return -1;
+    }
+    int64_t durationUs = -1;
+    AMediaFormat *format = AMediaExtractor_getFileFormat(ex);
+    AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
+    AMediaFormat_delete(format);
+    AMediaExtractor_delete(ex);
+    return durationUs;
+}
+
+extern "C" jlong Java_android_media_cts_NativeDecoderTest_getExtractorCachedDurationNative(
+        JNIEnv * env, jclass /*clazz*/, jstring jpath)
+{
+    AMediaExtractor *ex = AMediaExtractor_new();
+
+    const char *tmp = env->GetStringUTFChars(jpath, NULL);
+    if (tmp == NULL) {  // Out of memory
+        AMediaExtractor_delete(ex);
+        return -1;
+    }
+
+    int err = AMediaExtractor_setDataSource(ex, tmp);
+
+    env->ReleaseStringUTFChars(jpath, tmp);
+
+    if (err != 0) {
+        ALOGE("setDataSource error: %d", err);
+        AMediaExtractor_delete(ex);
+        return -1;
+    }
+
+    int64_t cachedDurationUs = AMediaExtractor_getCachedDuration(ex);
+    AMediaExtractor_delete(ex);
+    return cachedDurationUs;
+
+}
+
+extern "C" jobject Java_android_media_cts_NativeDecoderTest_getDecodedDataNative(JNIEnv *env,
+        jclass /*clazz*/, int fd, jlong offset, jlong size, jboolean wrapFd) {
+    ALOGV("getDecodedDataNative");
+
+    FdDataSource fdSrc(fd, offset, size);
+    AMediaExtractor *ex = AMediaExtractor_new();
+    AMediaDataSource *ndkSrc = AMediaDataSource_new();
+
+    int err;
+    if (wrapFd) {
+        AMediaDataSource_setUserdata(ndkSrc, &fdSrc);
+        AMediaDataSource_setReadAt(ndkSrc, FdSourceReadAt);
+        AMediaDataSource_setGetSize(ndkSrc, FdSourceGetSize);
+        AMediaDataSource_setClose(ndkSrc, FdSourceClose);
+        err = AMediaExtractor_setDataSourceCustom(ex, ndkSrc);
+    } else {
+        err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+    }
+    if (err != 0) {
+        ALOGE("setDataSource error: %d", err);
         return NULL;
     }
 
@@ -367,6 +477,7 @@
     delete[] format;
     delete[] codec;
     AMediaExtractor_delete(ex);
+    AMediaDataSource_delete(ndkSrc);
     return ret;
 }
 
diff --git a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
index cff5c18..cc4387b 100644
--- a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
@@ -64,6 +64,8 @@
     0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
 };
 
+// The test content is not packaged with clearkey UUID,
+// we have to use a canned clearkey pssh for the test.
 static const uint8_t kClearkeyPssh[] = {
     // BMFF box header (4 bytes size + 'pssh')
     0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
@@ -126,7 +128,7 @@
     return juuid;
 }
 
-extern "C" jboolean Java_android_media_cts_NativeClearKeySystemTest_isCryptoSchemeSupportedNative(
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(
     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
 
     if (NULL == uuid) {
@@ -158,7 +160,7 @@
         playbackParams, gFieldIds.videoUrl));
 }
 
-extern "C" jboolean Java_android_media_cts_NativeClearKeySystemTest_testGetPropertyStringNative(
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(
     JNIEnv* env, jclass clazz, jbyteArray uuid,
     jstring name, jobject outValue) {
 
@@ -203,7 +205,7 @@
     return JNI_TRUE;
 }
 
-extern "C" jboolean Java_android_media_cts_NativeClearKeySystemTest__testPsshNative(
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(
     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
 
     if (NULL == uuid || NULL == videoUrl) {
@@ -541,7 +543,7 @@
     jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), status);
 }
 
-extern "C" jboolean Java_android_media_cts_NativeClearKeySystemTest_testClearKeyPlaybackNative(
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(
     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
     if (NULL == uuid || NULL == playbackParams) {
         jniThrowException(env, "java/lang/NullPointerException",
@@ -646,7 +648,7 @@
     return JNI_TRUE;
 }
 
-extern "C" jboolean Java_android_media_cts_NativeClearKeySystemTest_testQueryKeyStatusNative(
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(
     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
 
     if (NULL == uuid) {
@@ -741,30 +743,30 @@
 
 static JNINativeMethod gMethods[] = {
     { "isCryptoSchemeSupportedNative", "([B)Z",
-            (void *)Java_android_media_cts_NativeClearKeySystemTest_isCryptoSchemeSupportedNative },
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
 
     { "testClearKeyPlaybackNative",
-            "([BLandroid/media/cts/NativeClearKeySystemTest$PlaybackParams;)Z",
-            (void *)Java_android_media_cts_NativeClearKeySystemTest_testClearKeyPlaybackNative },
+            "([BLandroid/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative },
 
     { "testGetPropertyStringNative",
             "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
-            (void *)Java_android_media_cts_NativeClearKeySystemTest_testGetPropertyStringNative },
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative },
 
     { "testPsshNative", "([BLjava/lang/String;)Z",
-            (void *)Java_android_media_cts_NativeClearKeySystemTest__testPsshNative },
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative },
 
     { "testQueryKeyStatusNative", "([B)Z",
-            (void *)Java_android_media_cts_NativeClearKeySystemTest_testQueryKeyStatusNative },
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative },
 };
 
-int register_android_media_cts_NativeClearKeySystemTest(JNIEnv* env) {
+int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
     jint result = JNI_ERR;
     jclass testClass =
-        env->FindClass("android/media/cts/NativeClearKeySystemTest");
+        env->FindClass("android/media/cts/NativeMediaDrmClearkeyTest");
     if (testClass) {
         jclass playbackParamsClass = env->FindClass(
-            "android/media/cts/NativeClearKeySystemTest$PlaybackParams");
+            "android/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams");
         if (playbackParamsClass) {
             jclass surfaceClass =
                 env->FindClass("android/view/Surface");
@@ -785,7 +787,7 @@
         }
 
     } else {
-        ALOGE("NativeClearKeySystemTest class not found");
+        ALOGE("NativeMediaDrmClearkeyTest class not found");
     }
 
     result = env->RegisterNatives(testClass, gMethods,
diff --git a/tests/tests/media/libndkaudio/Android.mk b/tests/tests/media/libndkaudio/Android.mk
index 29e2118..b547589 100644
--- a/tests/tests/media/libndkaudio/Android.mk
+++ b/tests/tests/media/libndkaudio/Android.mk
@@ -56,4 +56,6 @@
 
 LOCAL_CERTIFICATE := platform
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/media/res/raw/testmp3_4.mp3 b/tests/tests/media/res/raw/testmp3_4.mp3
new file mode 100755
index 0000000..2098ebd
--- /dev/null
+++ b/tests/tests/media/res/raw/testmp3_4.mp3
Binary files differ
diff --git a/tests/tests/media/res/values/strings.xml b/tests/tests/media/res/values/strings.xml
index b01b8ec..018ab70 100644
--- a/tests/tests/media/res/values/strings.xml
+++ b/tests/tests/media/res/values/strings.xml
@@ -16,4 +16,5 @@
 <resources>
     <string name="test_user_route_name">User route\'s name for test</string>
     <string name="test_route_category_name">Route category\'s name for test</string>
+    <string name="test_localizable_title">Translatable Ringtone Title</string>
 </resources>
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/AudioFocusTest.java b/tests/tests/media/src/android/media/cts/AudioFocusTest.java
index afc272e..e360ecb 100644
--- a/tests/tests/media/src/android/media/cts/AudioFocusTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioFocusTest.java
@@ -244,8 +244,15 @@
             assertEquals("1st abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
             focusRequests[1] = null;
             Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
-                    focusListeners[0].getFocusChangeAndReset());
+            // when focus was lost because it was requested with GAIN, focus is not given back
+            if (gainType != AudioManager.AUDIOFOCUS_GAIN) {
+                assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
+                        focusListeners[0].getFocusChangeAndReset());
+            } else {
+                // verify there was no focus change because focus user 0 was kicked out of stack
+                assertEquals("Focus change was dispatched", AudioManager.AUDIOFOCUS_NONE,
+                        focusListeners[0].getFocusChangeAndReset());
+            }
             res = am.abandonAudioFocusRequest(focusRequests[0]);
             assertEquals("2nd abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
             focusRequests[0] = null;
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index ab7e4b6..59e2361 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -26,8 +26,8 @@
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static android.media.AudioManager.STREAM_MUSIC;
 import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+import static android.media.AudioManager.STREAM_MUSIC;
 import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
 import static android.media.AudioManager.VIBRATE_SETTING_OFF;
 import static android.media.AudioManager.VIBRATE_SETTING_ON;
@@ -38,12 +38,16 @@
 
 import android.app.ActivityManager;
 import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.res.Resources;
-import android.media.AudioManager;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.media.AudioSystem;
+import android.content.res.Resources;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.media.MicrophoneInfo;
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.provider.Settings.System;
@@ -52,9 +56,11 @@
 import android.view.SoundEffectConstants;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class AudioManagerTest extends InstrumentationTestCase {
+    private final static String TAG = "AudioManagerTest";
 
     private final static int MP3_TO_PLAY = R.raw.testmp3;
     private final static long TIME_TO_PLAY = 2000;
@@ -144,6 +150,73 @@
         assertFalse(mAudioManager.isMicrophoneMute());
     }
 
+    public void testMicrophoneMuteIntent() throws Exception {
+        final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver();
+        final boolean initialMicMute = mAudioManager.isMicrophoneMute();
+        try {
+            mContext.registerReceiver(receiver,
+                    new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+            // change the mic mute state
+            mAudioManager.setMicrophoneMute(!initialMicMute);
+            // verify a change was reported
+            final boolean intentFired = receiver.waitForMicMuteChanged(500/*ms*/);
+            assertTrue("ACTION_MICROPHONE_MUTE_CHANGED wasn't fired", intentFired);
+            // verify the mic mute state is expected
+            final boolean newMicMute = mAudioManager.isMicrophoneMute();
+            assertTrue("new mic mute state not as expected (" + !initialMicMute + ")",
+                    newMicMute == !initialMicMute);
+        } finally {
+            mContext.unregisterReceiver(receiver);
+            mAudioManager.setMicrophoneMute(initialMicMute);
+        }
+    }
+
+    // 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
+        private boolean mIntentReceived = false;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                mIntentReceived = true;
+                mLock.safeNotify();
+            }
+        }
+
+        public boolean waitForMicMuteChanged(long timeOutMs) {
+            synchronized (mLock) {
+                try {
+                    mLock.safeWait(timeOutMs);
+                } catch (InterruptedException e) { }
+                return mIntentReceived;
+            }
+        }
+    }
+
     public void testSoundEffects() throws Exception {
         Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
 
@@ -176,6 +249,29 @@
         mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
     }
 
+    public void testCheckingZenModeBlockDoesNotRequireNotificationPolicyAccess() throws Exception {
+        try {
+            // set zen mode to priority only, so playSoundEffect will check notification policy
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    true);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
+
+            // take away write-notification policy access from the package
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    false);
+
+            // playSoundEffect should NOT throw a security exception; all apps have read-access
+            mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
+        } finally {
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    true);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    false);
+        }
+    }
+
     public void testMusicActive() throws Exception {
         if (mAudioManager.isMusicActive()) {
             return;
@@ -691,6 +787,13 @@
         }
     }
 
+    public void testSetVoiceCallVolumeToZeroPermission() {
+        // Verify that only apps with MODIFY_PHONE_STATE can set VOICE_CALL_STREAM to 0
+        mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 0, 0);
+        assertTrue("MODIFY_PHONE_STATE is required in order to set voice call volume to 0",
+                    mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL) != 0);
+    }
+
     public void testMuteFixedVolume() throws Exception {
         int[] streams = {
                 AudioManager.STREAM_VOICE_CALL,
@@ -802,12 +905,13 @@
 
         int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
                 System.MUTE_STREAMS_AFFECTED,
-                // Same defaults as in AudioService. Should be kept in
-                // sync.
-                ((1 << AudioManager.STREAM_MUSIC) |
-                        (1 << AudioManager.STREAM_RING) |
-                        (1 << AudioManager.STREAM_NOTIFICATION) |
-                        (1 << AudioManager.STREAM_SYSTEM)));
+                // same defaults as in AudioService. Should be kept in sync.
+                 (1 << STREAM_MUSIC) |
+                         (1 << AudioManager.STREAM_RING) |
+                         (1 << AudioManager.STREAM_NOTIFICATION) |
+                         (1 << AudioManager.STREAM_SYSTEM) |
+                         (1 << AudioManager.STREAM_VOICE_CALL));
+
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
         // This ensures we're out of vibrate or silent modes.
@@ -837,34 +941,41 @@
     }
 
     private void testStreamMuting(int stream) {
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-        assertTrue("Muting stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+        // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
+        if (stream == AudioManager.STREAM_VOICE_CALL) {
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertFalse("Muting voice call stream (" + stream + ") should require "
+                            + "MODIFY_PHONE_STATE.", mAudioManager.isStreamMute(stream));
+        } else {
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertTrue("Muting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
-        assertFalse("Unmuting stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+            assertFalse("Unmuting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-        assertTrue("Toggling mute on stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertTrue("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-        assertFalse("Toggling mute on stream " + stream + " failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertFalse("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
 
-        mAudioManager.setStreamMute(stream, true);
-        assertTrue("Muting stream " + stream + " using setStreamMute failed",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.setStreamMute(stream, true);
+            assertTrue("Muting stream " + stream + " using setStreamMute failed",
+                    mAudioManager.isStreamMute(stream));
 
-        // mute it three more times to verify the ref counting is gone.
-        mAudioManager.setStreamMute(stream, true);
-        mAudioManager.setStreamMute(stream, true);
-        mAudioManager.setStreamMute(stream, true);
+            // mute it three more times to verify the ref counting is gone.
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
 
-        mAudioManager.setStreamMute(stream, false);
-        assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
-                mAudioManager.isStreamMute(stream));
+            mAudioManager.setStreamMute(stream, false);
+            assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
+                    mAudioManager.isStreamMute(stream));
+        }
     }
 
     public void testSetInvalidRingerMode() {
@@ -962,11 +1073,174 @@
         }
     }
 
+    public void testSetStreamVolumeInPriorityOnlyMode() throws Exception {
+        if (mSkipRingerTests || !mSupportNotificationPolicyAccess) {
+            return;
+        }
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        NotificationManager.Policy previousPolicy = mNm.getNotificationPolicy();
+
+        try {
+            // disallow all sounds in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0,
+                    0 , 0));
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            // saved volumes before trying to adjust volume
+            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+
+            // check new volumes, cannot adjust music or alarm
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0);
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
+
+            // can set stream volume (this could trigger exiting of dnd mode)
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
+            assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            mNm.setNotificationPolicy(previousPolicy);
+        }
+    }
+
+    public void testAdjustVolumeInPriorityOnly() throws Exception {
+        if (mSkipRingerTests || !mSupportNotificationPolicyAccess) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        NotificationManager.Policy previousPolicy = mNm.getNotificationPolicy();
+        try {
+            // disallow all sounds in priority only, turn on priority only DND
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            // save volumes before trying to adjust volume
+            int ringVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
+            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+
+            // Ensure - we should not be able to adjust stream volumes (ie from volume buttons)
+            mAudioManager.adjustStreamVolume(
+                    AudioManager.STREAM_RING, AudioManager.ADJUST_RAISE, 0);
+            assertEquals(ringVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+
+            mAudioManager.adjustStreamVolume(
+                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+            mAudioManager.adjustStreamVolume(
+                    AudioManager.STREAM_ALARM, AudioManager.ADJUST_RAISE, 0);
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
+
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            mNm.setNotificationPolicy(previousPolicy);
+        }
+    }
+
     public void testAdjustVolumeWithIllegalDirection() throws Exception {
         // Call the method with illegal direction. System should not reboot.
         mAudioManager.adjustVolume(37, 0);
     }
 
+    private final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL,
+            AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC,
+            AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
+            AudioManager.STREAM_DTMF,  AudioManager.STREAM_ACCESSIBILITY };
+
+    public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
+        Exception ex = null;
+        // invalid stream type
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(-100 /*streamType*/, 0,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid stream type", ex);
+        assertEquals("Wrong exception thrown for invalid stream type",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid volume index
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, -101 /*volume*/,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid volume index", ex);
+        assertEquals("Wrong exception thrown for invalid volume index",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid out of range volume index
+        ex = null;
+        try {
+            final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, maxVol + 1,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid out of range volume index", ex);
+        assertEquals("Wrong exception thrown for invalid out of range volume index",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid device type
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+                    -102 /*deviceType*/);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid device type", ex);
+        assertEquals("Wrong exception thrown for invalid device type",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid input device type
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+                    AudioDeviceInfo.TYPE_BUILTIN_MIC);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid input device type", ex);
+        assertEquals("Wrong exception thrown for invalid input device type",
+                ex.getClass(), IllegalArgumentException.class);
+    }
+
+    public void testGetStreamVolumeDb() throws Exception {
+        for (int streamType : PUBLIC_STREAM_TYPES) {
+            // verify mininum index is strictly inferior to maximum index
+            final int minIndex = mAudioManager.getStreamMinVolume(streamType);
+            final int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
+            assertTrue("Min vol index (" + minIndex + ") for stream " + streamType + " not inferior"
+                    + " to max vol index (" + maxIndex + ")", minIndex <= maxIndex);
+            float prevGain = Float.NEGATIVE_INFINITY;
+            // verify gain increases with the volume indices
+            for (int idx = minIndex ; idx <= maxIndex ; idx++) {
+                float gain = mAudioManager.getStreamVolumeDb(streamType, idx,
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+                assertTrue("Non-monotonically increasing gain at index " + idx + " for stream"
+                        + streamType, prevGain <= gain);
+                prevGain = gain;
+            }
+        }
+    }
+
     public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
         // Call the method with illegal direction. System should not reboot.
         mAudioManager.adjustSuggestedStreamVolume(37, AudioManager.STREAM_MUSIC, 0);
@@ -975,6 +1249,36 @@
         mAudioManager.adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, 66747, 0);
     }
 
+    public void testGetMicrophones() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE)) {
+            return;
+        }
+        List<MicrophoneInfo> microphones = mAudioManager.getMicrophones();
+        assertTrue(microphones.size() > 0);
+        for (int i = 0; i < microphones.size(); i++) {
+            MicrophoneInfo microphone = microphones.get(i);
+            Log.i(TAG, "deviceId:" + microphone.getDescription());
+            Log.i(TAG, "portId:" + microphone.getId());
+            Log.i(TAG, "type:" + microphone.getType());
+            Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+            Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+                    + " index:" + microphone.getIndexInTheGroup());
+            MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+            Log.i(TAG, "position:" + position.x + " " + position.y + " " + position.z);
+            MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+            Log.i(TAG, "orientation:" + orientation.x + " "
+                    + orientation.y + " " + orientation.z);
+            Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+            Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+            Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+            Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+            Log.i(TAG, "min spl:" + microphone.getMinSpl());
+            Log.i(TAG, "directionality:" + microphone.getDirectionality());
+            Log.i(TAG, "--------------");
+        }
+    }
+
     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/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index e5f9385..f3c17cf 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -145,11 +145,11 @@
         final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
         final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
         try {
-            Integer uid = (Integer) getClientUidMethod.invoke(config, null);
+            Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
             assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
-            Integer pid = (Integer) getClientPidMethod.invoke(config, null);
+            Integer pid = (Integer) getClientPidMethod.invoke(config, (Object[]) null);
             assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
-            Integer type = (Integer) getPlayerTypeMethod.invoke(config, null);
+            Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null);
             assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
         } catch (Exception e) {
             fail("Exception thrown during reflection on config privileged fields"+ e);
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
new file mode 100644
index 0000000..6b9da8a
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.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 android.media.cts;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpActiveChangedListener;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.concurrent.TimeUnit;
+
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for media framework behaviors related to app ops.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordAppOpTest {
+    private static final long APP_OP_CHANGE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
+
+    @Test
+    public void testRecordAppOps() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        final String packageName = getContext().getPackageName();
+        final int uid = Process.myUid();
+
+        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        final OnOpActiveChangedListener listener = mock(OnOpActiveChangedListener.class);
+
+        AudioRecord recorder = null;
+        try {
+            // Setup a recorder
+            final AudioRecord candidateRecorder = new AudioRecord.Builder()
+                    .setAudioSource(MediaRecorder.AudioSource.MIC)
+                    .setBufferSizeInBytes(1024)
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(8000)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .build())
+                    .build();
+
+            // The app op should be reported as not started
+            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+                    uid, packageName)).isFalse();
+
+            // Start watching for app op active
+            appOpsManager.startWatchingActive(new int[] {OP_RECORD_AUDIO}, listener);
+
+            // Start recording
+            candidateRecorder.startRecording();
+            recorder = candidateRecorder;
+
+            // The app op should start
+            verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                    .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+                    eq(uid), eq(packageName), eq(true));
+
+            // The app op should be reported as started
+            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+                    uid, packageName)).isTrue();
+
+
+            // Start with a clean slate
+            Mockito.reset(listener);
+
+            // Stop recording
+            recorder.stop();
+            recorder.release();
+            recorder = null;
+
+            // The app op should stop
+            verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                    .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+                    eq(uid), eq(packageName), eq(false));
+
+            // The app op should be reported as not started
+            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+                    uid, packageName)).isFalse();
+        } finally {
+            if (recorder != null) {
+                recorder.stop();
+                recorder.release();
+            }
+
+            appOpsManager.stopWatchingActive(listener);
+        }
+    }
+
+    private static boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 8fd1b7a..63b3356 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -20,28 +20,46 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.AudioRecord;
 import android.media.AudioRecord.OnRecordPositionUpdateListener;
 import android.media.AudioTimestamp;
 import android.media.AudioTrack;
 import android.media.MediaRecorder;
 import android.media.MediaSyncEvent;
+import android.media.MicrophoneInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import com.android.compatibility.common.util.CtsAndroidTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.testng.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 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.SystemUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
 import java.util.ArrayList;
+import java.util.List;
 
-public class AudioRecordTest extends CtsAndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordTest {
     private final static String TAG = "AudioRecordTest";
     private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
     private AudioRecord mAudioRecord;
@@ -61,10 +79,8 @@
         }
     };
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         if (!hasMicrophone()) {
             return;
         }
@@ -100,13 +116,12 @@
         assertNotNull(mAudioRecord);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (hasMicrophone()) {
             mAudioRecord.release();
             mLooper.quit();
         }
-        super.tearDown();
     }
 
     private void reset() {
@@ -115,6 +130,7 @@
         mIsHandleMessageCalled = false;
     }
 
+    @Test
     public void testAudioRecordProperties() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -133,6 +149,7 @@
         assertTrue(bufferSize > 0);
     }
 
+    @Test
     public void testAudioRecordOP() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -235,6 +252,7 @@
         assertEquals(AudioRecord.STATE_UNINITIALIZED, mAudioRecord.getState());
     }
 
+    @Test
     public void testAudioRecordResamplerMono8Bit() throws Exception {
         doTest("resampler_mono_8bit", true /*localRecord*/, false /*customHandler*/,
                 1 /*periodsPerSecond*/, 1 /*markerPeriodsPerSecond*/,
@@ -243,6 +261,7 @@
                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT);
     }
 
+    @Test
     public void testAudioRecordResamplerStereo8Bit() throws Exception {
         doTest("resampler_stereo_8bit", true /*localRecord*/, false /*customHandler*/,
                 0 /*periodsPerSecond*/, 3 /*markerPeriodsPerSecond*/,
@@ -251,6 +270,17 @@
                 AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
     }
 
+    @Presubmit
+    @Test
+    public void testAudioRecordLocalMono16BitShort() throws Exception {
+        doTest("local_mono_16bit_short", true /*localRecord*/, false /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, true /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 500 /*TEST_TIME_MS*/);
+    }
+
+    @Test
     public void testAudioRecordLocalMono16Bit() throws Exception {
         doTest("local_mono_16bit", true /*localRecord*/, false /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
@@ -259,6 +289,7 @@
                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
     }
 
+    @Test
     public void testAudioRecordStereo16Bit() throws Exception {
         doTest("stereo_16bit", false /*localRecord*/, false /*customHandler*/,
                 2 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
@@ -267,6 +298,7 @@
                 AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
     }
 
+    @Test
     public void testAudioRecordMonoFloat() throws Exception {
         doTest("mono_float", false /*localRecord*/, true /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
@@ -275,6 +307,7 @@
                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT);
     }
 
+    @Test
     public void testAudioRecordLocalNonblockingStereoFloat() throws Exception {
         doTest("local_nonblocking_stereo_float", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
@@ -284,6 +317,7 @@
     }
 
     // Audit modes work best with non-blocking mode
+    @Test
     public void testAudioRecordAuditByteBufferResamplerStereoFloat() throws Exception {
         if (isLowRamDevice()) {
             return; // skip. FIXME: reenable when AF memory allocation is updated.
@@ -296,6 +330,7 @@
                 AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
     }
 
+    @Test
     public void testAudioRecordAuditChannelIndexMonoFloat() throws Exception {
         doTest("audit_channel_index_mono_float", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
@@ -306,6 +341,7 @@
 
     // Audit buffers can run out of space with high sample rate,
     // so keep the channels and pcm encoding low
+    @Test
     public void testAudioRecordAuditChannelIndex2() throws Exception {
         if (isLowRamDevice()) {
             return; // skip. FIXME: reenable when AF memory allocation is updated.
@@ -320,6 +356,7 @@
 
     // Audit buffers can run out of space with high numbers of channels,
     // so keep the sample rate low.
+    @Test
     public void testAudioRecordAuditChannelIndex5() throws Exception {
         doTest("audit_channel_index_5", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
@@ -331,6 +368,7 @@
 
     // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
     // an empty Builder matches the documentation / expected values
+    @Test
     public void testAudioRecordBuilderDefault() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -360,6 +398,7 @@
 
     // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
     // an incomplete AudioFormat matches the documentation / expected values
+    @Test
     public void testAudioRecordBuilderPartialFormat() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -391,6 +430,7 @@
 
     // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord matches
     // the parameters used in the builder
+    @Test
     public void testAudioRecordBuilderParams() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -432,6 +472,7 @@
     }
 
     // Test AudioRecord to ensure we can build after a failure.
+    @Test
     public void testAudioRecordBufferSize() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -467,6 +508,7 @@
         assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
     }
 
+    @Test
     public void testTimestamp() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -562,6 +604,69 @@
         }
     }
 
+    @Test
+    public void testRecordNoDataForIdleUids() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord recorder = null;
+
+        // We will record audio for 20 sec from active and idle state expecting
+        // the recording from active state to have data while from idle silence.
+        try {
+            // Setup a recorder
+            final AudioRecord candidateRecorder = new AudioRecord.Builder()
+                    .setAudioSource(MediaRecorder.AudioSource.MIC)
+                    .setBufferSizeInBytes(1024)
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(8000)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .build())
+                    .build();
+
+            // Unleash it :P
+            candidateRecorder.startRecording();
+            recorder = candidateRecorder;
+
+            final int sampleCount = AudioHelper.frameCountFromMsec(6000,
+                    candidateRecorder.getFormat()) * candidateRecorder.getFormat()
+                    .getChannelCount();
+            final ShortBuffer buffer = ShortBuffer.allocate(sampleCount);
+
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read non-empty bytes
+            assertSilence(buffer, false);
+
+            // Start clean
+            buffer.clear();
+            // Force idle the package
+            makeMyPackageIdle();
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read empty bytes
+            assertSilence(buffer, true);
+
+            // Start clean
+            buffer.clear();
+            // Reset to active
+            makeMyPackageActive();
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read non-empty bytes
+            assertSilence(buffer, false);
+        } finally {
+            if (recorder != null) {
+                recorder.stop();
+                recorder.release();
+            }
+            makeMyPackageActive();
+        }
+    }
+
+    @Test
     public void testSynchronizedRecord() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -700,6 +805,71 @@
         }
     }
 
+    @Test
+    public void testVoiceCallAudioSourcePermissions() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        // Make sure that VOICE_CALL, VOICE_DOWNLINK and VOICE_UPLINK audio sources cannot
+        // be used by apps that don't have the CAPTURE_AUDIO_OUTPUT permissions
+        final int[] voiceCallAudioSources = new int [] {MediaRecorder.AudioSource.VOICE_CALL,
+            MediaRecorder.AudioSource.VOICE_DOWNLINK,
+            MediaRecorder.AudioSource.VOICE_UPLINK};
+
+        for (int source : voiceCallAudioSources) {
+            // AudioRecord.Builder should fail when trying to use
+            // one of the voice call audio sources.
+            assertThrows(UnsupportedOperationException.class,
+                            () -> {
+                                new AudioRecord.Builder()
+                                 .setAudioSource(source)
+                                 .setAudioFormat(new AudioFormat.Builder()
+                                         .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                         .setSampleRate(8000)
+                                         .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                                         .build())
+                                 .build(); });
+        }
+    }
+
+    private void printMicrophoneInfo(MicrophoneInfo microphone) {
+        Log.i(TAG, "deviceId:" + microphone.getDescription());
+        Log.i(TAG, "portId:" + microphone.getId());
+        Log.i(TAG, "type:" + microphone.getType());
+        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+            + " index:" + microphone.getIndexInTheGroup());
+        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
+        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
+        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+        Log.i(TAG, "min spl:" + microphone.getMinSpl());
+        Log.i(TAG, "directionality:" + microphone.getDirectionality());
+        Log.i(TAG, "******");
+    }
+
+    @Test
+    public void testGetActiveMicrophones() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        mAudioRecord.startRecording();
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+        }
+        List<MicrophoneInfo> activeMicrophones = mAudioRecord.getActiveMicrophones();
+        assertTrue(activeMicrophones.size() > 0);
+        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
+            printMicrophoneInfo(activeMicrophone);
+        }
+    }
+
     private AudioRecord createAudioRecord(
             int audioSource, int sampleRateInHz,
             int channelConfig, int audioFormat, int bufferSizeInBytes,
@@ -743,11 +913,21 @@
             boolean useByteBuffer, boolean blocking,
             final boolean auditRecording, final boolean isChannelIndex,
             final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
+        final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
+        doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
+                useByteBuffer, blocking, auditRecording, isChannelIndex,
+                TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
+    }
+    private void doTest(String reportName, boolean localRecord, boolean customHandler,
+            int periodsPerSecond, int markerPeriodsPerSecond,
+            boolean useByteBuffer, boolean blocking,
+            final boolean auditRecording, final boolean isChannelIndex,
+            final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT, final int TEST_TIME_MS)
+            throws Exception {
         if (!hasMicrophone()) {
             return;
         }
         // audit recording plays back recorded audio, so use longer test timing
-        final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
         final int TEST_SOURCE = MediaRecorder.AudioSource.DEFAULT;
         mIsHandleMessageCalled = false;
 
@@ -1148,7 +1328,7 @@
                 ResultUnit.MS);
         log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.submit(getInstrumentation());
+        log.submit(InstrumentationRegistry.getInstrumentation());
     }
 
     private class MockOnRecordPositionUpdateListener
@@ -1267,4 +1447,61 @@
     private void printTimestamp(String s, AudioTimestamp ats) {
         Log.d(TAG, s + ":  pos: " + ats.framePosition + "  time: " + ats.nanoTime);
     }
+
+    private static void readDataTimed(AudioRecord recorder, long durationMillis,
+            ShortBuffer out) throws IOException {
+        final short[] buffer = new short[1024];
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        final long stopTimeMillis = startTimeMillis + durationMillis;
+        while (SystemClock.uptimeMillis() < stopTimeMillis) {
+            final int readCount = recorder.read(buffer, 0, buffer.length);
+            if (readCount <= 0) {
+                return;
+            }
+            out.put(buffer, 0, readCount);
+        }
+    }
+
+    private static void assertSilence(ShortBuffer buffer, boolean silence) {
+        // Always need some bytes read
+        assertTrue("Buffer should have some data", buffer.position() > 0);
+
+        // It is possible that the transition from empty to non empty bytes
+        // happened in the middle of the read data due to the async nature of
+        // the system. Therefore, we look for the transitions from non-empty
+        // to empty and from empty to non-empty values for robustness.
+        int totalSilenceCount = 0;
+        final int valueCount = buffer.position();
+        for (int i = valueCount - 1; i >= 0; i--) {
+            final short value = buffer.get(i);
+            if (value == 0) {
+                totalSilenceCount++;
+            }
+        }
+        if (silence) {
+            if (totalSilenceCount < valueCount / 2) {
+                fail("Recording was not silenced while UID idle");
+            }
+        } else {
+            if (totalSilenceCount > valueCount / 2) {
+                fail("Recording was silenced while UID active");
+            }
+        }
+    }
+
+    private static void makeMyPackageActive() throws IOException {
+        final String command = "cmd media.audio_policy reset-uid-state "
+                +  InstrumentationRegistry.getTargetContext().getPackageName();
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static void makeMyPackageIdle() throws IOException {
+        final String command = "cmd media.audio_policy set-uid-state "
+                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle";
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
index 068087d..eb7296f 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
@@ -319,9 +319,9 @@
         try {
             final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
             final Method getClientPackageName = confClass.getDeclaredMethod("getClientPackageName");
-            Integer uid = (Integer) getClientUidMethod.invoke(config, null);
+            Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
             assertEquals("client uid isn't protected", -1 /*expected*/, uid.intValue());
-            String name = (String) getClientPackageName.invoke(config, null);
+            String name = (String) getClientPackageName.invoke(config, (Object[]) null);
             assertNotNull("client package name is null", name);
             assertEquals("client package name isn't protected", 0 /*expected*/, name.length());
         } catch (Exception e) {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 3d853d4..c83b504 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -25,6 +25,7 @@
 import android.media.AudioTimestamp;
 import android.media.AudioTrack;
 import android.media.PlaybackParams;
+import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
 import com.android.compatibility.common.util.CtsAndroidTestCase;
@@ -1461,6 +1462,31 @@
         track.release();
     }
 
+    @Presubmit
+    public void testPlayStaticDataShort() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testPlayStaticDataShort";
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final double TEST_SWEEP = 100;
+        final int TEST_LOOPS = 1;
+        final double TEST_FREQUENCY = 400;
+        final long NO_WAIT = 0;
+        final double TEST_LOOP_DURATION = 0.25;
+
+        playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                TEST_LOOPS, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF, NO_WAIT, TEST_LOOP_DURATION);
+
+    }
+
     public void testPlayStaticData() throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
@@ -1487,90 +1513,118 @@
         final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
         final double TEST_SWEEP = 100;
         final int TEST_LOOPS = 1;
+        final double TEST_LOOP_DURATION=1;
 
         for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
             double frequency = 400; // frequency changes for each test
             for (int TEST_SR : TEST_SR_ARRAY) {
                 for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    // -------- initialization --------------
-                    final int seconds = 1;
-                    final int channelCount = Integer.bitCount(TEST_CONF);
-                    final int bufferFrames = seconds * TEST_SR;
-                    final int bufferSamples = bufferFrames * channelCount;
-                    final int bufferSize = bufferSamples
-                            * AudioFormat.getBytesPerSample(TEST_FORMAT);
-                    final double testFrequency = frequency / channelCount;
-                    final long MILLISECONDS_PER_SECOND = 1000;
-                    AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR,
-                            TEST_CONF, TEST_FORMAT, bufferSize, TEST_MODE);
-                    assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+                    playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                            TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_MSEC, TEST_LOOP_DURATION);
 
-                    // -------- test --------------
-
-                    // test setLoopPoints and setPosition can be called here.
-                    assertEquals(TEST_NAME,
-                            android.media.AudioTrack.SUCCESS,
-                            track.setPlaybackHeadPosition(bufferFrames/2));
-                    assertEquals(TEST_NAME,
-                            android.media.AudioTrack.SUCCESS,
-                            track.setLoopPoints(
-                                    0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
-                    // only need to write once to the static track
-                    switch (TEST_FORMAT) {
-                    case AudioFormat.ENCODING_PCM_8BIT: {
-                        byte data[] = AudioHelper.createSoundDataInByteArray(
-                                bufferSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        assertEquals(TEST_NAME,
-                                bufferSamples,
-                                track.write(data, 0 /*offsetInBytes*/, data.length));
-                        } break;
-                    case AudioFormat.ENCODING_PCM_16BIT: {
-                        short data[] = AudioHelper.createSoundDataInShortArray(
-                                bufferSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        assertEquals(TEST_NAME,
-                                bufferSamples,
-                                track.write(data, 0 /*offsetInBytes*/, data.length));
-                        } break;
-                    case AudioFormat.ENCODING_PCM_FLOAT: {
-                        float data[] = AudioHelper.createSoundDataInFloatArray(
-                                bufferSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        assertEquals(TEST_NAME,
-                                bufferSamples,
-                                track.write(data, 0 /*offsetInBytes*/, data.length,
-                                        AudioTrack.WRITE_BLOCKING));
-                        } break;
-                    }
-                    assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
-                    // test setLoopPoints and setPosition can be called here.
-                    assertEquals(TEST_NAME,
-                            android.media.AudioTrack.SUCCESS,
-                            track.setPlaybackHeadPosition(0 /*positionInFrames*/));
-                    assertEquals(TEST_NAME,
-                            android.media.AudioTrack.SUCCESS,
-                            track.setLoopPoints(0 /*startInFrames*/, bufferFrames, TEST_LOOPS));
-
-                    track.play();
-                    Thread.sleep(seconds * MILLISECONDS_PER_SECOND * (TEST_LOOPS + 1));
-                    Thread.sleep(WAIT_MSEC);
-
-                    // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
-                    // the running count of frames played, not the actual static buffer position.
-                    int position = track.getPlaybackHeadPosition();
-                    assertEquals(TEST_NAME, bufferFrames * (TEST_LOOPS + 1), position);
-
-                    track.stop();
-                    Thread.sleep(WAIT_MSEC);
-                    // -------- tear down --------------
-                    track.release();
                     frequency += 70; // increment test tone frequency
                 }
             }
         }
     }
 
+    private void playOnceStaticData(String testName, int testMode, int testStreamType,
+            double testSweep, int testLoops, int testFormat, double testFrequency, int testSr,
+            int testConf, long waitMsec, double testLoopDuration)
+            throws InterruptedException {
+        // -------- initialization --------------
+        final int channelCount = Integer.bitCount(testConf);
+        final int bufferFrames = (int)(testLoopDuration * testSr);
+        final int bufferSamples = bufferFrames * channelCount;
+        final int bufferSize = bufferSamples
+                * AudioFormat.getBytesPerSample(testFormat);
+        final double frequency = testFrequency / channelCount;
+        final long MILLISECONDS_PER_SECOND = 1000;
+        AudioTrack track = new AudioTrack(testStreamType, testSr,
+                testConf, testFormat, bufferSize, testMode);
+        assertEquals(testName, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+
+        // -------- test --------------
+
+        // test setLoopPoints and setPosition can be called here.
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setPlaybackHeadPosition(bufferFrames/2));
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setLoopPoints(
+                        0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
+        // only need to write once to the static track
+        switch (testFormat) {
+        case AudioFormat.ENCODING_PCM_8BIT: {
+            byte data[] = AudioHelper.createSoundDataInByteArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length));
+            } break;
+        case AudioFormat.ENCODING_PCM_16BIT: {
+            short data[] = AudioHelper.createSoundDataInShortArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length));
+            } break;
+        case AudioFormat.ENCODING_PCM_FLOAT: {
+            float data[] = AudioHelper.createSoundDataInFloatArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length,
+                            AudioTrack.WRITE_BLOCKING));
+            } break;
+        }
+        assertEquals(testName, AudioTrack.STATE_INITIALIZED, track.getState());
+        // test setLoopPoints and setPosition can be called here.
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setPlaybackHeadPosition(0 /*positionInFrames*/));
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setLoopPoints(0 /*startInFrames*/, bufferFrames, testLoops));
+
+        track.play();
+        Thread.sleep((int)(testLoopDuration * MILLISECONDS_PER_SECOND) * (testLoops + 1));
+        Thread.sleep(waitMsec);
+
+        // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
+        // the running count of frames played, not the actual static buffer position.
+        int position = track.getPlaybackHeadPosition();
+        assertEquals(testName, bufferFrames * (testLoops + 1), position);
+
+        track.stop();
+        Thread.sleep(waitMsec);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    @Presubmit
+    public void testPlayStreamDataShort() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlayStreamDataShort";
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final float TEST_SWEEP = 0; // sine wave only
+        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
+        final double TEST_FREQUENCY = 1000;
+        final long NO_WAIT = 0;
+
+        playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
+                NO_WAIT);
+    }
+
     public void testPlayStreamData() throws Exception {
         // constants for test
         final String TEST_NAME = "testPlayStreamData";
@@ -1607,94 +1661,105 @@
             double frequency = 400; // frequency changes for each test
             for (int TEST_SR : TEST_SR_ARRAY) {
                 for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    final int channelCount = Integer.bitCount(TEST_CONF);
-                    if (TEST_IS_LOW_RAM_DEVICE
-                            && (TEST_SR > 96000 || channelCount > 4)) {
-                        continue; // ignore. FIXME: reenable when AF memory allocation is updated.
-                    }
-                    // -------- initialization --------------
-                    final int minBufferSize = AudioTrack.getMinBufferSize(TEST_SR,
-                            TEST_CONF, TEST_FORMAT); // in bytes
-                    AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR,
-                            TEST_CONF, TEST_FORMAT, minBufferSize, TEST_MODE);
-                    assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-
-                    // compute parameters for the source signal data.
-                    AudioFormat format = track.getFormat();
-                    assertEquals(TEST_NAME, TEST_SR, format.getSampleRate());
-                    assertEquals(TEST_NAME, TEST_CONF, format.getChannelMask());
-                    assertEquals(TEST_NAME, channelCount, format.getChannelCount());
-                    assertEquals(TEST_NAME, TEST_FORMAT, format.getEncoding());
-                    final int sourceSamples = channelCount
-                            * AudioHelper.frameCountFromMsec(500,
-                                    format); // duration of test tones
-                    final double testFrequency = frequency / channelCount;
-
-                    int written = 0;
-                    // For streaming tracks, it's ok to issue the play() command
-                    // before any audio is written.
-                    track.play();
-                    // -------- test --------------
-
-                    // samplesPerWrite can be any positive value.
-                    // We prefer this to be a multiple of channelCount so write()
-                    // does not return a short count.
-                    // If samplesPerWrite is very large, it is limited to the data length
-                    // and we simply write (blocking) the entire source data and not even loop.
-                    // We choose a value here which simulates double buffer writes.
-                    final int buffers = 2; // double buffering mode
-                    final int samplesPerWrite =
-                            (track.getBufferSizeInFrames() / buffers) * channelCount;
-                    switch (TEST_FORMAT) {
-                    case AudioFormat.ENCODING_PCM_8BIT: {
-                        byte data[] = AudioHelper.createSoundDataInByteArray(
-                                sourceSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        while (written < data.length) {
-                            int samples = Math.min(data.length - written, samplesPerWrite);
-                            int ret = track.write(data, written, samples);
-                            assertEquals(TEST_NAME, samples, ret);
-                            written += ret;
-                        }
-                        } break;
-                    case AudioFormat.ENCODING_PCM_16BIT: {
-                        short data[] = AudioHelper.createSoundDataInShortArray(
-                                sourceSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        while (written < data.length) {
-                            int samples = Math.min(data.length - written, samplesPerWrite);
-                            int ret = track.write(data, written, samples);
-                            assertEquals(TEST_NAME, samples, ret);
-                            written += ret;
-                        }
-                        } break;
-                    case AudioFormat.ENCODING_PCM_FLOAT: {
-                        float data[] = AudioHelper.createSoundDataInFloatArray(
-                                sourceSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        while (written < data.length) {
-                            int samples = Math.min(data.length - written, samplesPerWrite);
-                            int ret = track.write(data, written, samples,
-                                    AudioTrack.WRITE_BLOCKING);
-                            assertEquals(TEST_NAME, samples, ret);
-                            written += ret;
-                        }
-                        } break;
-                    }
-
-                    // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
-                    // Rather, it allows the remaining data in the internal buffer to drain.
-                    track.stop();
-                    Thread.sleep(WAIT_MSEC); // wait for the data to drain.
-                    // -------- tear down --------------
-                    track.release();
-                    Thread.sleep(WAIT_MSEC); // wait for release to complete
+                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
+                            WAIT_MSEC);
                     frequency += 50; // increment test tone frequency
                 }
             }
         }
     }
 
+    private void playOnceStreamData(String testName, int testMode, int testStream,
+            float testSweep, boolean isLowRamDevice, int testFormat, double testFrequency,
+            int testSr, int testConf, long waitMsec) throws InterruptedException {
+        final int channelCount = Integer.bitCount(testConf);
+        if (isLowRamDevice
+                && (testSr > 96000 || channelCount > 4)) {
+            return; // ignore. FIXME: reenable when AF memory allocation is updated.
+        }
+        // -------- initialization --------------
+        final int minBufferSize = AudioTrack.getMinBufferSize(testSr,
+                testConf, testFormat); // in bytes
+        AudioTrack track = new AudioTrack(testStream, testSr,
+                testConf, testFormat, minBufferSize, testMode);
+        assertTrue(testName, track.getState() == AudioTrack.STATE_INITIALIZED);
+
+        // compute parameters for the source signal data.
+        AudioFormat format = track.getFormat();
+        assertEquals(testName, testSr, format.getSampleRate());
+        assertEquals(testName, testConf, format.getChannelMask());
+        assertEquals(testName, channelCount, format.getChannelCount());
+        assertEquals(testName, testFormat, format.getEncoding());
+        final int sourceSamples = channelCount
+                * AudioHelper.frameCountFromMsec(500,
+                format); // duration of test tones
+        final double frequency = testFrequency / channelCount;
+
+        int written = 0;
+        // For streaming tracks, it's ok to issue the play() command
+        // before any audio is written.
+        track.play();
+        // -------- test --------------
+
+        // samplesPerWrite can be any positive value.
+        // We prefer this to be a multiple of channelCount so write()
+        // does not return a short count.
+        // If samplesPerWrite is very large, it is limited to the data length
+        // and we simply write (blocking) the entire source data and not even loop.
+        // We choose a value here which simulates double buffer writes.
+        final int buffers = 2; // double buffering mode
+        final int samplesPerWrite =
+                (track.getBufferSizeInFrames() / buffers) * channelCount;
+        switch (testFormat) {
+            case AudioFormat.ENCODING_PCM_8BIT: {
+                byte data[] = AudioHelper.createSoundDataInByteArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+            case AudioFormat.ENCODING_PCM_16BIT: {
+                short data[] = AudioHelper.createSoundDataInShortArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+            case AudioFormat.ENCODING_PCM_FLOAT: {
+                float data[] = AudioHelper.createSoundDataInFloatArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples,
+                            AudioTrack.WRITE_BLOCKING);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+        }
+
+        // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
+        // Rather, it allows the remaining data in the internal buffer to drain.
+        track.stop();
+        Thread.sleep(waitMsec); // wait for the data to drain.
+        // -------- tear down --------------
+        track.release();
+        Thread.sleep(waitMsec); // wait for release to complete
+    }
+
     public void testPlayStreamByteBuffer() throws Exception {
         // constants for test
         final String TEST_NAME = "testPlayStreamByteBuffer";
@@ -2358,6 +2423,33 @@
         assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
     }
 
+    // Test AudioTrack to see if there are any problems with large frame counts.
+    public void testAudioTrackLargeFrameCount() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testAudioTrackLargeFrameCount";
+        final int[] BUFFER_SIZES = { 4294968, 42949680, 429496800, Integer.MAX_VALUE };
+        final int[] MODES = { AudioTrack.MODE_STATIC, AudioTrack.MODE_STREAM };
+
+        for (int mode : MODES) {
+            for (int bufferSizeInBytes : BUFFER_SIZES) {
+                try {
+                    final AudioTrack track = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+                            .setSampleRate(44100)
+                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                            .build())
+                        .setTransferMode(mode)
+                        .setBufferSizeInBytes(bufferSizeInBytes) // 1 byte == 1 frame
+                        .build();
+                    track.release(); // OK to successfully complete
+                } catch (UnsupportedOperationException e) {
+                    ; // OK to throw unsupported exception
+                }
+            }
+        }
+    }
+
 /* Do not run in JB-MR1. will be re-opened in the next platform release.
     public void testResourceLeakage() throws Exception {
         final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
deleted file mode 100644
index d43dce1..0000000
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ /dev/null
@@ -1,527 +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.media.cts;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaCodecList;
-import android.media.MediaDrm;
-import android.media.MediaDrmException;
-import android.media.MediaFormat;
-import android.media.CamcorderProfile;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Base64;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.Vector;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Tests of MediaPlayer streaming capabilities.
- */
-public class ClearKeySystemTest extends MediaPlayerTestBase {
-    private static final String TAG = ClearKeySystemTest.class.getSimpleName();
-
-    // Add additional keys here if the content has more keys.
-    private static final byte[] CLEAR_KEY_CENC = {
-            (byte)0x3f, (byte)0x0a, (byte)0x33, (byte)0xf3, (byte)0x40, (byte)0x98, (byte)0xb9, (byte)0xe2,
-            (byte)0x2b, (byte)0xc0, (byte)0x78, (byte)0xe0, (byte)0xa1, (byte)0xb5, (byte)0xe8, (byte)0x54 };
-
-    private static final byte[] CLEAR_KEY_WEBM = "_CLEAR_KEY_WEBM_".getBytes();
-
-    private static final int CONNECTION_RETRIES = 10;
-    private static final int SLEEP_TIME_MS = 1000;
-    private static final int VIDEO_WIDTH_CENC = 1280;
-    private static final int VIDEO_HEIGHT_CENC = 720;
-    private static final int VIDEO_WIDTH_WEBM = 352;
-    private static final int VIDEO_HEIGHT_WEBM = 288;
-    private static final int VIDEO_WIDTH_MPEG2TS = 320;
-    private static final int VIDEO_HEIGHT_MPEG2TS = 240;
-    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(25, TimeUnit.SECONDS);
-    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
-
-    private static final Uri CENC_AUDIO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/clear/h264/llama/llama_aac_audio.mp4");
-    private static final Uri CENC_VIDEO_URL = Uri.parse(
-            "https://storage.googleapis.com/wvmedia/clearkey/llama_h264_main_720p_8000.mp4");
-    private static final Uri WEBM_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt);
-    private static final Uri MPEG2TS_SCRAMBLED_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.segment000001_scrambled);
-    private static final Uri MPEG2TS_CLEAR_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.segment000001);
-
-    private static final UUID COMMON_PSSH_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-
-    private byte[] mDrmInitData;
-    private byte[] mSessionId;
-    private Looper mLooper;
-    private MediaCodecClearKeyPlayer mMediaCodecPlayer;
-    private MediaDrm mDrm;
-    private Object mLock = new Object();
-    private SurfaceHolder mSurfaceHolder;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (false == deviceHasMediaDrm()) {
-            tearDown();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    private boolean deviceHasMediaDrm() {
-        // ClearKey is introduced after KitKat.
-        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
-            Log.i(TAG, "This test is designed to work after Android KitKat.");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Extracts key ids from the pssh blob returned by getKeyRequest() and
-     * places it in keyIds.
-     * keyRequestBlob format (section 5.1.3.1):
-     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key
-     *
-     * @return size of keyIds vector that contains the key ids, 0 for error
-     */
-    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
-        if (0 == keyRequestBlob.length || keyIds == null)
-            return 0;
-
-        String jsonLicenseRequest = new String(keyRequestBlob);
-        keyIds.clear();
-
-        try {
-            JSONObject license = new JSONObject(jsonLicenseRequest);
-            final JSONArray ids = license.getJSONArray("kids");
-            for (int i = 0; i < ids.length(); ++i) {
-                keyIds.add(ids.getString(i));
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
-            return 0;
-        }
-        return keyIds.size();
-    }
-
-    /**
-     * Creates the JSON Web Key string.
-     *
-     * @return JSON Web Key string.
-     */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
-        String jwkSet = "{\"keys\":[";
-        for (int i = 0; i < keyIds.size(); ++i) {
-            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
-            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
-
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
-                    "\",\"k\":\"" + key + "\"}";
-        }
-        jwkSet += "]}";
-        return jwkSet;
-    }
-
-    /**
-     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
-     * set and send it to the CDM via provideKeyResponse().
-     */
-    private void getKeys(MediaDrm drm, String initDataType,
-            byte[] sessionId, byte[] drmInitData, byte[][] clearKeys) {
-        MediaDrm.KeyRequest drmRequest = null;;
-        try {
-            drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
-                    MediaDrm.KEY_TYPE_STREAMING, null);
-        } catch (Exception e) {
-            e.printStackTrace();
-            Log.i(TAG, "Failed to get key request: " + e.toString());
-        }
-        if (drmRequest == null) {
-            Log.e(TAG, "Failed getKeyRequest");
-            return;
-        }
-
-        Vector<String> keyIds = new Vector<String>();
-        if (0 == getKeyIds(drmRequest.getData(), keyIds)) {
-            Log.e(TAG, "No key ids found in initData");
-            return;
-        }
-
-        if (clearKeys.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
-                    keyIds.size() + ", keys=" + clearKeys.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],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
-            keys.add(clearKey);
-        }
-
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
-        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
-
-        try {
-            try {
-                drm.provideKeyResponse(sessionId, jsonResponse);
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Failed to provide key response: " + e.toString());
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            Log.e(TAG, "Failed to provide key response: " + e.toString());
-        }
-    }
-
-    private @NonNull MediaDrm startDrm(final byte[][] clearKeys, final String initDataType, final UUID drmSchemeUuid) {
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to handle events
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mDrm = new MediaDrm(drmSchemeUuid);
-                } catch (MediaDrmException e) {
-                    Log.e(TAG, "Failed to create MediaDrm: " + e.getMessage());
-                    return;
-                }
-
-                synchronized(mLock) {
-                    mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
-                            @Override
-                            public void onEvent(MediaDrm md, byte[] sessionId, int event,
-                                    int extra, byte[] data) {
-                                if (event == MediaDrm.EVENT_KEY_REQUIRED) {
-                                    Log.i(TAG, "MediaDrm event: Key required");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
-                                } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
-                                    Log.i(TAG, "MediaDrm event: Key expired");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
-                                } else {
-                                    Log.e(TAG, "Events not supported" + event);
-                                }
-                            }
-                        });
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        return mDrm;
-    }
-
-    private void stopDrm(MediaDrm drm) {
-        if (drm != mDrm) {
-            Log.e(TAG, "invalid drm specified in stopDrm");
-        }
-        mLooper.quit();
-    }
-
-    private @NonNull byte[] openSession(MediaDrm drm) {
-        byte[] mSessionId = null;
-        boolean mRetryOpen;
-        do {
-            try {
-                mRetryOpen = false;
-                mSessionId = drm.openSession();
-            } catch (Exception e) {
-                mRetryOpen = true;
-            }
-        } while (mRetryOpen);
-        return mSessionId;
-    }
-
-    private void closeSession(MediaDrm drm, byte[] sessionId) {
-        drm.closeSession(sessionId);
-    }
-
-    private boolean isResolutionSupported(String mime, String[] features,
-            int videoWidth, int videoHeight) {
-        if (ApiLevelUtil.isBefore(android.os.Build.VERSION_CODES.JELLY_BEAN)) {
-            if  (videoHeight <= 144) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QCIF);
-            } else if (videoHeight <= 240) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA);
-            } else if (videoHeight <= 288) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_CIF);
-            } else if (videoHeight <= 480) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P);
-            } else if (videoHeight <= 720) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P);
-            } else if (videoHeight <= 1080) {
-                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P);
-            } else {
-                return false;
-            }
-        }
-
-        MediaFormat format = MediaFormat.createVideoFormat(mime, videoWidth, videoHeight);
-        for (String feature: features) {
-            format.setFeatureEnabled(feature, true);
-        }
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        if (mcl.findDecoderForFormat(format) == null) {
-            Log.i(TAG, "could not find codec for " + format);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Tests clear key system playback.
-     */
-    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 {
-        MediaDrm drm = null;
-        mSessionId = null;
-        if (!scrambled) {
-            drm = startDrm(clearKeys, initDataType, drmSchemeUuid);
-            if (!drm.isCryptoSchemeSupported(drmSchemeUuid)) {
-                stopDrm(drm);
-                throw new Error("Crypto scheme is not supported.");
-            }
-            mSessionId = openSession(drm);
-        }
-
-        if (!isResolutionSupported(videoMime, videoFeatures, videoWidth, videoHeight)) {
-            Log.i(TAG, "Device does not support " +
-                    videoWidth + "x" + videoHeight + " resolution for " + videoMime);
-            return;
-        }
-
-        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
-        if (!connectionStatus.isAvailable()) {
-            throw new Error("Network is not available, reason: " +
-                    connectionStatus.getNotConnectedReason());
-        }
-
-        // If device is not online, recheck the status a few times.
-        int retries = 0;
-        while (!connectionStatus.isConnected()) {
-            if (retries++ >= CONNECTION_RETRIES) {
-                throw new Error("Device is not online, reason: " +
-                        connectionStatus.getNotConnectedReason());
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-            }
-        }
-        connectionStatus.testConnection(videoUrl);
-
-        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                getActivity().getSurfaceHolder(),
-                mSessionId, scrambled,
-                mContext.getResources());
-
-        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, audioEncrypted);
-        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, videoEncrypted);
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.prepare();
-        if (!scrambled) {
-            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
-        }
-        // starts video playback
-        mMediaCodecPlayer.startThread();
-
-        long timeOut = System.currentTimeMillis() + PLAY_TIME_MS;
-        while (timeOut > System.currentTimeMillis() && !mMediaCodecPlayer.isEnded()) {
-            Thread.sleep(SLEEP_TIME_MS);
-            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration() ) {
-                Log.d(TAG, "current pos = " + mMediaCodecPlayer.getCurrentPosition() +
-                        ">= duration = " + mMediaCodecPlayer.getDuration());
-                break;
-            }
-        }
-
-        Log.d(TAG, "playVideo player.reset()");
-        mMediaCodecPlayer.reset();
-        if (!scrambled) {
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-        }
-    }
-
-    private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
-        final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
-        if (keyStatus.isEmpty()) {
-            Log.e(TAG, "queryKeyStatus: empty key status");
-            return false;
-        }
-
-        final Set<String> keySet = keyStatus.keySet();
-        final int numKeys = keySet.size();
-        final String[] keys = keySet.toArray(new String[numKeys]);
-        for (int i = 0; i < numKeys; ++i) {
-            final String key = keys[i];
-            Log.i(TAG, "queryKeyStatus: key=" + key + ", value=" + keyStatus.get(key));
-        }
-
-        return true;
-    }
-
-    public void testQueryKeyStatus() throws Exception {
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc", COMMON_PSSH_SCHEME_UUID);
-        if (!drm.isCryptoSchemeSupported(COMMON_PSSH_SCHEME_UUID)) {
-            stopDrm(drm);
-            throw new Error("Crypto scheme is not supported.");
-        }
-
-        mSessionId = openSession(drm);
-
-        // Test default key status, should not be defined
-        final HashMap<String, String> keyStatus = drm.queryKeyStatus(mSessionId);
-        if (!keyStatus.isEmpty()) {
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-            throw new Error("query default key status failed");
-        }
-
-        // Test valid key status
-        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();
-
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, "cenc", mSessionId, mDrmInitData, new byte[][] { CLEAR_KEY_CENC });
-        boolean success = true;
-        if (!queryKeyStatus(drm, mSessionId)) {
-            success = false;
-        }
-
-        mMediaCodecPlayer.reset();
-        closeSession(drm, mSessionId);
-        stopDrm(drm);
-        if (!success) {
-            throw new Error("query key status failed");
-        }
-    }
-
-    public void testClearKeyPlaybackCenc() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_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,
-            CENC_VIDEO_URL, true,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false);
-    }
-
-    public void testClearKeyPlaybackCenc2() 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,
-            CENC_VIDEO_URL, true,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false);
-    }
-
-    public void testClearKeyPlaybackWebm() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            MIME_VIDEO_VP8, new String[0],
-            "webm", new byte[][] { CLEAR_KEY_WEBM },
-            WEBM_URL, true,
-            WEBM_URL, true,
-            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false);
-    }
-
-    public void testClearKeyPlaybackMpeg2ts() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            MIME_VIDEO_AVC, new String[0],
-            "mpeg2ts", null,
-            MPEG2TS_SCRAMBLED_URL, false,
-            MPEG2TS_SCRAMBLED_URL, false,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true);
-    }
-
-    public void testPlaybackMpeg2ts() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            MIME_VIDEO_AVC, new String[0],
-            "mpeg2ts", null,
-            MPEG2TS_CLEAR_URL, false,
-            MPEG2TS_CLEAR_URL, false,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
index fd8c3b1..c401c75 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
@@ -300,6 +300,24 @@
                 mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE));
     }
 
+    public void testSubscriptionCallbackNotCalledAfterDisconnect() {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        mMediaBrowser.disconnect();
+        resetCallbacks();
+        StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT);
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+        assertNull(mSubscriptionCallback.mLastParentId);
+    }
+
     public void testUnsubscribeForMultipleSubscriptions() {
         createMediaBrowser(TEST_BROWSER_SERVICE);
         connectMediaBrowserService();
@@ -436,6 +454,21 @@
         assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback.mLastErrorId);
     }
 
+    public void testItemCallbackNotCalledAfterDisconnect() {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+        mMediaBrowser.disconnect();
+        resetCallbacks();
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertNull(mItemCallback.mLastMediaItem);
+        assertNull(mItemCallback.mLastErrorId);
+    }
+
     private void createMediaBrowser(final ComponentName component) {
         getInstrumentation().runOnMainSync(new Runnable() {
             @Override
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 0ea3ae0..16c6417 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -466,6 +466,89 @@
         }
     }
 
+    private MediaFormat createVideoFormatForBitrateMode(String mime, int width, int height,
+            int bitrateMode, CodecCapabilities caps) {
+        MediaCodecInfo.EncoderCapabilities encoderCaps = caps.getEncoderCapabilities();
+        if (!encoderCaps.isBitrateModeSupported(bitrateMode)) {
+            return null;
+        }
+
+        VideoCapabilities vidCaps = caps.getVideoCapabilities();
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+
+        // bitrate
+        int maxWidth = vidCaps.getSupportedWidths().getUpper();
+        int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
+        int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
+        format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
+        if (bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) {
+            int quality = encoderCaps.getQualityRange().getLower();
+            Log.i(TAG, "reasonable quality for " + width + "x" + height + "@" + maxRate
+                    + " " + mime + " = " + quality);
+            format.setInteger(MediaFormat.KEY_QUALITY, quality);
+        } else {
+            int bitrate = vidCaps.getBitrateRange().clamp(
+                    (int)(vidCaps.getBitrateRange().getUpper()
+                            / Math.sqrt((double)maxWidth * maxHeight / width / height)));
+            Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
+                    + " " + mime + " = " + bitrate + " mode " + bitrateMode);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        }
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, maxRate);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                CodecCapabilities.COLOR_FormatYUV420Flexible);
+
+        return format;
+    }
+
+    public void testAllAdvertisedVideoEncoderBitrateModes() throws IOException {
+        boolean skipped = true;
+        final int[] modes = {
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ,
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR,
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
+        };
+        for (MediaCodecInfo info : mAllInfos) {
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            for (String mime: info.getSupportedTypes()) {
+                boolean isVideo = isVideoMime(mime);
+                if (!isVideo) {
+                    continue;
+                }
+                skipped = false;
+
+                int numSupportedModes = 0;
+                for (int mode : modes) {
+                    MediaFormat format = createVideoFormatForBitrateMode(
+                            mime, 176, 144, mode, info.getCapabilitiesForType(mime));
+                    if (format == null) {
+                        continue;
+                    }
+                    MediaCodec codec = null;
+                    try {
+                        codec = MediaCodec.createByCodecName(info.getName());
+                        codec.configure(format, null /* surface */, null /* crypto */,
+                                MediaCodec.CONFIGURE_FLAG_ENCODE);
+                    } finally {
+                        if (codec != null) {
+                            codec.release();
+                        }
+                    }
+                    numSupportedModes++;
+                }
+                assertTrue(info.getName() + " has no supported bitrate mode",
+                        numSupportedModes > 0);
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video encoders found");
+        }
+    }
+
     public void testAllNonTunneledVideoCodecsSupportFlexibleYUV() throws IOException {
         boolean skipped = true;
         for (MediaCodecInfo info : mAllInfos) {
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
new file mode 100644
index 0000000..8ef8869
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -0,0 +1,793 @@
+/*
+ * 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.media.cts;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaDrm;
+import android.media.MediaDrmException;
+import android.media.MediaFormat;
+import android.media.CamcorderProfile;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Base64;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Vector;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Tests of MediaPlayer streaming capabilities.
+ */
+public class MediaDrmClearkeyTest extends MediaPlayerTestBase {
+    private static final String TAG = MediaDrmClearkeyTest.class.getSimpleName();
+
+    // Add additional keys here if the content has more keys.
+    private static final byte[] CLEAR_KEY_CENC = {
+            (byte)0x3f, (byte)0x0a, (byte)0x33, (byte)0xf3, (byte)0x40, (byte)0x98, (byte)0xb9, (byte)0xe2,
+            (byte)0x2b, (byte)0xc0, (byte)0x78, (byte)0xe0, (byte)0xa1, (byte)0xb5, (byte)0xe8, (byte)0x54 };
+
+    private static final byte[] CLEAR_KEY_WEBM = "_CLEAR_KEY_WEBM_".getBytes();
+
+    private static final int CONNECTION_RETRIES = 10;
+    private static final int SLEEP_TIME_MS = 1000;
+    private static final int VIDEO_WIDTH_CENC = 1280;
+    private static final int VIDEO_HEIGHT_CENC = 720;
+    private static final int VIDEO_WIDTH_WEBM = 352;
+    private static final int VIDEO_HEIGHT_WEBM = 288;
+    private static final int VIDEO_WIDTH_MPEG2TS = 320;
+    private static final int VIDEO_HEIGHT_MPEG2TS = 240;
+    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(25, TimeUnit.SECONDS);
+    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
+
+    // Property Keys
+    private static final String ALGORITHMS_PROPERTY_KEY = MediaDrm.PROPERTY_ALGORITHMS;
+    private static final String DESCRIPTION_PROPERTY_KEY = MediaDrm.PROPERTY_DESCRIPTION;
+    private static final String DEVICEID_PROPERTY_KEY = "deviceId";
+    private static final String INVALID_PROPERTY_KEY = "invalid property key";
+    private static final String LISTENER_TEST_SUPPORT_PROPERTY_KEY = "listenerTestSupport";
+    private static final String VENDOR_PROPERTY_KEY = MediaDrm.PROPERTY_VENDOR;
+    private static final String VERSION_PROPERTY_KEY = MediaDrm.PROPERTY_VERSION;
+
+    // Error message
+    private static final String ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED = "Crypto scheme is not supported";
+
+    private static final Uri CENC_AUDIO_URL = Uri.parse(
+            "https://storage.googleapis.com/wvmedia/clear/h264/llama/llama_aac_audio.mp4");
+    private static final Uri CENC_VIDEO_URL = Uri.parse(
+            "https://storage.googleapis.com/wvmedia/clearkey/llama_h264_main_720p_8000.mp4");
+    private static final Uri WEBM_URL = Uri.parse(
+            "android.resource://android.media.cts/" + R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt);
+    private static final Uri MPEG2TS_SCRAMBLED_URL = Uri.parse(
+            "android.resource://android.media.cts/" + R.raw.segment000001_scrambled);
+    private static final Uri MPEG2TS_CLEAR_URL = Uri.parse(
+            "android.resource://android.media.cts/" + R.raw.segment000001);
+
+    private static final UUID COMMON_PSSH_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+
+    private byte[] mDrmInitData;
+    private byte[] mSessionId;
+    private Looper mLooper;
+    private MediaCodecClearKeyPlayer mMediaCodecPlayer;
+    private MediaDrm mDrm;
+    private final Object mLock = new Object();
+    private SurfaceHolder mSurfaceHolder;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (false == deviceHasMediaDrm()) {
+            tearDown();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private boolean deviceHasMediaDrm() {
+        // ClearKey is introduced after KitKat.
+        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
+            Log.i(TAG, "This test is designed to work after Android KitKat.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Extracts key ids from the pssh blob returned by getKeyRequest() and
+     * places it in keyIds.
+     * keyRequestBlob format (section 5.1.3.1):
+     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key
+     *
+     * @return size of keyIds vector that contains the key ids, 0 for error
+     */
+    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
+        if (0 == keyRequestBlob.length || keyIds == null)
+            return 0;
+
+        String jsonLicenseRequest = new String(keyRequestBlob);
+        keyIds.clear();
+
+        try {
+            JSONObject license = new JSONObject(jsonLicenseRequest);
+            final JSONArray ids = license.getJSONArray("kids");
+            for (int i = 0; i < ids.length(); ++i) {
+                keyIds.add(ids.getString(i));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
+            return 0;
+        }
+        return keyIds.size();
+    }
+
+    /**
+     * Creates the JSON Web Key string.
+     *
+     * @return JSON Web Key string.
+     */
+    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
+        String jwkSet = "{\"keys\":[";
+        for (int i = 0; i < keyIds.size(); ++i) {
+            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
+            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
+
+            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
+                    "\",\"k\":\"" + key + "\"}";
+        }
+        jwkSet += "]}";
+        return jwkSet;
+    }
+
+    /**
+     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
+     * set and send it to the CDM via provideKeyResponse().
+     */
+    private void getKeys(MediaDrm drm, String initDataType,
+            byte[] sessionId, byte[] drmInitData, byte[][] clearKeys) {
+        MediaDrm.KeyRequest drmRequest = null;;
+        try {
+            drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
+                    MediaDrm.KEY_TYPE_STREAMING, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.i(TAG, "Failed to get key request: " + e.toString());
+        }
+        if (drmRequest == null) {
+            Log.e(TAG, "Failed getKeyRequest");
+            return;
+        }
+
+        Vector<String> keyIds = new Vector<String>();
+        if (0 == getKeyIds(drmRequest.getData(), keyIds)) {
+            Log.e(TAG, "No key ids found in initData");
+            return;
+        }
+
+        if (clearKeys.length != keyIds.size()) {
+            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
+                    keyIds.size() + ", keys=" + clearKeys.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],
+                    Base64.NO_PADDING | Base64.NO_WRAP);
+            keys.add(clearKey);
+        }
+
+        String jwkSet = createJsonWebKeySet(keyIds, keys);
+        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
+
+        try {
+            try {
+                drm.provideKeyResponse(sessionId, jsonResponse);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to provide key response: " + e.toString());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, "Failed to provide key response: " + e.toString());
+        }
+    }
+
+    private @NonNull MediaDrm startDrm(final byte[][] clearKeys, final String initDataType, final UUID drmSchemeUuid) {
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to handle events
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mDrm = new MediaDrm(drmSchemeUuid);
+                } catch (MediaDrmException e) {
+                    Log.e(TAG, "Failed to create MediaDrm: " + e.getMessage());
+                    return;
+                }
+
+                synchronized(mLock) {
+                    mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
+                            @Override
+                            public void onEvent(MediaDrm md, byte[] sessionId, int event,
+                                    int extra, byte[] data) {
+                                if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+                                    Log.i(TAG, "MediaDrm event: Key required");
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+                                } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
+                                    Log.i(TAG, "MediaDrm event: Key expired");
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+                                } else {
+                                    Log.e(TAG, "Events not supported" + event);
+                                }
+                            }
+                        });
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        return mDrm;
+    }
+
+    private void stopDrm(MediaDrm drm) {
+        if (drm != mDrm) {
+            Log.e(TAG, "invalid drm specified in stopDrm");
+        }
+        mLooper.quit();
+    }
+
+    private @NonNull byte[] openSession(MediaDrm drm) {
+        byte[] mSessionId = null;
+        boolean mRetryOpen;
+        do {
+            try {
+                mRetryOpen = false;
+                mSessionId = drm.openSession();
+            } catch (Exception e) {
+                mRetryOpen = true;
+            }
+        } while (mRetryOpen);
+        return mSessionId;
+    }
+
+    private void closeSession(MediaDrm drm, byte[] sessionId) {
+        drm.closeSession(sessionId);
+    }
+
+    private boolean isResolutionSupported(String mime, String[] features,
+            int videoWidth, int videoHeight) {
+        if (ApiLevelUtil.isBefore(android.os.Build.VERSION_CODES.JELLY_BEAN)) {
+            if  (videoHeight <= 144) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QCIF);
+            } else if (videoHeight <= 240) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA);
+            } else if (videoHeight <= 288) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_CIF);
+            } else if (videoHeight <= 480) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P);
+            } else if (videoHeight <= 720) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P);
+            } else if (videoHeight <= 1080) {
+                return CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P);
+            } else {
+                return false;
+            }
+        }
+
+        MediaFormat format = MediaFormat.createVideoFormat(mime, videoWidth, videoHeight);
+        for (String feature: features) {
+            format.setFeatureEnabled(feature, true);
+        }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "could not find codec for " + format);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Tests clear key system playback.
+     */
+    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 {
+        MediaDrm drm = null;
+        mSessionId = null;
+        if (!scrambled) {
+            drm = startDrm(clearKeys, initDataType, drmSchemeUuid);
+            if (!drm.isCryptoSchemeSupported(drmSchemeUuid)) {
+                stopDrm(drm);
+                throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
+            }
+            mSessionId = openSession(drm);
+        }
+
+        if (!isResolutionSupported(videoMime, videoFeatures, videoWidth, videoHeight)) {
+            Log.i(TAG, "Device does not support " +
+                    videoWidth + "x" + videoHeight + " resolution for " + videoMime);
+            return;
+        }
+
+        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
+        if (!connectionStatus.isAvailable()) {
+            throw new Error("Network is not available, reason: " +
+                    connectionStatus.getNotConnectedReason());
+        }
+
+        // If device is not online, recheck the status a few times.
+        int retries = 0;
+        while (!connectionStatus.isConnected()) {
+            if (retries++ >= CONNECTION_RETRIES) {
+                throw new Error("Device is not online, reason: " +
+                        connectionStatus.getNotConnectedReason());
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+        }
+        connectionStatus.testConnection(videoUrl);
+
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getActivity().getSurfaceHolder(),
+                mSessionId, scrambled,
+                mContext.getResources());
+
+        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, audioEncrypted);
+        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, videoEncrypted);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+        if (!scrambled) {
+            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+        }
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+
+        long timeOut = System.currentTimeMillis() + PLAY_TIME_MS;
+        while (timeOut > System.currentTimeMillis() && !mMediaCodecPlayer.isEnded()) {
+            Thread.sleep(SLEEP_TIME_MS);
+            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration() ) {
+                Log.d(TAG, "current pos = " + mMediaCodecPlayer.getCurrentPosition() +
+                        ">= duration = " + mMediaCodecPlayer.getDuration());
+                break;
+            }
+        }
+
+        Log.d(TAG, "playVideo player.reset()");
+        mMediaCodecPlayer.reset();
+        if (!scrambled) {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+    }
+
+    private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
+        final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
+        if (keyStatus.isEmpty()) {
+            Log.e(TAG, "queryKeyStatus: empty key status");
+            return false;
+        }
+
+        final Set<String> keySet = keyStatus.keySet();
+        final int numKeys = keySet.size();
+        final String[] keys = keySet.toArray(new String[numKeys]);
+        for (int i = 0; i < numKeys; ++i) {
+            final String key = keys[i];
+            Log.i(TAG, "queryKeyStatus: key=" + key + ", value=" + keyStatus.get(key));
+        }
+
+        return true;
+    }
+
+    public void testQueryKeyStatus() throws Exception {
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc", COMMON_PSSH_SCHEME_UUID);
+        if (!drm.isCryptoSchemeSupported(COMMON_PSSH_SCHEME_UUID)) {
+            stopDrm(drm);
+            throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
+        }
+
+        mSessionId = openSession(drm);
+
+        // Test default key status, should not be defined
+        final HashMap<String, String> keyStatus = drm.queryKeyStatus(mSessionId);
+        if (!keyStatus.isEmpty()) {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+            throw new Error("query default key status failed");
+        }
+
+        // Test valid key status
+        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();
+
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, "cenc", mSessionId, mDrmInitData, new byte[][] { CLEAR_KEY_CENC });
+        boolean success = true;
+        if (!queryKeyStatus(drm, mSessionId)) {
+            success = false;
+        }
+
+        mMediaCodecPlayer.reset();
+        closeSession(drm, mSessionId);
+        stopDrm(drm);
+        if (!success) {
+            throw new Error("query key status failed");
+        }
+    }
+
+    public void testClearKeyPlaybackCenc() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_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 */);
+    }
+
+    public void testClearKeyPlaybackCenc2() 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 */);
+    }
+
+    public void testClearKeyPlaybackWebm() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            MIME_VIDEO_VP8, new String[0],
+            "webm", new byte[][] { CLEAR_KEY_WEBM },
+            WEBM_URL, true /* audioEncrypted */,
+            WEBM_URL, true /* videoEncrypted */,
+            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */);
+    }
+
+    public void testClearKeyPlaybackMpeg2ts() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            MIME_VIDEO_AVC, new String[0],
+            "mpeg2ts", null,
+            MPEG2TS_SCRAMBLED_URL, false /* audioEncrypted */,
+            MPEG2TS_SCRAMBLED_URL, false /* videoEncrypted */,
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */);
+    }
+
+    public void testPlaybackMpeg2ts() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            MIME_VIDEO_AVC, new String[0],
+            "mpeg2ts", null,
+            MPEG2TS_CLEAR_URL, false /* audioEncrypted */,
+            MPEG2TS_CLEAR_URL, false /* videoEncrypted */,
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */);
+    }
+
+    private String getStringProperty(final MediaDrm drm,  final String key) {
+        String value = "";
+        try {
+            value = drm.getPropertyString(key);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected result: " + e.getMessage());
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+        return value;
+    }
+
+    private byte[] getByteArrayProperty(final MediaDrm drm,  final String key) {
+        byte[] bytes = new byte[0];
+        try {
+            bytes = drm.getPropertyByteArray(key);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+        return bytes;
+    }
+
+    private void setStringProperty(final MediaDrm drm, final String key, final String value) {
+        try {
+            drm.setPropertyString(key, value);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+    }
+
+    private void setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes) {
+        try {
+            drm.setPropertyByteArray(key, bytes);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+    }
+
+    public void testGetProperties() throws Exception {
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
+                "cenc", COMMON_PSSH_SCHEME_UUID);
+
+        try {
+            // The following tests will not verify the value we are getting
+            // back since it could change in the future.
+            final String[] sKeys = {
+                    DESCRIPTION_PROPERTY_KEY, LISTENER_TEST_SUPPORT_PROPERTY_KEY,
+                    VENDOR_PROPERTY_KEY, VERSION_PROPERTY_KEY};
+            String value;
+            for (String key : sKeys) {
+                value = getStringProperty(drm, key);
+                Log.d(TAG, "getPropertyString returns: " + key + ", " + value);
+                if (value.isEmpty()) {
+                    throw new Error("Failed to get property for: " + key);
+                }
+            }
+
+            byte[] bytes = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+            if (0 == bytes.length) {
+                throw new Error("Failed to get property for: " + DEVICEID_PROPERTY_KEY);
+            }
+
+            // Test with an invalid property key.
+            value = getStringProperty(drm, INVALID_PROPERTY_KEY);
+            bytes = getByteArrayProperty(drm, INVALID_PROPERTY_KEY);
+            if (!value.isEmpty() || 0 != bytes.length) {
+                throw new Error("get property failed using an invalid property key");
+            }
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    public void testSetProperties() throws Exception {
+        MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
+                "cenc", COMMON_PSSH_SCHEME_UUID);
+
+        try {
+            // Test setting predefined string property
+            // - Save the value to be restored later
+            // - Set the property value
+            // - Check the value that was set
+            // - Restore previous value
+            String listenerTestSupport = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+
+            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
+
+            String value = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+            if (!value.equals("testing")) {
+                throw new Error("Failed to set property: " + LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+            }
+
+            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, listenerTestSupport);
+
+            // Test setting immutable properties
+            HashMap<String, String> defaultImmutableProperties = new HashMap<String, String>();
+            defaultImmutableProperties.put(ALGORITHMS_PROPERTY_KEY,
+                    getStringProperty(drm, ALGORITHMS_PROPERTY_KEY));
+            defaultImmutableProperties.put(DESCRIPTION_PROPERTY_KEY,
+                    getStringProperty(drm, DESCRIPTION_PROPERTY_KEY));
+            defaultImmutableProperties.put(VENDOR_PROPERTY_KEY,
+                    getStringProperty(drm, VENDOR_PROPERTY_KEY));
+            defaultImmutableProperties.put(VERSION_PROPERTY_KEY,
+                    getStringProperty(drm, VERSION_PROPERTY_KEY));
+
+            HashMap<String, String> immutableProperties = new HashMap<String, String>();
+            immutableProperties.put(ALGORITHMS_PROPERTY_KEY, "brute force");
+            immutableProperties.put(DESCRIPTION_PROPERTY_KEY, "testing only");
+            immutableProperties.put(VENDOR_PROPERTY_KEY, "my Google");
+            immutableProperties.put(VERSION_PROPERTY_KEY, "undefined");
+
+            for (String key : immutableProperties.keySet()) {
+                setStringProperty(drm, key, immutableProperties.get(key));
+            }
+
+            // Verify the immutable properties have not been set
+            for (String key : immutableProperties.keySet()) {
+                value = getStringProperty(drm, key);
+                if (!defaultImmutableProperties.get(key).equals(getStringProperty(drm, key))) {
+                    throw new Error("Immutable property has changed, key=" + key);
+                }
+            }
+
+            // Test setPropertyByteArray for immutable property
+            final byte[] bytes = new byte[] {
+                    0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
+                    0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
+
+            final byte[] deviceId = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+
+            setByteArrayProperty(drm, DEVICEID_PROPERTY_KEY, bytes);
+
+            // Verify deviceId has not changed
+            if (!Arrays.equals(deviceId, getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY))) {
+                throw new Error("Failed to set byte array for key=" + DEVICEID_PROPERTY_KEY);
+            }
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    public void testGetOpenSessionCount() {
+        byte[] sessionId = null;
+        MediaDrm drm = null;
+        try {
+            drm = new MediaDrm(COMMON_PSSH_SCHEME_UUID);
+
+            if (drm.getOpenSessionCount() != 0) {
+                throw new Error("expected open session count to be 0");
+            }
+            sessionId = drm.openSession();
+            if (drm.getOpenSessionCount() != 1) {
+                throw new Error("expected open session count to be 1");
+            }
+            drm.closeSession(sessionId);
+            sessionId = null;
+
+            if (drm.getOpenSessionCount() != 0) {
+                throw new Error("expected open session count to be 0");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception requesting open sessions", e);
+        } finally  {
+            if (sessionId != null) {
+                drm.closeSession(sessionId);
+            }
+        }
+    }
+
+    private final static int CLEARKEY_MAX_SESSIONS = 10;
+
+    public void testMaxSessionCount() {
+        try {
+            MediaDrm drm = new MediaDrm(COMMON_PSSH_SCHEME_UUID);
+
+            if (drm.getMaxSessionCount() != CLEARKEY_MAX_SESSIONS) {
+                throw new Error("expected open session count to be " +
+                        CLEARKEY_MAX_SESSIONS);
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception requesting open sessions", e);
+        }
+    }
+
+    public void testHdcpLevels() {
+        try {
+            MediaDrm drm = new MediaDrm(COMMON_PSSH_SCHEME_UUID);
+
+            if (drm.getConnectedHdcpLevel() != MediaDrm.HDCP_NONE) {
+                throw new Error("expected connected hdcp level to be HDCP_NONE");
+            }
+
+            if (drm.getMaxHdcpLevel() != MediaDrm.HDCP_NO_DIGITAL_OUTPUT) {
+                throw new Error("expected max hdcp level to be HDCP_NO_DIGITAL_OUTPUT");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception requesting open sessions", e);
+        }
+    }
+
+    public void testSecurityLevels() {
+        MediaDrm drm = null;
+        byte[] sessionId = null;
+        try {
+            drm = new MediaDrm(COMMON_PSSH_SCHEME_UUID);
+
+            sessionId = drm.openSession(MediaDrm.SW_SECURE_CRYPTO);
+            if (drm.getSecurityLevel(sessionId) != MediaDrm.SW_SECURE_CRYPTO) {
+                throw new Error("expected security level to be SW_SECURE_CRYPTO");
+            }
+            drm.closeSession(sessionId);
+            sessionId = null;
+
+            sessionId = drm.openSession();
+            if (drm.getSecurityLevel(sessionId) != MediaDrm.SW_SECURE_CRYPTO) {
+                throw new Error("expected security level to be SW_SECURE_CRYPTO");
+            }
+            drm.closeSession(sessionId);
+            sessionId = null;
+
+            try {
+                sessionId = drm.openSession(MediaDrm.SW_SECURE_DECODE);
+            } catch (IllegalArgumentException e) {
+                /* caught expected exception */
+            } catch (Exception e) {
+                throw new Exception ("did't get expected IllegalArgumentException" +
+                        " while opening a session with disallowed security level");
+            } finally  {
+                if (sessionId != null) {
+                    drm.closeSession(sessionId);
+                }
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception requesting open sessions", e);
+        } finally  {
+            if (sessionId != null) {
+                drm.closeSession(sessionId);
+            }
+        }
+     }
+
+    /* TODO: add secure stop tests */
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
new file mode 100644
index 0000000..31a9669
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.media.cts;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
+import android.media.MediaDrm;
+import android.media.MediaDrm.MediaDrmStateException;
+import android.os.PersistableBundle;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import com.google.common.io.BaseEncoding;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.StringJoiner;
+import java.util.UUID;
+
+
+/**
+ * MediaDrm tests covering {@link MediaDrm#getMetrics} and related
+ * functionality.
+ */
+public class MediaDrmMetricsTest extends AndroidTestCase {
+    private static final String TAG = MediaDrmMetricsTest.class.getSimpleName();
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+    private static final String GOOGLE_CLEARKEY_VENDOR_ID = "Google.ClearKey CDM";
+
+    private String dumpBundleKeys(PersistableBundle bundle) {
+      StringJoiner joiner = new StringJoiner(",");
+      for (String key : bundle.keySet()) {
+        joiner.add(key);
+      }
+      return joiner.toString();
+    }
+
+    public void testGetMetricsEmpty() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+
+        assertEquals(1, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+        drm.close();
+    }
+
+    public void testGetMetricsSession() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+        byte[] sid1 = drm.openSession();
+        assertNotNull(sid1);
+        byte[] sid2 = drm.openSession();
+        assertNotNull(sid2);
+
+        drm.closeSession(sid1);
+        drm.closeSession(sid2);
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+        assertEquals(dumpBundleKeys(metrics), 5, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+
+        assertEquals(2, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
+        assertEquals(-1, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_ERROR_COUNT, -1));
+        assertEquals(2, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
+        assertEquals(-1, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_ERROR_COUNT, -1));
+
+        PersistableBundle startTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
+        assertNotNull(startTimesMs);
+        assertEquals(2, startTimesMs.keySet().size());
+        assertThat("Start times contain all session ids. ",
+            startTimesMs.keySet(), containsInAnyOrder(
+                BaseEncoding.base16().encode(sid1).toLowerCase(),
+                BaseEncoding.base16().encode(sid2).toLowerCase()));
+
+        PersistableBundle endTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
+        assertNotNull(endTimesMs);
+        assertEquals(2, endTimesMs.keySet().size());
+        assertThat("End times contain all session ids.",
+            endTimesMs.keySet(), containsInAnyOrder(
+                BaseEncoding.base16().encode(sid1).toLowerCase(),
+                BaseEncoding.base16().encode(sid2).toLowerCase()));
+       drm.close();
+    }
+
+    public void testGetMetricsGetKeyRequest() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+        byte[] sid = drm.openSession();
+        assertNotNull(sid);
+
+        try {
+          drm.getKeyRequest(sid, null, "", 2, null);
+        } catch (MediaDrmStateException e) {
+          // Exception expected.
+        }
+
+        drm.closeSession(sid);
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+
+        // Verify the count of metric, operation counts and errors.
+        assertEquals(7, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_COUNT));
+        long[] errorList = metrics.getLongArray(
+            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_LIST);
+        assertEquals(1, errorList.length);
+        assertFalse(errorList[0] == 0);
+
+        // Verify the start and end time groups in the nested
+        // PersistableBundles.
+        String hexSid = BaseEncoding.base16().encode(sid).toLowerCase();
+        PersistableBundle startTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
+        assertNotNull(startTimesMs);
+        assertEquals(1, startTimesMs.keySet().size());
+        assertEquals(startTimesMs.keySet().toArray()[0], hexSid);
+
+        PersistableBundle endTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
+        assertNotNull(endTimesMs);
+        assertEquals(1, endTimesMs.keySet().size());
+        assertEquals(endTimesMs.keySet().toArray()[0], hexSid);
+        assertThat(startTimesMs.getLong(hexSid),
+            lessThanOrEqualTo(endTimesMs.getLong(hexSid)));
+        drm.close();
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index c2d2991..c2f4d46 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -1854,6 +1854,14 @@
     }
 
     public void testChangeTimedTextTrack() throws Throwable {
+        testChangeTimedTextTrackWithSpeed(1.0f);
+    }
+
+    public void testChangeTimedTextTrackFast() throws Throwable {
+        testChangeTimedTextTrackWithSpeed(2.0f);
+    }
+
+    private void testChangeTimedTextTrackWithSpeed(float speed) throws Throwable {
         testTimedText(R.raw.testvideo_with_2_timedtext_tracks, 2,
                 new int[] {R.raw.test_subtitle1_srt, R.raw.test_subtitle2_srt},
                 new VerifyAndSignalTimedText(),
@@ -1864,6 +1872,10 @@
                         mOnTimedTextCalled.reset();
 
                         mMediaPlayer.start();
+                        if (speed != 1.0f) {
+                            mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
+                        }
+
                         assertTrue(mMediaPlayer.isPlaying());
 
                         // Waits until at least two subtitles are fired. Timeout is 2.5 sec.
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 3782416..7284341 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -35,6 +35,7 @@
 import android.media.MediaRecorder.OnErrorListener;
 import android.media.MediaRecorder.OnInfoListener;
 import android.media.MediaMetadataRetriever;
+import android.media.MicrophoneInfo;
 import android.opengl.GLES20;
 import android.os.ConditionVariable;
 import android.os.Environment;
@@ -607,6 +608,47 @@
         return 1;
     }
 
+    public void testGetActiveMicrophones() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.setMaxFileSize(MAX_FILE_SIZE * 10);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(1000);
+        List<MicrophoneInfo> activeMicrophones = mMediaRecorder.getActiveMicrophones();
+        assertTrue(activeMicrophones.size() > 0);
+        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
+            printMicrophoneInfo(activeMicrophone);
+        }
+        mMediaRecorder.stop();
+    }
+
+    private void printMicrophoneInfo(MicrophoneInfo microphone) {
+        Log.i(TAG, "deviceId:" + microphone.getDescription());
+        Log.i(TAG, "portId:" + microphone.getId());
+        Log.i(TAG, "type:" + microphone.getType());
+        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+            + " index:" + microphone.getIndexInTheGroup());
+        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
+        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
+        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+        Log.i(TAG, "min spl:" + microphone.getMinSpl());
+        Log.i(TAG, "directionality:" + microphone.getDirectionality());
+        Log.i(TAG, "******");
+    }
+
     public void testRecordAudioFromAudioSourceUnprocessed() throws Exception {
         if (!hasMicrophone() || !hasAmrNb()) {
             MediaUtils.skipTest("no audio codecs or microphone");
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 137b7cf..4a1dc09 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -110,6 +110,76 @@
                 "_data like ?", new String[] { mFileDir + "%"});
     }
 
+    public void testLocalizeRingtoneTitles() throws Exception {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+            mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // start connection and wait until connected
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        // Write unlocalizable audio file and scan to insert into database
+        final String unlocalizablePath = mFileDir + "/unlocalizable.mp3";
+        writeFile(R.raw.testmp3, unlocalizablePath);
+        mMediaScannerConnection.scanFile(unlocalizablePath, null);
+        checkMediaScannerConnection();
+        final Uri media1Uri = mMediaScannerConnectionClient.mediaUri;
+
+        // Ensure unlocalizable titles come back correctly
+        final ContentResolver res = mContext.getContentResolver();
+        final String unlocalizedTitle = "Chimey Phone";
+        Cursor c = res.query(media1Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(unlocalizedTitle, c.getString(0));
+
+        mMediaScannerConnectionClient.reset();
+
+        // Write localizable audio file and scan to insert into database
+        final String localizablePath = mFileDir + "/localizable.mp3";
+        writeFile(R.raw.testmp3_4, localizablePath);
+        mMediaScannerConnection.scanFile(localizablePath, null);
+        checkMediaScannerConnection();
+        final Uri media2Uri = mMediaScannerConnectionClient.mediaUri;
+
+        // Ensure localized title comes back localized
+        final String localizedTitle = mContext.getString(R.string.test_localizable_title);
+        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(localizedTitle, c.getString(0));
+
+        // Update localizable audio file to have unlocalizable title
+        final ContentValues values = new ContentValues();
+        final String newTitle = "New Title";
+        values.put("title", newTitle);
+        res.update(media2Uri, values, null, null);
+
+        // Ensure title comes back correctly
+        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(newTitle, c.getString(0));
+
+        // Update audio file to have localizable title once again
+        final String newLocalizableTitle =
+            "android.resource://android.media.cts/string/test_localizable_title";
+        values.put("title", newLocalizableTitle);
+        res.update(media2Uri, values, null, null);
+
+        // Ensure title comes back localized
+        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(localizedTitle, c.getString(0));
+
+        mMediaScannerConnection.disconnect();
+        c.close();
+    }
+
     public void testMediaScanner() throws InterruptedException, IOException {
         mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
         mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
@@ -321,11 +391,21 @@
         mMediaScannerConnection.connect();
         checkConnectionState(true);
 
+        // test unlocalizable file
+        canonicalizeTest(R.raw.testmp3);
+
+        mMediaScannerConnectionClient.reset();
+
+        // test localizable file
+        canonicalizeTest(R.raw.testmp3_4);
+    }
+
+    private void canonicalizeTest(int resId) throws Exception {
         // write file and scan to insert into database
         String fileDir = Environment.getExternalStorageDirectory() + "/"
                 + getClass().getCanonicalName() + "/canonicaltest-" + System.currentTimeMillis();
         String fileName = fileDir + "/test.mp3";
-        writeFile(R.raw.testmp3, fileName);
+        writeFile(resId, fileName);
         mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE);
         checkMediaScannerConnection();
 
@@ -350,7 +430,7 @@
         // write same file again and scan to insert into database
         mMediaScannerConnectionClient.reset();
         String fileName2 = fileDir + "/test2.mp3";
-        writeFile(R.raw.testmp3, fileName2);
+        writeFile(resId, fileName2);
         mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE);
         checkMediaScannerConnection();
 
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index cc6f186..58be212 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -15,6 +15,8 @@
  */
 package android.media.cts;
 
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,6 +35,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.view.KeyEvent;
 
@@ -417,6 +420,27 @@
         }
     }
 
+    /**
+     * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called
+     * once {@code setCallback(null)} is done.
+     */
+    public void testSetCallbackWithNull() throws Exception {
+        MediaSessionCallback sessionCallback = new MediaSessionCallback();
+        mSession.setCallback(sessionCallback, mHandler);
+        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setActive(true);
+
+        MediaController controller = mSession.getController();
+        setPlaybackState(PlaybackState.STATE_PLAYING);
+
+        sessionCallback.reset(1);
+        mSession.setCallback(null, mHandler);
+
+        controller.getTransportControls().pause();
+        assertFalse(sessionCallback.await(WAIT_MS));
+        assertFalse("Callback shouldn't be called.", sessionCallback.mOnPauseCalled);
+    }
+
     private void setPlaybackState(int state) {
         final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
                 | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
diff --git a/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
deleted file mode 100644
index 45b2b8b..0000000
--- a/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
+++ /dev/null
@@ -1,256 +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.media.cts;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
-import android.net.Uri;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-import com.google.android.collect.Lists;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.UUID;
-
-/**
- * Tests MediaDrm NDK APIs. ClearKey system uses a subset of NDK APIs,
- * this test only tests the APIs that are supported by ClearKey system.
- */
-public class NativeClearKeySystemTest extends MediaPlayerTestBase {
-    private static final String TAG = NativeClearKeySystemTest.class.getSimpleName();
-
-    private static final int CONNECTION_RETRIES = 10;
-    private static final int VIDEO_WIDTH_CENC = 1280;
-    private static final int VIDEO_HEIGHT_CENC = 720;
-    private static final String ISO_BMFF_VIDEO_MIME_TYPE = "video/avc";
-    private static final String ISO_BMFF_AUDIO_MIME_TYPE = "audio/avc";
-    private static final Uri CENC_AUDIO_URL = Uri.parse(
-        "https://storage.googleapis.com/wvmedia/clear/h264/llama/" +
-        "llama_aac_audio.mp4");
-
-    private static final Uri CENC_CLEARKEY_VIDEO_URL = Uri.parse(
-        "https://storage.googleapis.com/wvmedia/clearkey/" +
-        "llama_h264_main_720p_8000.mp4");
-
-    private static final int UUID_BYTE_SIZE = 16;
-    private static final UUID COMMON_PSSH_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-    private static final UUID BAD_SCHEME_UUID =
-            new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
-    private MediaCodecClearKeyPlayer mMediaCodecPlayer;
-
-    static {
-        try {
-            System.loadLibrary("ctsmediadrm_jni");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "NativeClearKeySystemTest: Error loading JNI library");
-            e.printStackTrace();
-        }
-        try {
-            System.loadLibrary("mediandk");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "NativeClearKeySystemTest: Error loading JNI library");
-            e.printStackTrace();
-        }
-    }
-
-    public static class PlaybackParams {
-        public Surface surface;
-        public String mimeType;
-        public String audioUrl;
-        public String videoUrl;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (false == deviceHasMediaDrm()) {
-            tearDown();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    private boolean deviceHasMediaDrm() {
-        // ClearKey is introduced after KitKat.
-        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
-            Log.i(TAG, "This test is designed to work after Android KitKat.");
-            return false;
-        }
-        return true;
-    }
-
-    private static final byte[] uuidByteArray(UUID uuid) {
-        ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
-        buffer.putLong(uuid.getMostSignificantBits());
-        buffer.putLong(uuid.getLeastSignificantBits());
-        return buffer.array();
-    }
-
-    public void testIsCryptoSchemeSupported() throws Exception {
-        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
-        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
-    }
-
-    public void testIsCryptoSchemeNotSupported() throws Exception {
-        assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
-    }
-
-    public void testPssh() throws Exception {
-        // The test uses a canned PSSH that contains the common box UUID.
-        assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
-                CENC_CLEARKEY_VIDEO_URL.toString()));
-    }
-
-    public void testQueryKeyStatus() throws Exception {
-        assertTrue(testQueryKeyStatusNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
-    }
-
-    public void testGetPropertyString() throws Exception {
-        StringBuffer value = new StringBuffer();
-        testGetPropertyStringNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID), "description", value);
-        assertEquals("ClearKey CDM", value.toString());
-
-        value.delete(0, value.length());
-        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
-        assertEquals("ClearKey CDM", value.toString());
-    }
-
-    public void testUnknownPropertyString() throws Exception {
-        StringBuffer value = new StringBuffer();
-
-        try {
-            testGetPropertyStringNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
-                    "unknown-property", value);
-            fail("Should have thrown an exception");
-        } catch (RuntimeException e) {
-            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
-            assertThat(e.getMessage(), containsString("get property string returns"));
-        }
-
-        value.delete(0, value.length());
-        try {
-            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
-                    "unknown-property", value);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
-            assertThat(e.getMessage(), containsString("get property string returns"));
-        }
-    }
-
-    /**
-     * Tests native clear key system playback.
-     */
-    private void testClearKeyPlayback(
-            UUID drmSchemeUuid, String mimeType, /*String initDataType,*/ Uri audioUrl, Uri videoUrl,
-            int videoWidth, int videoHeight) throws Exception {
-
-        if (!isCryptoSchemeSupportedNative(uuidByteArray(drmSchemeUuid))) {
-            throw new Error("Crypto scheme is not supported.");
-        }
-
-        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
-        if (!connectionStatus.isAvailable()) {
-            throw new Error("Network is not available, reason: " +
-                    connectionStatus.getNotConnectedReason());
-        }
-
-        // If device is not online, recheck the status a few times.
-        int retries = 0;
-        while (!connectionStatus.isConnected()) {
-            if (retries++ >= CONNECTION_RETRIES) {
-                throw new Error("Device is not online, reason: " +
-                        connectionStatus.getNotConnectedReason());
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                // do nothing
-            }
-        }
-        connectionStatus.testConnection(videoUrl);
-
-        if (!MediaUtils.checkCodecsForPath(mContext, videoUrl.toString())) {
-            Log.i(TAG, "Device does not support " +
-                  videoWidth + "x" + videoHeight + " resolution for " + mimeType);
-            return;  // skip
-        }
-
-        PlaybackParams params = new PlaybackParams();
-        params.surface = mActivity.getSurfaceHolder().getSurface();
-        params.mimeType = mimeType;
-        params.audioUrl = audioUrl.toString();
-        params.videoUrl = videoUrl.toString();
-
-        if (!testClearKeyPlaybackNative(
-            uuidByteArray(drmSchemeUuid), params)) {
-            Log.e(TAG, "Fails play back using native media drm APIs.");
-        }
-        params.surface.release();
-    }
-
-    private ArrayList<Integer> intVersion(String version) {
-        String versions[] = version.split("\\.");
-
-        ArrayList<Integer> versionNumbers = Lists.newArrayList();
-        for (String subVersion : versions) {
-            versionNumbers.add(Integer.parseInt(subVersion));
-        }
-        return versionNumbers;
-    }
-
-    private static native boolean isCryptoSchemeSupportedNative(final byte[] uuid);
-
-    private static native boolean testClearKeyPlaybackNative(final byte[] uuid,
-            PlaybackParams params);
-
-    private static native boolean testGetPropertyStringNative(final byte[] uuid,
-            final String name, StringBuffer value);
-
-    private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
-
-    private static native boolean testQueryKeyStatusNative(final byte[] uuid);
-
-    public void testClearKeyPlaybackCenc() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            ISO_BMFF_VIDEO_MIME_TYPE,
-            CENC_AUDIO_URL,
-            CENC_CLEARKEY_VIDEO_URL,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-    }
-
-    public void testClearKeyPlaybackCenc2() throws Exception {
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            ISO_BMFF_VIDEO_MIME_TYPE,
-            CENC_AUDIO_URL,
-            CENC_CLEARKEY_VIDEO_URL,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index 32cf661..4d6ba31 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -183,6 +183,46 @@
     private static native int[] getSampleSizesNative(int fd, long offset, long size);
     private static native int[] getSampleSizesNativePath(String path);
 
+    public void testExtractorFileDurationNative() throws Exception {
+        int res = R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz;
+        testExtractorFileDurationNative(res);
+    }
+
+    private void testExtractorFileDurationNative(int res) throws Exception {
+
+        AssetFileDescriptor fd = mResources.openRawResourceFd(res);
+        long durationUs = getExtractorFileDurationNative(
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+
+        int numtracks = ex.getTrackCount();
+        long aDurationUs = -1, vDurationUs = -1;
+        for (int i = 0; i < numtracks; i++) {
+            MediaFormat format = ex.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.startsWith("audio/")) {
+                aDurationUs = format.getLong(MediaFormat.KEY_DURATION);
+            } else if (mime.startsWith("video/")) {
+                vDurationUs = format.getLong(MediaFormat.KEY_DURATION);
+            }
+        }
+
+        assertTrue("duration inconsistency",
+                durationUs < 0 || durationUs >= aDurationUs && durationUs >= vDurationUs);
+
+    }
+
+    private static native long getExtractorFileDurationNative(int fd, long offset, long size);
+
+    public void testExtractorCachedDurationNative() throws Exception {
+        CtsTestServer foo = new CtsTestServer(mContext);
+        long cachedDurationUs = getExtractorCachedDurationNative(foo.getAssetUrl("ringer.mp3"));
+        assertTrue("cached duration negative", cachedDurationUs >= 0);
+    }
+
+    private static native long getExtractorCachedDurationNative(String uri);
 
     public void testDecoder() throws Exception {
         int testsRun =
@@ -203,7 +243,18 @@
         }
     }
 
+    public void testDataSource() throws Exception {
+        int testsRun = testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz, true);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
+    }
+
     private int testDecoder(int res) throws Exception {
+        return testDecoder(res, /* wrapFd */ false);
+    }
+
+    private int testDecoder(int res, boolean wrapFd) throws Exception {
         if (!MediaUtils.hasCodecsForResource(mContext, res)) {
             return 0; // skip
         }
@@ -213,7 +264,7 @@
         int[] jdata = getDecodedData(
                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
         int[] ndata = getDecodedDataNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd);
 
         fd.close();
         Log.i("@@@", Arrays.toString(jdata));
@@ -384,7 +435,7 @@
         return ret;
     }
 
-    private static native int[] getDecodedDataNative(int fd, long offset, long size)
+    private static native int[] getDecodedDataNative(int fd, long offset, long size, boolean wrapFd)
             throws IOException;
 
     public void testVideoPlayback() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
new file mode 100644
index 0000000..1858da0
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import android.net.Uri;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+import com.google.android.collect.Lists;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.UUID;
+
+/**
+ * Tests MediaDrm NDK APIs. ClearKey system uses a subset of NDK APIs,
+ * this test only tests the APIs that are supported by ClearKey system.
+ */
+public class NativeMediaDrmClearkeyTest extends MediaPlayerTestBase {
+    private static final String TAG = NativeMediaDrmClearkeyTest.class.getSimpleName();
+
+    private static final int CONNECTION_RETRIES = 10;
+    private static final int VIDEO_WIDTH_CENC = 1280;
+    private static final int VIDEO_HEIGHT_CENC = 720;
+    private static final String ISO_BMFF_VIDEO_MIME_TYPE = "video/avc";
+    private static final String ISO_BMFF_AUDIO_MIME_TYPE = "audio/avc";
+    private static final Uri CENC_AUDIO_URL = Uri.parse(
+        "https://storage.googleapis.com/wvmedia/clear/h264/llama/" +
+        "llama_aac_audio.mp4");
+
+    private static final Uri CENC_CLEARKEY_VIDEO_URL = Uri.parse(
+        "https://storage.googleapis.com/wvmedia/clearkey/" +
+        "llama_h264_main_720p_8000.mp4");
+
+    private static final int UUID_BYTE_SIZE = 16;
+    private static final UUID COMMON_PSSH_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+    private static final UUID BAD_SCHEME_UUID =
+            new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
+    private MediaCodecClearKeyPlayer mMediaCodecPlayer;
+
+    static {
+        try {
+            System.loadLibrary("ctsmediadrm_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
+            e.printStackTrace();
+        }
+        try {
+            System.loadLibrary("mediandk");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
+            e.printStackTrace();
+        }
+    }
+
+    public static class PlaybackParams {
+        public Surface surface;
+        public String mimeType;
+        public String audioUrl;
+        public String videoUrl;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (false == deviceHasMediaDrm()) {
+            tearDown();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private boolean deviceHasMediaDrm() {
+        // ClearKey is introduced after KitKat.
+        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
+            Log.i(TAG, "This test is designed to work after Android KitKat.");
+            return false;
+        }
+        return true;
+    }
+
+    private static final byte[] uuidByteArray(UUID uuid) {
+        ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
+        buffer.putLong(uuid.getMostSignificantBits());
+        buffer.putLong(uuid.getLeastSignificantBits());
+        return buffer.array();
+    }
+
+    public void testIsCryptoSchemeSupported() throws Exception {
+        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
+        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
+    public void testIsCryptoSchemeNotSupported() throws Exception {
+        assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
+    }
+
+    public void testPssh() throws Exception {
+        // The test uses a canned PSSH that contains the common box UUID.
+        assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
+                CENC_CLEARKEY_VIDEO_URL.toString()));
+    }
+
+    public void testQueryKeyStatus() throws Exception {
+        assertTrue(testQueryKeyStatusNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
+    }
+
+    public void testGetPropertyString() throws Exception {
+        StringBuffer value = new StringBuffer();
+        testGetPropertyStringNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID), "description", value);
+        assertEquals("ClearKey CDM", value.toString());
+
+        value.delete(0, value.length());
+        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
+        assertEquals("ClearKey CDM", value.toString());
+    }
+
+    public void testUnknownPropertyString() throws Exception {
+        StringBuffer value = new StringBuffer();
+
+        try {
+            testGetPropertyStringNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
+                    "unknown-property", value);
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
+            assertThat(e.getMessage(), containsString("get property string returns"));
+        }
+
+        value.delete(0, value.length());
+        try {
+            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
+                    "unknown-property", value);
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
+            assertThat(e.getMessage(), containsString("get property string returns"));
+        }
+    }
+
+    /**
+     * Tests native clear key system playback.
+     */
+    private void testClearKeyPlayback(
+            UUID drmSchemeUuid, String mimeType, /*String initDataType,*/ Uri audioUrl, Uri videoUrl,
+            int videoWidth, int videoHeight) throws Exception {
+
+        if (!isCryptoSchemeSupportedNative(uuidByteArray(drmSchemeUuid))) {
+            throw new Error("Crypto scheme is not supported.");
+        }
+
+        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
+        if (!connectionStatus.isAvailable()) {
+            throw new Error("Network is not available, reason: " +
+                    connectionStatus.getNotConnectedReason());
+        }
+
+        // If device is not online, recheck the status a few times.
+        int retries = 0;
+        while (!connectionStatus.isConnected()) {
+            if (retries++ >= CONNECTION_RETRIES) {
+                throw new Error("Device is not online, reason: " +
+                        connectionStatus.getNotConnectedReason());
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+        connectionStatus.testConnection(videoUrl);
+
+        if (!MediaUtils.checkCodecsForPath(mContext, videoUrl.toString())) {
+            Log.i(TAG, "Device does not support " +
+                  videoWidth + "x" + videoHeight + " resolution for " + mimeType);
+            return;  // skip
+        }
+
+        PlaybackParams params = new PlaybackParams();
+        params.surface = mActivity.getSurfaceHolder().getSurface();
+        params.mimeType = mimeType;
+        params.audioUrl = audioUrl.toString();
+        params.videoUrl = videoUrl.toString();
+
+        if (!testClearKeyPlaybackNative(
+            uuidByteArray(drmSchemeUuid), params)) {
+            Log.e(TAG, "Fails play back using native media drm APIs.");
+        }
+        params.surface.release();
+    }
+
+    private ArrayList<Integer> intVersion(String version) {
+        String versions[] = version.split("\\.");
+
+        ArrayList<Integer> versionNumbers = Lists.newArrayList();
+        for (String subVersion : versions) {
+            versionNumbers.add(Integer.parseInt(subVersion));
+        }
+        return versionNumbers;
+    }
+
+    private static native boolean isCryptoSchemeSupportedNative(final byte[] uuid);
+
+    private static native boolean testClearKeyPlaybackNative(final byte[] uuid,
+            PlaybackParams params);
+
+    private static native boolean testGetPropertyStringNative(final byte[] uuid,
+            final String name, StringBuffer value);
+
+    private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
+
+    private static native boolean testQueryKeyStatusNative(final byte[] uuid);
+
+    public void testClearKeyPlaybackCenc() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            ISO_BMFF_VIDEO_MIME_TYPE,
+            CENC_AUDIO_URL,
+            CENC_CLEARKEY_VIDEO_URL,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+    }
+
+    public void testClearKeyPlaybackCenc2() throws Exception {
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            ISO_BMFF_VIDEO_MIME_TYPE,
+            CENC_AUDIO_URL,
+            CENC_CLEARKEY_VIDEO_URL,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/ParamsTest.java b/tests/tests/media/src/android/media/cts/ParamsTest.java
index 6cf9be6..fab616a 100644
--- a/tests/tests/media/src/android/media/cts/ParamsTest.java
+++ b/tests/tests/media/src/android/media/cts/ParamsTest.java
@@ -373,59 +373,18 @@
     }
 
     public void testBufferingParamsBuilderAndGet() {
-        final int initialMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
         final int initialMarkMs = 2;
-        final int initialMarkKB = 20;
-        final int rebufferingMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
-        final int rebufferingMarkLowMs = 1;
-        final int rebufferingMarkHighMs = 3;
-        final int rebufferingMarkLowKB = 10;
-        final int rebufferingMarkHighKB = 30;
+        final int resumePlaybackMarkMs = 3;
 
         BufferingParams p1 = new BufferingParams.Builder()
-                .setInitialBufferingMode(initialMode)
-                .setInitialBufferingWatermarkMs(initialMarkMs)
-                .setInitialBufferingWatermarkKB(initialMarkKB)
-                .setRebufferingMode(rebufferingMode)
-                .setRebufferingWatermarkLowMs(rebufferingMarkLowMs)
-                .setRebufferingWatermarkHighMs(rebufferingMarkHighMs)
-                .setRebufferingWatermarkLowKB(rebufferingMarkLowKB)
-                .setRebufferingWatermarkHighKB(rebufferingMarkHighKB)
+                .setInitialMarkMs(initialMarkMs)
+                .setResumePlaybackMarkMs(resumePlaybackMarkMs)
                 .build();
 
-        assertEquals("initial buffering mode should match",
-                p1.getInitialBufferingMode(), initialMode);
-        assertEquals("rebuffering mode should match",
-                p1.getRebufferingMode(), rebufferingMode);
         assertEquals("intial markMs should match",
-                p1.getInitialBufferingWatermarkMs(), initialMarkMs);
-        assertEquals("intial markKB should match",
-                p1.getInitialBufferingWatermarkKB(), initialMarkKB);
-        assertEquals("rebuffering low markMs should match",
-                p1.getRebufferingWatermarkLowMs(), rebufferingMarkLowMs);
-        assertEquals("rebuffering low markKB should match",
-                p1.getRebufferingWatermarkLowKB(), rebufferingMarkLowKB);
-        assertEquals("rebuffering high markMs should match",
-                p1.getRebufferingWatermarkHighMs(), rebufferingMarkHighMs);
-        assertEquals("rebuffering high markKB should match",
-                p1.getRebufferingWatermarkHighKB(), rebufferingMarkHighKB);
-
-        final int rebufferingMarkLowMsPair = 4;
-        final int rebufferingMarkHighMsPair = 5;
-        final int rebufferingMarkLowKBPair = 40;
-        final int rebufferingMarkHighKBPair = 50;
-        BufferingParams p2 = new BufferingParams.Builder(p1)
-                .setRebufferingWatermarksMs(rebufferingMarkLowMsPair, rebufferingMarkHighMsPair)
-                .setRebufferingWatermarksKB(rebufferingMarkLowKBPair, rebufferingMarkHighKBPair)
-                .build();
-        assertEquals("paired low markMs should match",
-                p2.getRebufferingWatermarkLowMs(), rebufferingMarkLowMsPair);
-        assertEquals("paired low markKB should match",
-                p2.getRebufferingWatermarkLowKB(), rebufferingMarkLowKBPair);
-        assertEquals("paired high markMs should match",
-                p2.getRebufferingWatermarkHighMs(), rebufferingMarkHighMsPair);
-        assertEquals("paired high markKB should match",
-                p2.getRebufferingWatermarkHighKB(), rebufferingMarkHighKBPair);
+                p1.getInitialMarkMs(), initialMarkMs);
+        assertEquals("resume playback markMs should match",
+                p1.getResumePlaybackMarkMs(), resumePlaybackMarkMs);
     }
 
     public void testBufferingParamsDescribeContents() {
@@ -434,24 +393,12 @@
     }
 
     public void testBufferingParamsWriteToParcel() {
-        final int initialMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
         final int initialMarkMs = 2;
-        final int initialMarkKB = 20;
-        final int rebufferingMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
-        final int rebufferingMarkLowMs = 1;
-        final int rebufferingMarkHighMs = 3;
-        final int rebufferingMarkLowKB = 10;
-        final int rebufferingMarkHighKB = 30;
+        final int resumePlaybackMarkMs = 3;
 
         BufferingParams p = new BufferingParams.Builder()
-                .setInitialBufferingMode(initialMode)
-                .setInitialBufferingWatermarkMs(initialMarkMs)
-                .setInitialBufferingWatermarkKB(initialMarkKB)
-                .setRebufferingMode(rebufferingMode)
-                .setRebufferingWatermarkLowMs(rebufferingMarkLowMs)
-                .setRebufferingWatermarkHighMs(rebufferingMarkHighMs)
-                .setRebufferingWatermarkLowKB(rebufferingMarkLowKB)
-                .setRebufferingWatermarkHighKB(rebufferingMarkHighKB)
+                .setInitialMarkMs(initialMarkMs)
+                .setResumePlaybackMarkMs(resumePlaybackMarkMs)
                 .build();
 
         Parcel parcel = Parcel.obtain();
@@ -459,22 +406,10 @@
         parcel.setDataPosition(0);
         BufferingParams q = BufferingParams.CREATOR.createFromParcel(parcel);
 
-        assertEquals("initial buffering mode should match",
-                p.getInitialBufferingMode(), q.getInitialBufferingMode());
-        assertEquals("rebuffering mode should match",
-                p.getRebufferingMode(), q.getRebufferingMode());
-        assertEquals("initial buffering markMs should match",
-                p.getInitialBufferingWatermarkMs(), q.getInitialBufferingWatermarkMs());
-        assertEquals("initial buffering markKB should match",
-                p.getInitialBufferingWatermarkKB(), q.getInitialBufferingWatermarkKB());
-        assertEquals("rebuffering low markMs should match",
-                p.getRebufferingWatermarkLowMs(), q.getRebufferingWatermarkLowMs());
-        assertEquals("rebuffering low markKB should match",
-                p.getRebufferingWatermarkLowKB(), q.getRebufferingWatermarkLowKB());
-        assertEquals("rebuffering high markMs should match",
-                p.getRebufferingWatermarkHighMs(), q.getRebufferingWatermarkHighMs());
-        assertEquals("rebuffering high markKB should match",
-                p.getRebufferingWatermarkHighKB(), q.getRebufferingWatermarkHighKB());
+        assertEquals("initial markMs should match",
+                p.getInitialMarkMs(), q.getInitialMarkMs());
+        assertEquals("resume playback markMs should match",
+                p.getResumePlaybackMarkMs(), q.getResumePlaybackMarkMs());
 
         parcel.recycle();
     }
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index 7614568..2afad1e 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -19,25 +19,39 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
 import android.media.AudioRouting;
 import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.MediaFormat;
 import android.media.MediaRecorder;
 
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemClock;
 
 import android.test.AndroidTestCase;
 
 import android.util.Log;
 
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
 import java.lang.Runnable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
- * AudioTrack / AudioRecord preferred device and routing listener tests.
+ * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
+ * and routing listener tests.
  * The routing tests are mostly here to exercise the routing code, as an actual test would require
  * adding / removing an audio device for the listeners to be called.
  * The routing listener code is designed to run for two versions of the routing code:
@@ -46,8 +60,18 @@
  */
 public class RoutingTest extends AndroidTestCase {
     private static final String TAG = "RoutingTest";
+    private static final int MAX_WAITING_ROUTING_CHANGED_COUNT = 3;
+    private static final long WAIT_ROUTING_CHANGE_TIME_MS = 1000;
+    private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
+    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
+    private static final long MAX_FILE_SIZE_BYTE = 5000;
+    private static final int RECORD_TIME_MS = 3000;
+    private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
+        Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
 
     private AudioManager mAudioManager;
+    private CountDownLatch mRoutingChangedLatch;
+    private File mOutFile;
 
     @Override
     protected void setUp() throws Exception {
@@ -58,6 +82,14 @@
         assertNotNull(mAudioManager);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOutFile != null && mOutFile.exists()) {
+            mOutFile.delete();
+        }
+        super.tearDown();
+    }
+
     private AudioTrack allocAudioTrack() {
         int bufferSize =
                 AudioTrack.getMinBufferSize(
@@ -104,6 +136,66 @@
         audioTrack.release();
     }
 
+    public void test_audioTrack_incallMusicRoutingPermissions() {
+        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;
+        }
+
+        AudioTrack audioTrack = null;
+
+        try {
+            audioTrack = allocAudioTrack();
+            assertNotNull(audioTrack);
+
+            audioTrack.setPreferredDevice(telephonyDevice);
+            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, audioTrack.getPreferredDevice().getType());
+
+            audioTrack.play();
+            assertTrue(audioTrack.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
+
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.stop();
+                audioTrack.release();
+            }
+            mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+    }
+
+    private AudioDeviceInfo getTelephonyDeviceAndSetInCommunicationMode() {
+        // get the output device for telephony
+        AudioDeviceInfo telephonyDevice = null;
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                telephonyDevice = deviceList[index];
+            }
+        }
+
+        if (telephonyDevice == null) {
+            return null;
+        }
+
+        // simulate an in call state using MODE_IN_COMMUNICATION since
+        // AudioManager.setMode requires MODIFY_PHONE_STATE permission
+        // for setMode with MODE_IN_CALL.
+        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+        assertEquals(AudioManager.MODE_IN_COMMUNICATION, mAudioManager.getMode());
+
+        return telephonyDevice;
+    }
+
     /*
      * tests if the Looper for the current thread has been prepared,
      * If not, it makes one, prepares it and returns it.
@@ -455,4 +547,297 @@
         audioRecord.stop();
         audioRecord.release();
     }
+
+    private class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
+    {
+        public void onRoutingChanged(AudioRouting audioRouting) {
+            if (mRoutingChangedLatch != null) {
+                mRoutingChangedLatch.countDown();
+            }
+        }
+    }
+
+    private MediaPlayer allocMediaPlayer() {
+        final int resid = R.raw.testmp3_2;
+        MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resid);
+        mediaPlayer.setAudioAttributes(
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
+        mediaPlayer.start();
+        return mediaPlayer;
+    }
+
+    public void test_mediaPlayer_preferredDevice() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+        assertTrue(mediaPlayer.isPlaying());
+
+        // None selected (new MediaPlayer), so check for default
+        assertNull(mediaPlayer.getPreferredDevice());
+
+        // resets to default
+        assertTrue(mediaPlayer.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
+            assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(mediaPlayer.setPreferredDevice(null));
+        assertNull(mediaPlayer.getPreferredDevice());
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+    public void test_mediaPlayer_getRoutedDevice() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+        assertTrue(mediaPlayer.isPlaying());
+
+        // Sleep for 1s to ensure the output device open
+        SystemClock.sleep(1000);
+
+        // No explicit route
+        AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
+        assertNotNull(routedDevice);
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+    public void test_MediaPlayer_RoutingListener() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+
+        // null listener
+        mediaPlayer.addOnRoutingChangedListener(null, null);
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+        // add a listener
+        mediaPlayer.addOnRoutingChangedListener(listener, null);
+
+        // remove listeners
+        // remove a listener we didn't add
+        mediaPlayer.removeOnRoutingChangedListener(someOtherListener);
+        // remove a valid listener
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+
+        Looper myLooper = prepareIfNeededLooper();
+
+        mediaPlayer.addOnRoutingChangedListener(listener, new Handler());
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+
+    public void test_MediaPlayer_RoutingChangedCallback() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+        AudioRoutingListener listener = new AudioRoutingListener();
+        mediaPlayer.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(mediaPlayer.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 = mediaPlayer.getRoutedDevice();
+                if (routedDevice == null) {
+                    continue;
+                }
+                if (routedDevice.getId() == deviceList[index].getId()) {
+                    routingChanged = true;
+                    break;
+                }
+            }
+            assertTrue("Switching to device" + deviceList[index].getType() + " failed",
+                    routingChanged);
+        }
+
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+    public void test_mediaPlayer_incallMusicRoutingPermissions() {
+        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;
+        }
+
+        MediaPlayer mediaPlayer = null;
+
+        try {
+            mediaPlayer = allocMediaPlayer();
+
+            mediaPlayer.setPreferredDevice(telephonyDevice);
+            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, mediaPlayer.getPreferredDevice().getType());
+
+            // Sleep for 1s to ensure the output device open
+            SystemClock.sleep(1000);
+            assertTrue(mediaPlayer.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
+
+        } finally {
+            if (mediaPlayer != null) {
+                mediaPlayer.stop();
+                mediaPlayer.release();
+            }
+            mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+    }
+
+    private MediaRecorder allocMediaRecorder() throws Exception {
+        final String outputPath = new File(Environment.getExternalStorageDirectory(),
+            "record.out").getAbsolutePath();
+        mOutFile = new File(outputPath);
+        MediaRecorder mediaRecorder = new MediaRecorder();
+        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        assertEquals(0, mediaRecorder.getMaxAmplitude());
+        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mediaRecorder.setOutputFile(outputPath);
+        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mediaRecorder.setAudioChannels(AudioFormat.CHANNEL_OUT_DEFAULT);
+        mediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
+        mediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
+        mediaRecorder.setMaxFileSize(MAX_FILE_SIZE_BYTE);
+        mediaRecorder.prepare();
+        mediaRecorder.start();
+        // Sleep a while to ensure the underlying AudioRecord is initialized.
+        Thread.sleep(1000);
+        return mediaRecorder;
+    }
+
+    public void test_mediaRecorder_preferredDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+                || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        // None selected (new MediaPlayer), so check for default
+        assertNull(mediaRecorder.getPreferredDevice());
+
+        // resets to default
+        assertTrue(mediaRecorder.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (!AVAILABLE_INPUT_DEVICES_TYPE.contains(deviceList[index].getType())) {
+                // Only try to set devices whose type is contained in predefined set as preferred
+                // device in case of permission denied when switching input device.
+                continue;
+            }
+            assertTrue(mediaRecorder.setPreferredDevice(deviceList[index]));
+            assertTrue(mediaRecorder.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(mediaRecorder.setPreferredDevice(null));
+        assertNull(mediaRecorder.getPreferredDevice());
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+    }
+
+    public void test_mediaRecorder_getRoutedDeviceId() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        AudioDeviceInfo routedDevice = mediaRecorder.getRoutedDevice();
+        assertNotNull(routedDevice); // we probably can't say anything more than this
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+    }
+
+    public void test_mediaRecorder_RoutingListener() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        // null listener
+        mediaRecorder.addOnRoutingChangedListener(null, null);
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+        // add a listener
+        mediaRecorder.addOnRoutingChangedListener(listener, null);
+
+        // remove listeners we didn't add
+        mediaRecorder.removeOnRoutingChangedListener(someOtherListener);
+        // remove a valid listener
+        mediaRecorder.removeOnRoutingChangedListener(listener);
+
+        Looper myLooper = prepareIfNeededLooper();
+        mediaRecorder.addOnRoutingChangedListener(listener, new Handler());
+        mediaRecorder.removeOnRoutingChangedListener(listener);
+
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index ab54e75..3a0d512 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -391,10 +391,22 @@
                 return; // skip
             }
 
-            // getDefaultBufferingParams should be called after setDataSource.
+            // getBufferingParams should be called after setDataSource.
             try {
-                BufferingParams params = mMediaPlayer.getDefaultBufferingParams();
-                fail("MediaPlayer failed to check state for getDefaultBufferingParams");
+                BufferingParams params = mMediaPlayer.getBufferingParams();
+                fail("MediaPlayer failed to check state for getBufferingParams");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            // setBufferingParams should be called after setDataSource.
+            try {
+                BufferingParams params = new BufferingParams.Builder()
+                        .setInitialMarkMs(2)
+                        .setResumePlaybackMarkMs(3)
+                        .build();
+                mMediaPlayer.setBufferingParams(params);
+                fail("MediaPlayer failed to check state for setBufferingParams");
             } catch (IllegalStateException e) {
                 // expected
             }
@@ -421,33 +433,17 @@
 
             assertFalse(mOnBufferingUpdateCalled.isSignalled());
 
-            BufferingParams params = mMediaPlayer.getDefaultBufferingParams();
+            BufferingParams params = mMediaPlayer.getBufferingParams();
 
-            int newMark = -1;
-            BufferingParams newParams = null;
-            int initialBufferingMode = params.getInitialBufferingMode();
-            if (initialBufferingMode == BufferingParams.BUFFERING_MODE_SIZE_ONLY
-                    || initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE) {
-                newMark = params.getInitialBufferingWatermarkKB() + 1;
-                newParams = new BufferingParams.Builder(params).setInitialBufferingWatermarkKB(
-                        newMark).build();
-            } else if (initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_ONLY) {
-                newMark = params.getInitialBufferingWatermarkMs() + 1;
-                newParams = new BufferingParams.Builder(params).setInitialBufferingWatermarkMs(
-                        newMark).build();
-            } else {
-                newParams = params;
-            }
+            int newMark = params.getInitialMarkMs() + 2;
+            BufferingParams newParams =
+                    new BufferingParams.Builder(params).setInitialMarkMs(newMark).build();
+
             mMediaPlayer.setBufferingParams(newParams);
 
             int checkMark = -1;
             BufferingParams checkParams = mMediaPlayer.getBufferingParams();
-            if (initialBufferingMode == BufferingParams.BUFFERING_MODE_SIZE_ONLY
-                    || initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE) {
-                checkMark = checkParams.getInitialBufferingWatermarkKB();
-            } else if (initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_ONLY) {
-                checkMark = checkParams.getInitialBufferingWatermarkMs();
-            }
+            checkMark = checkParams.getInitialMarkMs();
             assertEquals("marks do not match", newMark, checkMark);
 
             // TODO: add more dynamic checking, e.g., buffering shall not exceed pre-set mark.
diff --git a/tests/tests/mediastress/Android.mk b/tests/tests/mediastress/Android.mk
index 0213c00..633708f 100644
--- a/tests/tests/mediastress/Android.mk
+++ b/tests/tests/mediastress/Android.mk
@@ -28,6 +28,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_HOST_SHARED_LIBRARIES := compatibility-device-media-preconditions
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediastress_jni libnativehelper_compat_libc++
diff --git a/tests/tests/mediastress/AndroidTest.xml b/tests/tests/mediastress/AndroidTest.xml
index 3572ab8..5d186b8 100644
--- a/tests/tests/mediastress/AndroidTest.xml
+++ b/tests/tests/mediastress/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Media Stress test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/midi/Android.mk b/tests/tests/midi/Android.mk
index cefe3cc..212c8ac 100755
--- a/tests/tests/midi/Android.mk
+++ b/tests/tests/midi/Android.mk
@@ -27,6 +27,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 # Must match the package name in CtsTestCaseList.mk
diff --git a/tests/tests/midi/AndroidTest.xml b/tests/tests/midi/AndroidTest.xml
index f0b3cce..d8d12e2 100644
--- a/tests/tests/midi/AndroidTest.xml
+++ b/tests/tests/midi/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS MIDI test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/multiuser/Android.mk b/tests/tests/multiuser/Android.mk
index 992b5fe..7c9ca7e 100644
--- a/tests/tests/multiuser/Android.mk
+++ b/tests/tests/multiuser/Android.mk
@@ -29,6 +29,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index 823ec5f..2edb2ec 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License
   -->
 <configuration description="Config for CTS Multiuser 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" />
diff --git a/tests/tests/nativehardware/Android.mk b/tests/tests/nativehardware/Android.mk
index 38688be..2e5f68b 100644
--- a/tests/tests/nativehardware/Android.mk
+++ b/tests/tests/nativehardware/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
 
-LOCAL_JAVA_LIBRARIES := platform-test-annotations
+LOCAL_JAVA_LIBRARIES := platform-test-annotations android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
@@ -87,7 +87,7 @@
 
 LOCAL_SDK_VERSION := current
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_NDK_STL_VARIANT := c++_shared
 
diff --git a/tests/tests/nativehardware/AndroidTest.xml b/tests/tests/nativehardware/AndroidTest.xml
index 074a962..43457b3 100644
--- a/tests/tests/nativehardware/AndroidTest.xml
+++ b/tests/tests/nativehardware/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativemedia/aaudio/Android.mk b/tests/tests/nativemedia/aaudio/Android.mk
index 2f64131..2017eba 100644
--- a/tests/tests/nativemedia/aaudio/Android.mk
+++ b/tests/tests/nativemedia/aaudio/Android.mk
@@ -1,4 +1,4 @@
-# Copyright 2017 The Android Open Source Project
+# 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.
@@ -12,40 +12,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := CtsNativeMediaAAudioTestCases
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
+
+LOCAL_PACKAGE_NAME := CtsNativeMediaAAudioTestCases
+
+# Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
 
-LOCAL_SRC_FILES := \
-    src/test_aaudio.cpp \
-    src/test_aaudio_callback.cpp \
-    src/test_aaudio_misc.cpp \
-    src/test_aaudio_mmap.cpp \
-    src/test_aaudio_stream_builder.cpp \
-    src/utils.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
-    libaaudio \
-    liblog \
-
-LOCAL_STATIC_LIBRARIES := \
-    libgtest_ndk_c++ \
-
-LOCAL_CTS_TEST_PACKAGE := android.nativemedia.aaudio
+# 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_CFLAGS := -Werror -Wall
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner nativetesthelper
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativeaaudiotest
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
 
-include $(BUILD_CTS_EXECUTABLE)
+include $(BUILD_CTS_PACKAGE)
+
+# Include the associated library's makefile.
+include $(LOCAL_PATH)/jni/Android.mk
diff --git a/tests/tests/nativemedia/aaudio/AndroidManifest.xml b/tests/tests/nativemedia/aaudio/AndroidManifest.xml
new file mode 100644
index 0000000..97bf5f9
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.nativemedia.aaudio">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.nativemedia.aaudio"
+                     android:label="CTS tests of native AAudio API">
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
index 7030d0c..b796ea6 100644
--- a/tests/tests/nativemedia/aaudio/AndroidTest.xml
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -15,16 +15,14 @@
 -->
 <configuration description="Config for CTS Native Media AAudio test cases">
     <option name="config-descriptor:metadata" key="component" value="media" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="cleanup" value="true" />
-        <option name="push" value="CtsNativeMediaAAudioTestCases->/data/local/tmp/CtsNativeMediaAAudioTestCases" />
-        <option name="append-bitness" value="true" />
+    <option name="test-suite-tag" value="cts" />
+    <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="CtsNativeMediaAAudioTestCases.apk" />
     </target_preparer>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="CtsNativeMediaAAudioTestCases" />
-        <option name="runtime-hint" value="2m" />
-        <!-- test-timeout unit is ms, value = 2 min -->
-        <option name="native-test-timeout" value="120000" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.nativemedia.aaudio" />
+        <option name="runtime-hint" value="2m0s" />
     </test>
 </configuration>
diff --git a/tests/tests/nativemedia/aaudio/jni/Android.mk b/tests/tests/nativemedia/aaudio/jni/Android.mk
new file mode 100644
index 0000000..361245e
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+# Build the unit tests.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libnativeaaudiotest
+LOCAL_MULTILIB := both
+
+LOCAL_SRC_FILES := \
+    test_aaudio.cpp \
+    test_aaudio_attributes.cpp \
+    test_aaudio_callback.cpp \
+    test_aaudio_mmap.cpp \
+    test_aaudio_misc.cpp \
+    test_aaudio_stream_builder.cpp \
+    test_session_id.cpp \
+    utils.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+    libaaudio \
+    liblog
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
+
+LOCAL_CFLAGS := -Werror -Wall
+
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.h b/tests/tests/nativemedia/aaudio/jni/test_aaudio.h
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio.h
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio.h
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
new file mode 100644
index 0000000..6143613
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
@@ -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.
+ */
+
+// Test AAudio attributes such as Usage, ContentType and InputPreset.
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+#include <gtest/gtest.h>
+
+constexpr int64_t kNanosPerSecond = 1000000000;
+constexpr int kNumFrames = 256;
+constexpr int kChannelCount = 2;
+
+constexpr int32_t DONT_SET = -1000;
+
+static void checkAttributes(aaudio_performance_mode_t perfMode,
+                            aaudio_usage_t usage,
+                            aaudio_content_type_t contentType,
+                            aaudio_input_preset_t preset = DONT_SET,
+                            aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    AAudioStream *aaudioStream = nullptr;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+
+    // Set the attribute in the builder.
+    if (usage != DONT_SET) {
+        AAudioStreamBuilder_setUsage(aaudioBuilder, usage);
+    }
+    if (contentType != DONT_SET) {
+        AAudioStreamBuilder_setContentType(aaudioBuilder, contentType);
+    }
+    if (preset != DONT_SET) {
+        AAudioStreamBuilder_setInputPreset(aaudioBuilder, preset);
+    }
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+    AAudioStreamBuilder_delete(aaudioBuilder);
+
+    // Make sure we get the same attributes back from the stream.
+    aaudio_usage_t expectedUsage =
+            (usage == DONT_SET || usage == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_USAGE_MEDIA // default
+            : usage;
+    EXPECT_EQ(expectedUsage, AAudioStream_getUsage(aaudioStream));
+
+    aaudio_content_type_t expectedContentType =
+            (contentType == DONT_SET || contentType == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_CONTENT_TYPE_MUSIC // default
+            : contentType;
+    EXPECT_EQ(expectedContentType, AAudioStream_getContentType(aaudioStream));
+
+    aaudio_input_preset_t expectedPreset =
+            (preset == DONT_SET || preset == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION // default
+            : preset;
+    EXPECT_EQ(expectedPreset, AAudioStream_getInputPreset(aaudioStream));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+    if (direction == AAUDIO_DIRECTION_INPUT) {
+        EXPECT_EQ(kNumFrames,
+                  AAudioStream_read(aaudioStream, buffer, kNumFrames, kNanosPerSecond));
+    } else {
+        EXPECT_EQ(kNumFrames,
+                  AAudioStream_write(aaudioStream, buffer, kNumFrames, kNanosPerSecond));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+    delete[] buffer;
+}
+
+static const aaudio_usage_t sUsages[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_USAGE_MEDIA,
+    AAUDIO_USAGE_VOICE_COMMUNICATION,
+    AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING,
+    AAUDIO_USAGE_ALARM,
+    AAUDIO_USAGE_NOTIFICATION,
+    AAUDIO_USAGE_NOTIFICATION_RINGTONE,
+    AAUDIO_USAGE_NOTIFICATION_EVENT,
+    AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY,
+    AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+    AAUDIO_USAGE_ASSISTANCE_SONIFICATION,
+    AAUDIO_USAGE_GAME,
+    AAUDIO_USAGE_ASSISTANT
+};
+
+static const aaudio_content_type_t sContentypes[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_CONTENT_TYPE_SPEECH,
+    AAUDIO_CONTENT_TYPE_MUSIC,
+    AAUDIO_CONTENT_TYPE_MOVIE,
+    AAUDIO_CONTENT_TYPE_SONIFICATION
+};
+
+static const aaudio_input_preset_t sInputPresets[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_INPUT_PRESET_GENERIC,
+    AAUDIO_INPUT_PRESET_CAMCORDER,
+    AAUDIO_INPUT_PRESET_VOICE_RECOGNITION,
+    AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION,
+    AAUDIO_INPUT_PRESET_UNPROCESSED,
+};
+
+static void checkAttributesUsage(aaudio_performance_mode_t perfMode) {
+    for (aaudio_usage_t usage : sUsages) {
+        checkAttributes(perfMode, usage, DONT_SET);
+    }
+}
+
+static void checkAttributesContentType(aaudio_input_preset_t perfMode) {
+    for (aaudio_content_type_t contentType : sContentypes) {
+        checkAttributes(perfMode, DONT_SET, contentType);
+    }
+}
+
+static void checkAttributesInputPreset(aaudio_performance_mode_t perfMode) {
+    for (aaudio_input_preset_t inputPreset : sInputPresets) {
+        checkAttributes(perfMode,
+                        DONT_SET,
+                        DONT_SET,
+                        inputPreset,
+                        AAUDIO_DIRECTION_INPUT);
+    }
+}
+
+TEST(test_attributes, aaudio_usage_perfnone) {
+    checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_content_type_perfnone) {
+    checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_input_preset_perfnone) {
+    checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_usage_lowlat) {
+    checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+TEST(test_attributes, aaudio_content_type_lowlat) {
+    checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+TEST(test_attributes, aaudio_input_preset_lowlat) {
+    checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
new file mode 100644
index 0000000..01ed9f6
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License", ENUM_CANNOT_CHANGE);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "AAudioTest"
+
+#include <aaudio/AAudio.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+// Make sure enums do not change value.
+TEST(test_aaudio_misc, aaudio_freeze_enums) {
+
+#define ENUM_CANNOT_CHANGE "enum in API cannot change"
+
+    static_assert(0 == AAUDIO_DIRECTION_OUTPUT, ENUM_CANNOT_CHANGE);
+    static_assert(1 == AAUDIO_DIRECTION_INPUT, ENUM_CANNOT_CHANGE);
+
+    static_assert(-1 == AAUDIO_FORMAT_INVALID, ENUM_CANNOT_CHANGE);
+    static_assert(0 == AAUDIO_FORMAT_UNSPECIFIED, ENUM_CANNOT_CHANGE);
+    static_assert(1 == AAUDIO_FORMAT_PCM_I16, ENUM_CANNOT_CHANGE);
+    static_assert(2 == AAUDIO_FORMAT_PCM_FLOAT, ENUM_CANNOT_CHANGE);
+
+    static_assert(0 == AAUDIO_OK, ENUM_CANNOT_CHANGE);
+    static_assert(-900 == AAUDIO_ERROR_BASE, ENUM_CANNOT_CHANGE);
+    static_assert(-899 == AAUDIO_ERROR_DISCONNECTED, ENUM_CANNOT_CHANGE);
+    static_assert(-898 == AAUDIO_ERROR_ILLEGAL_ARGUMENT, ENUM_CANNOT_CHANGE);
+    // reserved
+    static_assert(-896 == AAUDIO_ERROR_INTERNAL, ENUM_CANNOT_CHANGE);
+    static_assert(-895 == AAUDIO_ERROR_INVALID_STATE, ENUM_CANNOT_CHANGE);
+    // reserved
+    // reserved
+    static_assert(-892 == AAUDIO_ERROR_INVALID_HANDLE, ENUM_CANNOT_CHANGE);
+    // reserved
+    static_assert(-890 == AAUDIO_ERROR_UNIMPLEMENTED, ENUM_CANNOT_CHANGE);
+    static_assert(-889 == AAUDIO_ERROR_UNAVAILABLE, ENUM_CANNOT_CHANGE);
+    static_assert(-888 == AAUDIO_ERROR_NO_FREE_HANDLES, ENUM_CANNOT_CHANGE);
+    static_assert(-887 == AAUDIO_ERROR_NO_MEMORY, ENUM_CANNOT_CHANGE);
+    static_assert(-886 == AAUDIO_ERROR_NULL, ENUM_CANNOT_CHANGE);
+    static_assert(-885 == AAUDIO_ERROR_TIMEOUT, ENUM_CANNOT_CHANGE);
+    static_assert(-884 == AAUDIO_ERROR_WOULD_BLOCK, ENUM_CANNOT_CHANGE);
+    static_assert(-883 == AAUDIO_ERROR_INVALID_FORMAT, ENUM_CANNOT_CHANGE);
+    static_assert(-882 == AAUDIO_ERROR_OUT_OF_RANGE, ENUM_CANNOT_CHANGE);
+    static_assert(-881 == AAUDIO_ERROR_NO_SERVICE, ENUM_CANNOT_CHANGE);
+
+    static_assert(0 == AAUDIO_STREAM_STATE_UNINITIALIZED, ENUM_CANNOT_CHANGE);
+    static_assert(1 == AAUDIO_STREAM_STATE_UNKNOWN, ENUM_CANNOT_CHANGE);
+    static_assert(2 == AAUDIO_STREAM_STATE_OPEN, ENUM_CANNOT_CHANGE);
+    static_assert(3 == AAUDIO_STREAM_STATE_STARTING, ENUM_CANNOT_CHANGE);
+    static_assert(4 == AAUDIO_STREAM_STATE_STARTED, ENUM_CANNOT_CHANGE);
+    static_assert(5 == AAUDIO_STREAM_STATE_PAUSING, ENUM_CANNOT_CHANGE);
+    static_assert(6 == AAUDIO_STREAM_STATE_PAUSED, ENUM_CANNOT_CHANGE);
+    static_assert(7 == AAUDIO_STREAM_STATE_FLUSHING, ENUM_CANNOT_CHANGE);
+    static_assert(8 == AAUDIO_STREAM_STATE_FLUSHED, ENUM_CANNOT_CHANGE);
+    static_assert(9 == AAUDIO_STREAM_STATE_STOPPING, ENUM_CANNOT_CHANGE);
+    static_assert(10 == AAUDIO_STREAM_STATE_STOPPED, ENUM_CANNOT_CHANGE);
+    static_assert(11 == AAUDIO_STREAM_STATE_CLOSING, ENUM_CANNOT_CHANGE);
+    static_assert(12 == AAUDIO_STREAM_STATE_CLOSED, ENUM_CANNOT_CHANGE);
+
+    static_assert(0 == AAUDIO_SHARING_MODE_EXCLUSIVE, ENUM_CANNOT_CHANGE);
+    static_assert(1 == AAUDIO_SHARING_MODE_SHARED, ENUM_CANNOT_CHANGE);
+
+    static_assert(0 == AAUDIO_CALLBACK_RESULT_CONTINUE, ENUM_CANNOT_CHANGE);
+    static_assert(1 == AAUDIO_CALLBACK_RESULT_STOP, ENUM_CANNOT_CHANGE);
+
+    static_assert(1 == AAUDIO_USAGE_MEDIA, ENUM_CANNOT_CHANGE);
+    static_assert(2 == AAUDIO_USAGE_VOICE_COMMUNICATION, ENUM_CANNOT_CHANGE);
+    static_assert(3 == AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, ENUM_CANNOT_CHANGE);
+    static_assert(4 == AAUDIO_USAGE_ALARM, ENUM_CANNOT_CHANGE);
+    static_assert(5 == AAUDIO_USAGE_NOTIFICATION, ENUM_CANNOT_CHANGE);
+    static_assert(6 == AAUDIO_USAGE_NOTIFICATION_RINGTONE, ENUM_CANNOT_CHANGE);
+    static_assert(10 == AAUDIO_USAGE_NOTIFICATION_EVENT, ENUM_CANNOT_CHANGE);
+    static_assert(11 == AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, ENUM_CANNOT_CHANGE);
+    static_assert(12 == AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ENUM_CANNOT_CHANGE);
+    static_assert(13 == AAUDIO_USAGE_ASSISTANCE_SONIFICATION, ENUM_CANNOT_CHANGE);
+    static_assert(14 == AAUDIO_USAGE_GAME, ENUM_CANNOT_CHANGE);
+    static_assert(16 == AAUDIO_USAGE_ASSISTANT, ENUM_CANNOT_CHANGE);
+
+    static_assert(1 == AAUDIO_CONTENT_TYPE_SPEECH, ENUM_CANNOT_CHANGE);
+    static_assert(2 == AAUDIO_CONTENT_TYPE_MUSIC, ENUM_CANNOT_CHANGE);
+    static_assert(3 == AAUDIO_CONTENT_TYPE_MOVIE, ENUM_CANNOT_CHANGE);
+    static_assert(4 == AAUDIO_CONTENT_TYPE_SONIFICATION, ENUM_CANNOT_CHANGE);
+
+    static_assert(1 == AAUDIO_INPUT_PRESET_GENERIC, ENUM_CANNOT_CHANGE);
+    static_assert(5 == AAUDIO_INPUT_PRESET_CAMCORDER, ENUM_CANNOT_CHANGE);
+    static_assert(6 == AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, ENUM_CANNOT_CHANGE);
+    static_assert(7 == AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, ENUM_CANNOT_CHANGE);
+    static_assert(9 == AAUDIO_INPUT_PRESET_UNPROCESSED, ENUM_CANNOT_CHANGE);
+}
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_mmap.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_mmap.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_mmap.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_mmap.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
diff --git a/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp b/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
new file mode 100644
index 0000000..fa82f71
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Test AAudio SessionId, which is used to associate Effects with a stream
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+#include <gtest/gtest.h>
+
+#include "test_aaudio.h"
+
+constexpr int kNumFrames = 256;
+constexpr int kChannelCount = 2;
+
+// Test AAUDIO_SESSION_ID_NONE default
+static void checkSessionIdNone(aaudio_performance_mode_t perfMode) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+    AAudioStream *aaudioStream1 = nullptr;
+    int32_t       sessionId1 = 0;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+    // Since we did not request or specify a SessionID, we should get NONE
+    sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+    ASSERT_EQ(AAUDIO_SESSION_ID_NONE, sessionId1);
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+    ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1, buffer, kNumFrames, NANOS_PER_SECOND));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+    delete[] buffer;
+    AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_none_perfnone) {
+    checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_session_id, aaudio_session_id_none_lowlat) {
+    checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+// Test AAUDIO_SESSION_ID_ALLOCATE
+static void checkSessionIdAllocate(aaudio_performance_mode_t perfMode,
+                                   aaudio_direction_t direction) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+    AAudioStream *aaudioStream1 = nullptr;
+    int32_t       sessionId1 = 0;
+    AAudioStream *aaudioStream2 = nullptr;
+    int32_t       sessionId2 = 0;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+    // This stream could be input or output.
+    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+
+    // Ask AAudio to allocate a Session ID.
+    AAudioStreamBuilder_setSessionId(aaudioBuilder, AAUDIO_SESSION_ID_ALLOCATE);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+    // Get the allocated ID from the stream.
+    sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+
+    // Check for invalid session IDs.
+    ASSERT_NE(AAUDIO_SESSION_ID_NONE, sessionId1);
+    ASSERT_NE(AAUDIO_SESSION_ID_ALLOCATE, sessionId1);
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+    if (direction == AAUDIO_DIRECTION_INPUT) {
+        ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream1,
+                                                buffer, kNumFrames, NANOS_PER_SECOND));
+    } else {
+        ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1,
+                                         buffer, kNumFrames, NANOS_PER_SECOND));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+    // Now open a second stream using the same session ID. ==================
+    AAudioStreamBuilder_setSessionId(aaudioBuilder, sessionId1);
+
+    // Reverse direction for second stream.
+    aaudio_direction_t otherDirection = (direction == AAUDIO_DIRECTION_OUTPUT)
+                                        ? AAUDIO_DIRECTION_INPUT
+                                        : AAUDIO_DIRECTION_OUTPUT;
+    AAudioStreamBuilder_setDirection(aaudioBuilder, otherDirection);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream2));
+
+    // Get the allocated ID from the stream.
+    // It should match the ID that we set it to in the builder.
+    sessionId2 = AAudioStream_getSessionId(aaudioStream2);
+    ASSERT_EQ(sessionId1, sessionId2);
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream2));
+
+    if (otherDirection == AAUDIO_DIRECTION_INPUT) {
+        ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream2,
+                                                 buffer, kNumFrames, NANOS_PER_SECOND));
+    } else {
+        ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream2,
+                                                 buffer, kNumFrames, NANOS_PER_SECOND));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream2));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream2));
+
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+    delete[] buffer;
+    AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_in) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_out) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_OUTPUT);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_in) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_out) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_OUTPUT);
+}
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.cpp b/tests/tests/nativemedia/aaudio/jni/utils.cpp
new file mode 100644
index 0000000..55d1e13
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/utils.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AAudioTest"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+#include "test_aaudio.h"
+#include "utils.h"
+
+int64_t getNanoseconds(clockid_t clockId) {
+    struct timespec time;
+    int result = clock_gettime(clockId, &time);
+    if (result < 0) {
+        return -errno;
+    }
+    return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
+}
+
+const char* performanceModeToString(aaudio_performance_mode_t mode) {
+    switch (mode) {
+        case AAUDIO_PERFORMANCE_MODE_NONE: return "DEFAULT";
+        case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: return "POWER_SAVING";
+        case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: return "LOW_LATENCY";
+    }
+    return "UNKNOWN";
+}
+
+const char* sharingModeToString(aaudio_sharing_mode_t mode) {
+    switch (mode) {
+        case AAUDIO_SHARING_MODE_SHARED: return "SHARED";
+        case AAUDIO_SHARING_MODE_EXCLUSIVE: return "EXCLUSIVE";
+    }
+    return "UNKNOWN";
+}
+
+
+// These periods are quite generous. They are not designed to put
+// any restrictions on the implementation, but only to ensure sanity.
+// Use int64_t because 96000 * 30000 is close to int32_t limits.
+const std::map<aaudio_performance_mode_t, int64_t> StreamBuilderHelper::sMaxFramesPerBurstMs =
+{ { AAUDIO_PERFORMANCE_MODE_NONE, 128 },
+  { AAUDIO_PERFORMANCE_MODE_POWER_SAVING, 30 * 1000 },
+  { AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, 40 } };
+
+StreamBuilderHelper::StreamBuilderHelper(
+        aaudio_direction_t direction, int32_t sampleRate,
+        int32_t channelCount, aaudio_format_t dataFormat,
+        aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode)
+        : mDirection{direction},
+          mRequested{sampleRate, channelCount, dataFormat, sharingMode, perfMode},
+          mActual{0, 0, AAUDIO_FORMAT_INVALID, -1, -1}, mFramesPerBurst{-1},
+          mBuilder{nullptr}, mStream{nullptr} {}
+
+StreamBuilderHelper::~StreamBuilderHelper() {
+    close();
+}
+
+void StreamBuilderHelper::initBuilder() {
+    ASSERT_EQ(1U, sMaxFramesPerBurstMs.count(mRequested.perfMode));
+
+    // Use an AAudioStreamBuilder to define the stream.
+    aaudio_result_t result = AAudio_createStreamBuilder(&mBuilder);
+    ASSERT_EQ(AAUDIO_OK, result);
+    ASSERT_TRUE(mBuilder != nullptr);
+
+    // Request stream properties.
+    AAudioStreamBuilder_setDeviceId(mBuilder, AAUDIO_UNSPECIFIED);
+    AAudioStreamBuilder_setDirection(mBuilder, mDirection);
+    AAudioStreamBuilder_setSampleRate(mBuilder, mRequested.sampleRate);
+    AAudioStreamBuilder_setChannelCount(mBuilder, mRequested.channelCount);
+    AAudioStreamBuilder_setFormat(mBuilder, mRequested.dataFormat);
+    AAudioStreamBuilder_setSharingMode(mBuilder, mRequested.sharingMode);
+    AAudioStreamBuilder_setPerformanceMode(mBuilder, mRequested.perfMode);
+}
+
+// Needs to be a 'void' function due to ASSERT requirements.
+void StreamBuilderHelper::createAndVerifyStream(bool *success) {
+    *success = false;
+
+    aaudio_result_t result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+    if (mRequested.sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE && result != AAUDIO_OK) {
+        __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "Could not open a stream in EXCLUSIVE mode");
+        return;
+    }
+    ASSERT_EQ(AAUDIO_OK, result);
+    ASSERT_TRUE(mStream != nullptr);
+    ASSERT_EQ(AAUDIO_STREAM_STATE_OPEN, AAudioStream_getState(mStream));
+    ASSERT_EQ(mDirection, AAudioStream_getDirection(mStream));
+
+    mActual.sharingMode = AAudioStream_getSharingMode(mStream);
+    if (mActual.sharingMode != mRequested.sharingMode) {
+        // Since we are covering all possible values, the "actual" mode
+        // will also be tested, so no need to run the same test twice.
+        __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Sharing mode %s is not available",
+                sharingModeToString(mRequested.sharingMode));
+        return;
+    }
+
+    // Check to see what kind of stream we actually got.
+    mActual.sampleRate = AAudioStream_getSampleRate(mStream);
+    ASSERT_GE(mActual.sampleRate, 44100);
+    ASSERT_LE(mActual.sampleRate, 96000); // TODO what is min/max?
+
+    mActual.channelCount = AAudioStream_getChannelCount(mStream);
+    ASSERT_GE(mActual.channelCount, 1);
+    ASSERT_LE(mActual.channelCount, 16); // TODO what is min/max?
+
+    mActual.dataFormat = AAudioStream_getFormat(mStream);
+    ASSERT_EQ(AAUDIO_FORMAT_PCM_I16, mActual.dataFormat);
+
+    mActual.perfMode = AAudioStream_getPerformanceMode(mStream);
+    if (mRequested.perfMode != AAUDIO_PERFORMANCE_MODE_NONE
+            && mRequested.perfMode != mActual.perfMode) {
+        // Since we are covering all possible values, the "actual" mode
+        // will also be tested, so no need to run the same test twice.
+        __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Performance mode %s is not available",
+                performanceModeToString(mRequested.sharingMode));
+        return;
+    }
+
+    mFramesPerBurst = AAudioStream_getFramesPerBurst(mStream);
+    ASSERT_GE(mFramesPerBurst, 16);
+    const int32_t maxFramesPerBurst =
+            mActual.sampleRate * sMaxFramesPerBurstMs.at(mActual.perfMode) / MILLIS_PER_SECOND;
+    ASSERT_LE(mFramesPerBurst, maxFramesPerBurst);
+
+    int32_t actualBufferSize = AAudioStream_getBufferSizeInFrames(mStream);
+    ASSERT_GT(actualBufferSize, 0);
+    ASSERT_GT(AAudioStream_setBufferSizeInFrames(mStream, actualBufferSize), 0);
+
+    *success = true;
+}
+
+void StreamBuilderHelper::close() {
+    if (mBuilder != nullptr) {
+        ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(mBuilder));
+    }
+    if (mStream != nullptr) {
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_close(mStream));
+    }
+}
+
+void StreamBuilderHelper::streamCommand(
+        StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState) {
+    ASSERT_EQ(AAUDIO_OK, cmd(mStream));
+    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
+    ASSERT_EQ(AAUDIO_OK,
+            AAudioStream_waitForStateChange(mStream, fromState, &state, DEFAULT_STATE_TIMEOUT));
+    ASSERT_EQ(toState, state);
+}
+
+
+InputStreamBuilderHelper::InputStreamBuilderHelper(
+        aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
+        : StreamBuilderHelper{AAUDIO_DIRECTION_INPUT,
+            48000, 1, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
+
+
+OutputStreamBuilderHelper::OutputStreamBuilderHelper(
+        aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
+        : StreamBuilderHelper{AAUDIO_DIRECTION_OUTPUT,
+            48000, 2, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
+
+void OutputStreamBuilderHelper::initBuilder() {
+    StreamBuilderHelper::initBuilder();
+    AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
+}
+
+void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
+    StreamBuilderHelper::createAndVerifyStream(success);
+    if (*success) {
+        ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
+    }
+}
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.h b/tests/tests/nativemedia/aaudio/jni/utils.h
new file mode 100644
index 0000000..7f38fef
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/utils.h
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+#ifndef CTS_MEDIA_TEST_AAUDIO_UTILS_H
+#define CTS_MEDIA_TEST_AAUDIO_UTILS_H
+
+#include <map>
+
+#include <aaudio/AAudio.h>
+
+int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC);
+const char* performanceModeToString(aaudio_performance_mode_t mode);
+const char* sharingModeToString(aaudio_sharing_mode_t mode);
+
+class StreamBuilderHelper {
+  public:
+    struct Parameters {
+        int32_t sampleRate;
+        int32_t channelCount;
+        aaudio_format_t dataFormat;
+        aaudio_sharing_mode_t sharingMode;
+        aaudio_performance_mode_t perfMode;
+    };
+
+    void initBuilder();
+    void createAndVerifyStream(bool *success);
+    void close();
+
+    void startStream() {
+        streamCommand(&AAudioStream_requestStart,
+                AAUDIO_STREAM_STATE_STARTING, AAUDIO_STREAM_STATE_STARTED);
+    }
+    void pauseStream() {
+        streamCommand(&AAudioStream_requestPause,
+                AAUDIO_STREAM_STATE_PAUSING, AAUDIO_STREAM_STATE_PAUSED);
+    }
+    void stopStream() {
+        streamCommand(&AAudioStream_requestStop,
+                AAUDIO_STREAM_STATE_STOPPING, AAUDIO_STREAM_STATE_STOPPED);
+    }
+    void flushStream() {
+        streamCommand(&AAudioStream_requestFlush,
+                AAUDIO_STREAM_STATE_FLUSHING, AAUDIO_STREAM_STATE_FLUSHED);
+    }
+
+    AAudioStreamBuilder* builder() const { return mBuilder; }
+    AAudioStream* stream() const { return mStream; }
+    const Parameters& actual() const { return mActual; }
+    int32_t framesPerBurst() const { return mFramesPerBurst; }
+
+  protected:
+    StreamBuilderHelper(aaudio_direction_t direction, int32_t sampleRate,
+            int32_t channelCount, aaudio_format_t dataFormat,
+            aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode);
+    ~StreamBuilderHelper();
+
+    typedef aaudio_result_t (StreamCommand)(AAudioStream*);
+    void streamCommand(
+            StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState);
+
+    static const std::map<aaudio_performance_mode_t, int64_t> sMaxFramesPerBurstMs;
+    const aaudio_direction_t mDirection;
+    const Parameters mRequested;
+    Parameters mActual;
+    int32_t mFramesPerBurst;
+    AAudioStreamBuilder *mBuilder;
+    AAudioStream *mStream;
+};
+
+class InputStreamBuilderHelper : public StreamBuilderHelper {
+  public:
+    InputStreamBuilderHelper(
+            aaudio_sharing_mode_t requestedSharingMode,
+            aaudio_performance_mode_t requestedPerfMode);
+};
+
+class OutputStreamBuilderHelper : public StreamBuilderHelper {
+  public:
+    OutputStreamBuilderHelper(
+            aaudio_sharing_mode_t requestedSharingMode,
+            aaudio_performance_mode_t requestedPerfMode);
+    void initBuilder();
+    void createAndVerifyStream(bool *success);
+
+  private:
+    const int32_t kBufferCapacityFrames = 2000;
+};
+
+#endif  // CTS_MEDIA_TEST_AAUDIO_UTILS_H
diff --git a/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java b/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java
new file mode 100644
index 0000000..afb2db4
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java
@@ -0,0 +1,25 @@
+/*
+ * 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.nativemedia.aaudio;
+
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
+
+@RunWith(GtestRunner.class)
+@TargetLibrary("nativeaaudiotest")
+public class AAudioTests {}
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_misc.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio_misc.cpp
deleted file mode 100644
index 42c9f42..0000000
--- a/tests/tests/nativemedia/aaudio/src/test_aaudio_misc.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License", ENUM_CANNOT_CHANGE);
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_NDEBUG 0
-#define LOG_TAG "AAudioTest"
-
-#include <aaudio/AAudio.h>
-#include <android/log.h>
-#include <gtest/gtest.h>
-
-// Make sure enums do not change value.
-TEST(test_aaudio_misc, aaudio_freeze_enums) {
-
-#define ENUM_CANNOT_CHANGE "enum in API cannot change"
-
-    static_assert(0 == AAUDIO_DIRECTION_OUTPUT, ENUM_CANNOT_CHANGE);
-    static_assert(1 == AAUDIO_DIRECTION_INPUT, ENUM_CANNOT_CHANGE);
-
-    static_assert(-1 == AAUDIO_FORMAT_INVALID, ENUM_CANNOT_CHANGE);
-    static_assert(0 == AAUDIO_FORMAT_UNSPECIFIED, ENUM_CANNOT_CHANGE);
-    static_assert(1 == AAUDIO_FORMAT_PCM_I16, ENUM_CANNOT_CHANGE);
-    static_assert(2 == AAUDIO_FORMAT_PCM_FLOAT, ENUM_CANNOT_CHANGE);
-
-    static_assert(0 == AAUDIO_OK, ENUM_CANNOT_CHANGE);
-    static_assert(-900 == AAUDIO_ERROR_BASE, ENUM_CANNOT_CHANGE);
-    static_assert(-899 == AAUDIO_ERROR_DISCONNECTED, ENUM_CANNOT_CHANGE);
-    static_assert(-898 == AAUDIO_ERROR_ILLEGAL_ARGUMENT, ENUM_CANNOT_CHANGE);
-    // reserved
-    static_assert(-896 == AAUDIO_ERROR_INTERNAL, ENUM_CANNOT_CHANGE);
-    static_assert(-895 == AAUDIO_ERROR_INVALID_STATE, ENUM_CANNOT_CHANGE);
-    // reserved
-    // reserved
-    static_assert(-892 == AAUDIO_ERROR_INVALID_HANDLE, ENUM_CANNOT_CHANGE);
-    // reserved
-    static_assert(-890 == AAUDIO_ERROR_UNIMPLEMENTED, ENUM_CANNOT_CHANGE);
-    static_assert(-889 == AAUDIO_ERROR_UNAVAILABLE, ENUM_CANNOT_CHANGE);
-    static_assert(-888 == AAUDIO_ERROR_NO_FREE_HANDLES, ENUM_CANNOT_CHANGE);
-    static_assert(-887 == AAUDIO_ERROR_NO_MEMORY, ENUM_CANNOT_CHANGE);
-    static_assert(-886 == AAUDIO_ERROR_NULL, ENUM_CANNOT_CHANGE);
-    static_assert(-885 == AAUDIO_ERROR_TIMEOUT, ENUM_CANNOT_CHANGE);
-    static_assert(-884 == AAUDIO_ERROR_WOULD_BLOCK, ENUM_CANNOT_CHANGE);
-    static_assert(-883 == AAUDIO_ERROR_INVALID_FORMAT, ENUM_CANNOT_CHANGE);
-    static_assert(-882 == AAUDIO_ERROR_OUT_OF_RANGE, ENUM_CANNOT_CHANGE);
-    static_assert(-881 == AAUDIO_ERROR_NO_SERVICE, ENUM_CANNOT_CHANGE);
-
-    static_assert(0 == AAUDIO_STREAM_STATE_UNINITIALIZED, ENUM_CANNOT_CHANGE);
-    static_assert(1 == AAUDIO_STREAM_STATE_UNKNOWN, ENUM_CANNOT_CHANGE);
-    static_assert(2 == AAUDIO_STREAM_STATE_OPEN, ENUM_CANNOT_CHANGE);
-    static_assert(3 == AAUDIO_STREAM_STATE_STARTING, ENUM_CANNOT_CHANGE);
-    static_assert(4 == AAUDIO_STREAM_STATE_STARTED, ENUM_CANNOT_CHANGE);
-    static_assert(5 == AAUDIO_STREAM_STATE_PAUSING, ENUM_CANNOT_CHANGE);
-    static_assert(6 == AAUDIO_STREAM_STATE_PAUSED, ENUM_CANNOT_CHANGE);
-    static_assert(7 == AAUDIO_STREAM_STATE_FLUSHING, ENUM_CANNOT_CHANGE);
-    static_assert(8 == AAUDIO_STREAM_STATE_FLUSHED, ENUM_CANNOT_CHANGE);
-    static_assert(9 == AAUDIO_STREAM_STATE_STOPPING, ENUM_CANNOT_CHANGE);
-    static_assert(10 == AAUDIO_STREAM_STATE_STOPPED, ENUM_CANNOT_CHANGE);
-    static_assert(11 == AAUDIO_STREAM_STATE_CLOSING, ENUM_CANNOT_CHANGE);
-    static_assert(12 == AAUDIO_STREAM_STATE_CLOSED, ENUM_CANNOT_CHANGE);
-
-    static_assert(0 == AAUDIO_SHARING_MODE_EXCLUSIVE, ENUM_CANNOT_CHANGE);
-    static_assert(1 == AAUDIO_SHARING_MODE_SHARED, ENUM_CANNOT_CHANGE);
-
-    static_assert(0 == AAUDIO_CALLBACK_RESULT_CONTINUE, ENUM_CANNOT_CHANGE);
-    static_assert(1 == AAUDIO_CALLBACK_RESULT_STOP, ENUM_CANNOT_CHANGE);
-}
diff --git a/tests/tests/nativemedia/aaudio/src/utils.cpp b/tests/tests/nativemedia/aaudio/src/utils.cpp
deleted file mode 100644
index b039f4e..0000000
--- a/tests/tests/nativemedia/aaudio/src/utils.cpp
+++ /dev/null
@@ -1,208 +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.
- */
-
-#define LOG_TAG "AAudioTest"
-
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-
-#include "test_aaudio.h"
-#include "utils.h"
-
-int64_t getNanoseconds(clockid_t clockId) {
-    struct timespec time;
-    int result = clock_gettime(clockId, &time);
-    if (result < 0) {
-        return -errno;
-    }
-    return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
-}
-
-const char* performanceModeToString(aaudio_performance_mode_t mode) {
-    switch (mode) {
-        case AAUDIO_PERFORMANCE_MODE_NONE: return "DEFAULT";
-        case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: return "POWER_SAVING";
-        case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: return "LOW_LATENCY";
-    }
-    return "UNKNOWN";
-}
-
-const char* sharingModeToString(aaudio_sharing_mode_t mode) {
-    switch (mode) {
-        case AAUDIO_SHARING_MODE_SHARED: return "SHARED";
-        case AAUDIO_SHARING_MODE_EXCLUSIVE: return "EXCLUSIVE";
-    }
-    return "UNKNOWN";
-}
-
-
-// These periods are quite generous. They are not designed to put
-// any restrictions on the implementation, but only to ensure sanity.
-// Use int64_t because 96000 * 30000 is close to int32_t limits.
-const std::map<aaudio_performance_mode_t, int64_t> StreamBuilderHelper::sMaxFramesPerBurstMs =
-{ { AAUDIO_PERFORMANCE_MODE_NONE, 128 },
-  { AAUDIO_PERFORMANCE_MODE_POWER_SAVING, 30 * 1000 },
-  { AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, 40 } };
-
-StreamBuilderHelper::StreamBuilderHelper(
-        aaudio_direction_t direction, int32_t sampleRate,
-        int32_t channelCount, aaudio_format_t dataFormat,
-        aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode)
-        : mDirection{direction},
-          mRequested{sampleRate, channelCount, dataFormat, sharingMode, perfMode},
-          mActual{0, 0, AAUDIO_FORMAT_INVALID, -1, -1}, mFramesPerBurst{-1},
-          mBuilder{nullptr}, mStream{nullptr} {}
-
-StreamBuilderHelper::~StreamBuilderHelper() {
-    close();
-}
-
-void StreamBuilderHelper::initBuilder() {
-    ASSERT_EQ(1U, sMaxFramesPerBurstMs.count(mRequested.perfMode));
-
-    // Use an AAudioStreamBuilder to define the stream.
-    aaudio_result_t result = AAudio_createStreamBuilder(&mBuilder);
-    ASSERT_EQ(AAUDIO_OK, result);
-    ASSERT_TRUE(mBuilder != nullptr);
-
-    // Request stream properties.
-    AAudioStreamBuilder_setDeviceId(mBuilder, AAUDIO_UNSPECIFIED);
-    AAudioStreamBuilder_setDirection(mBuilder, mDirection);
-    AAudioStreamBuilder_setSampleRate(mBuilder, mRequested.sampleRate);
-    AAudioStreamBuilder_setChannelCount(mBuilder, mRequested.channelCount);
-    AAudioStreamBuilder_setFormat(mBuilder, mRequested.dataFormat);
-    AAudioStreamBuilder_setSharingMode(mBuilder, mRequested.sharingMode);
-    AAudioStreamBuilder_setPerformanceMode(mBuilder, mRequested.perfMode);
-}
-
-// Needs to be a 'void' function due to ASSERT requirements.
-void StreamBuilderHelper::createAndVerifyStream(bool *success) {
-    *success = false;
-
-    aaudio_result_t result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
-    if (mRequested.sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE && result != AAUDIO_OK) {
-        __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "Could not open a stream in EXCLUSIVE mode");
-        return;
-    }
-    ASSERT_EQ(AAUDIO_OK, result);
-    ASSERT_TRUE(mStream != nullptr);
-    ASSERT_EQ(AAUDIO_STREAM_STATE_OPEN, AAudioStream_getState(mStream));
-    ASSERT_EQ(mDirection, AAudioStream_getDirection(mStream));
-
-    mActual.sharingMode = AAudioStream_getSharingMode(mStream);
-    if (mActual.sharingMode != mRequested.sharingMode) {
-        // Since we are covering all possible values, the "actual" mode
-        // will also be tested, so no need to run the same test twice.
-        __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Sharing mode %s is not available",
-                sharingModeToString(mRequested.sharingMode));
-        return;
-    }
-
-    // Check to see what kind of stream we actually got.
-    mActual.sampleRate = AAudioStream_getSampleRate(mStream);
-    ASSERT_GE(mActual.sampleRate, 44100);
-    ASSERT_LE(mActual.sampleRate, 96000); // TODO what is min/max?
-
-    mActual.channelCount = AAudioStream_getChannelCount(mStream);
-    ASSERT_GE(mActual.channelCount, 1);
-    ASSERT_LE(mActual.channelCount, 16); // TODO what is min/max?
-
-    mActual.dataFormat = AAudioStream_getFormat(mStream);
-    ASSERT_EQ(AAUDIO_FORMAT_PCM_I16, mActual.dataFormat);
-
-    mActual.perfMode = AAudioStream_getPerformanceMode(mStream);
-    if (mRequested.perfMode != AAUDIO_PERFORMANCE_MODE_NONE
-            && mRequested.perfMode != mActual.perfMode) {
-        // Since we are covering all possible values, the "actual" mode
-        // will also be tested, so no need to run the same test twice.
-        __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Performance mode %s is not available",
-                performanceModeToString(mRequested.sharingMode));
-        return;
-    }
-
-    mFramesPerBurst = AAudioStream_getFramesPerBurst(mStream);
-    ASSERT_GE(mFramesPerBurst, 16);
-    const int32_t maxFramesPerBurst =
-            mActual.sampleRate * sMaxFramesPerBurstMs.at(mActual.perfMode) / MILLIS_PER_SECOND;
-    ASSERT_LE(mFramesPerBurst, maxFramesPerBurst);
-
-    int32_t actualBufferSize = AAudioStream_getBufferSizeInFrames(mStream);
-    ASSERT_GT(actualBufferSize, 0);
-    ASSERT_GT(AAudioStream_setBufferSizeInFrames(mStream, actualBufferSize), 0);
-
-    *success = true;
-}
-
-void StreamBuilderHelper::close() {
-    if (mBuilder != nullptr) {
-        ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(mBuilder));
-    }
-    if (mStream != nullptr) {
-        ASSERT_EQ(AAUDIO_OK, AAudioStream_close(mStream));
-    }
-}
-
-void StreamBuilderHelper::streamCommand(
-        StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState) {
-    ASSERT_EQ(AAUDIO_OK, cmd(mStream));
-    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
-    ASSERT_EQ(AAUDIO_OK,
-            AAudioStream_waitForStateChange(mStream, fromState, &state, DEFAULT_STATE_TIMEOUT));
-    ASSERT_EQ(toState, state);
-}
-
-
-InputStreamBuilderHelper::InputStreamBuilderHelper(
-        aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
-        : StreamBuilderHelper{AAUDIO_DIRECTION_INPUT,
-            48000, 1, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
-
-// Native apps don't have permissions, thus recording can
-// only be tested when running as root.
-static bool canTestRecording() {
-    static const bool runningAsRoot = getuid() == 0;
-    return runningAsRoot;
-}
-
-void InputStreamBuilderHelper::createAndVerifyStream(bool *success) {
-    if (!canTestRecording()) {
-        __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "No permissions to run recording tests");
-        *success = false;
-    } else {
-        StreamBuilderHelper::createAndVerifyStream(success);
-    }
-}
-
-
-OutputStreamBuilderHelper::OutputStreamBuilderHelper(
-        aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
-        : StreamBuilderHelper{AAUDIO_DIRECTION_OUTPUT,
-            48000, 2, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
-
-void OutputStreamBuilderHelper::initBuilder() {
-    StreamBuilderHelper::initBuilder();
-    AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
-}
-
-void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
-    StreamBuilderHelper::createAndVerifyStream(success);
-    if (*success) {
-        ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
-    }
-}
diff --git a/tests/tests/nativemedia/aaudio/src/utils.h b/tests/tests/nativemedia/aaudio/src/utils.h
deleted file mode 100644
index 44c3c69..0000000
--- a/tests/tests/nativemedia/aaudio/src/utils.h
+++ /dev/null
@@ -1,102 +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.
- */
-#ifndef CTS_MEDIA_TEST_AAUDIO_UTILS_H
-#define CTS_MEDIA_TEST_AAUDIO_UTILS_H
-
-#include <map>
-
-#include <aaudio/AAudio.h>
-
-int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC);
-const char* performanceModeToString(aaudio_performance_mode_t mode);
-const char* sharingModeToString(aaudio_sharing_mode_t mode);
-
-class StreamBuilderHelper {
-  public:
-    struct Parameters {
-        int32_t sampleRate;
-        int32_t channelCount;
-        aaudio_format_t dataFormat;
-        aaudio_sharing_mode_t sharingMode;
-        aaudio_performance_mode_t perfMode;
-    };
-
-    void initBuilder();
-    void createAndVerifyStream(bool *success);
-    void close();
-
-    void startStream() {
-        streamCommand(&AAudioStream_requestStart,
-                AAUDIO_STREAM_STATE_STARTING, AAUDIO_STREAM_STATE_STARTED);
-    }
-    void pauseStream() {
-        streamCommand(&AAudioStream_requestPause,
-                AAUDIO_STREAM_STATE_PAUSING, AAUDIO_STREAM_STATE_PAUSED);
-    }
-    void stopStream() {
-        streamCommand(&AAudioStream_requestStop,
-                AAUDIO_STREAM_STATE_STOPPING, AAUDIO_STREAM_STATE_STOPPED);
-    }
-    void flushStream() {
-        streamCommand(&AAudioStream_requestFlush,
-                AAUDIO_STREAM_STATE_FLUSHING, AAUDIO_STREAM_STATE_FLUSHED);
-    }
-
-    AAudioStreamBuilder* builder() const { return mBuilder; }
-    AAudioStream* stream() const { return mStream; }
-    const Parameters& actual() const { return mActual; }
-    int32_t framesPerBurst() const { return mFramesPerBurst; }
-
-  protected:
-    StreamBuilderHelper(aaudio_direction_t direction, int32_t sampleRate,
-            int32_t channelCount, aaudio_format_t dataFormat,
-            aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode);
-    ~StreamBuilderHelper();
-
-    typedef aaudio_result_t (StreamCommand)(AAudioStream*);
-    void streamCommand(
-            StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState);
-
-    static const std::map<aaudio_performance_mode_t, int64_t> sMaxFramesPerBurstMs;
-    const aaudio_direction_t mDirection;
-    const Parameters mRequested;
-    Parameters mActual;
-    int32_t mFramesPerBurst;
-    AAudioStreamBuilder *mBuilder;
-    AAudioStream *mStream;
-};
-
-class InputStreamBuilderHelper : public StreamBuilderHelper {
-  public:
-    InputStreamBuilderHelper(
-            aaudio_sharing_mode_t requestedSharingMode,
-            aaudio_performance_mode_t requestedPerfMode);
-    void createAndVerifyStream(bool *success);
-};
-
-class OutputStreamBuilderHelper : public StreamBuilderHelper {
-  public:
-    OutputStreamBuilderHelper(
-            aaudio_sharing_mode_t requestedSharingMode,
-            aaudio_performance_mode_t requestedPerfMode);
-    void initBuilder();
-    void createAndVerifyStream(bool *success);
-
-  private:
-    const int32_t kBufferCapacityFrames = 2000;
-};
-
-#endif  // CTS_MEDIA_TEST_AAUDIO_UTILS_H
diff --git a/tests/tests/nativemedia/sl/AndroidTest.xml b/tests/tests/nativemedia/sl/AndroidTest.xml
index 0677a62..cfb0cae 100644
--- a/tests/tests/nativemedia/sl/AndroidTest.xml
+++ b/tests/tests/nativemedia/sl/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Native Media Open SL ES test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/xa/AndroidTest.xml b/tests/tests/nativemedia/xa/AndroidTest.xml
index c63c1ab..ae07686 100644
--- a/tests/tests/nativemedia/xa/AndroidTest.xml
+++ b/tests/tests/nativemedia/xa/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Native Media OpenMax AL test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/ndef/AndroidTest.xml b/tests/tests/ndef/AndroidTest.xml
index ca949bd..aef105f 100644
--- a/tests/tests/ndef/AndroidTest.xml
+++ b/tests/tests/ndef/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS NDEF test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/net/Android.mk b/tests/tests/net/Android.mk
index 9559779..e67bb8f 100644
--- a/tests/tests/net/Android.mk
+++ b/tests/tests/net/Android.mk
@@ -24,7 +24,12 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := voip-common conscrypt org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    voip-common \
+    conscrypt \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
 
 LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libnativedns_jni \
                               libnativemultinetwork_jni libnativehelper_compat_libc++
@@ -40,8 +45,7 @@
     ctstestrunner \
     ctstestserver \
     mockwebserver \
-    junit \
-    legacy-android-test
+    junit
 
 # uncomment when b/13249961 is fixed
 #LOCAL_SDK_VERSION := current
diff --git a/tests/tests/net/AndroidManifest.xml b/tests/tests/net/AndroidManifest.xml
index dd310a1..37bf323 100644
--- a/tests/tests/net/AndroidManifest.xml
+++ b/tests/tests/net/AndroidManifest.xml
@@ -30,7 +30,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
-    <application>
+    <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/tests/net/AndroidTest.xml b/tests/tests/net/AndroidTest.xml
index 4a578ea..4190a77 100644
--- a/tests/tests/net/AndroidTest.xml
+++ b/tests/tests/net/AndroidTest.xml
@@ -13,6 +13,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Net 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" />
diff --git a/tests/tests/net/assets/network_watchlist_config_for_test.xml b/tests/tests/net/assets/network_watchlist_config_for_test.xml
new file mode 100644
index 0000000..835ae0f
--- /dev/null
+++ b/tests/tests/net/assets/network_watchlist_config_for_test.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.
+*/
+-->
+<!-- This test config file just contains some random hashes for testing
+ConnectivityManager.getWatchlistConfigHash() -->
+<watchlist-config>
+    <sha256-domain>
+        <hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
+    </sha256-domain>
+    <sha256-ip>
+        <hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2EC</hash>
+    </sha256-ip>
+    <crc32-domain>
+        <hash>AAAAAAAA</hash>
+    </crc32-domain>
+    <crc32-ip>
+        <hash>BBBBBBBB</hash>
+    </crc32-ip>
+</watchlist-config>
diff --git a/tests/tests/net/native/qtaguid/AndroidTest.xml b/tests/tests/net/native/qtaguid/AndroidTest.xml
index 2eea82e..7591c87 100644
--- a/tests/tests/net/native/qtaguid/AndroidTest.xml
+++ b/tests/tests/net/native/qtaguid/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Native Network xt_qtaguid test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index c885942..b2a152b 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,8 +16,11 @@
 
 package android.net.cts;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.app.PendingIntent;
@@ -29,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
@@ -50,14 +54,23 @@
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.net.Socket;
 import java.net.InetSocketAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Scanner;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import libcore.io.Streams;
 
 public class ConnectivityManagerTest extends AndroidTestCase {
 
@@ -107,6 +120,8 @@
     private final HashMap<Integer, NetworkConfig> mNetworks =
             new HashMap<Integer, NetworkConfig>();
     boolean mWifiConnectAttempted;
+    private TestNetworkCallback mCellNetworkCallback;
+
 
     @Override
     protected void setUp() throws Exception {
@@ -140,6 +155,10 @@
         if (mWifiConnectAttempted) {
             disconnectFromWifi(null);
         }
+        if (cellConnectAttempted()) {
+            disconnectFromCell();
+        }
+        super.tearDown();
     }
 
     /**
@@ -246,6 +265,95 @@
         }
     }
 
+    /**
+     * Tests that connections can be opened on WiFi and cellphone networks,
+     * and that they are made from different IP addresses.
+     */
+    public void testOpenConnection() throws Exception {
+        boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+        if (!canRunTest) {
+            Log.i(TAG,"testOpenConnection cannot execute unless device supports both WiFi "
+                    + "and a cellular connection");
+            return;
+        }
+
+        Network wifiNetwork = connectToWifi();
+        Network cellNetwork = connectToCell();
+        // This server returns the requestor's IP address as the response body.
+        URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+        String wifiAddressString = httpGet(wifiNetwork, url);
+        String cellAddressString = httpGet(cellNetwork, url);
+
+        assertFalse(String.format("Same address '%s' on two different networks (%s, %s)",
+                wifiAddressString, wifiNetwork, cellNetwork),
+                wifiAddressString.equals(cellAddressString));
+
+        // Sanity check that the IP addresses that the requests appeared to come from
+        // are actually on the respective networks.
+        assertOnNetwork(wifiAddressString, wifiNetwork);
+        assertOnNetwork(cellAddressString, cellNetwork);
+
+        assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
+    }
+
+    private Network connectToCell() throws InterruptedException {
+        if (cellConnectAttempted()) {
+            throw new IllegalStateException("Already connected");
+        }
+        NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        mCellNetworkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(cellRequest, mCellNetworkCallback);
+        final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
+        assertNotNull("Cell network not available within timeout", cellNetwork);
+        return cellNetwork;
+    }
+
+    private boolean cellConnectAttempted() {
+        return mCellNetworkCallback != null;
+    }
+
+    private void disconnectFromCell() {
+        if (!cellConnectAttempted()) {
+            throw new IllegalStateException("Cell connection not attempted");
+        }
+        mCm.unregisterNetworkCallback(mCellNetworkCallback);
+        mCellNetworkCallback = null;
+    }
+
+    /**
+     * Performs a HTTP GET to the specified URL on the specified Network, and returns
+     * the response body decoded as UTF-8.
+     */
+    private static String httpGet(Network network, URL httpUrl) throws IOException {
+        HttpURLConnection connection = (HttpURLConnection) network.openConnection(httpUrl);
+        try {
+            InputStream inputStream = connection.getInputStream();
+            return Streams.readFully(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+        } finally {
+            connection.disconnect();
+        }
+    }
+
+    private void assertOnNetwork(String adressString, Network network) throws UnknownHostException {
+        InetAddress address = InetAddress.getByName(adressString);
+        LinkProperties linkProperties = mCm.getLinkProperties(network);
+        // To make sure that the request went out on the right network, check that
+        // the IP address seen by the server is assigned to the expected network.
+        // We can only do this for IPv6 addresses, because in IPv4 we will likely
+        // have a private IPv4 address, and that won't match what the server sees.
+        if (address instanceof Inet6Address) {
+            assertContains(linkProperties.getAddresses(), address);
+        }
+    }
+
+    private static<T> void assertContains(Collection<T> collection, T element) {
+        assertTrue(element + " not found in " + collection, collection.contains(element));
+    }
+
     private void assertStartUsingNetworkFeatureUnsupported(int networkType, String feature) {
         try {
             mCm.startUsingNetworkFeature(networkType, feature);
@@ -324,7 +432,7 @@
      * that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
      */
     public void testRegisterNetworkCallback() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
             return;
         }
@@ -364,7 +472,7 @@
      * of a {@code NetworkCallback}.
      */
     public void testRegisterNetworkCallback_withPendingIntent() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
             return;
         }
@@ -458,7 +566,7 @@
      * Tests reporting of connectivity changed.
      */
     public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
             return;
         }
@@ -475,7 +583,7 @@
     }
 
     public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
             return;
         }
@@ -495,14 +603,14 @@
 
     public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
             throws InterruptedException {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
             return;
         }
-        Intent startIntent = new Intent();
-        startIntent.setComponent(new ComponentName("android.net.cts.appForApi23",
-                "android.net.cts.appForApi23.ConnectivityListeningActivity"));
-        mContext.startActivity(startIntent);
+        mContext.startActivity(new Intent()
+                .setComponent(new ComponentName("android.net.cts.appForApi23",
+                        "android.net.cts.appForApi23.ConnectivityListeningActivity"))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
         Thread.sleep(200);
 
         toggleWifi();
diff --git a/tests/tests/net/src/android/net/cts/NetworkWatchlistTest.java b/tests/tests/net/src/android/net/cts/NetworkWatchlistTest.java
new file mode 100644
index 0000000..939079f
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.test.AndroidTestCase;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Formatter;
+
+public class NetworkWatchlistTest extends AndroidTestCase {
+
+    private static final String TEST_WATCHLIST_XML = "assets/network_watchlist_config_for_test.xml";
+    private static final String SDCARD_CONFIG_PATH =
+            "/sdcard/network_watchlist_config_for_test.xml";
+    private static final String TMP_CONFIG_PATH =
+            "/data/local/tmp/network_watchlist_config_for_test.xml";
+    // Generated from sha256sum network_watchlist_config_for_test.xml
+    private static final String TEST_WATCHLIST_CONFIG_HASH =
+            "B5FC4636994180D54E1E912F78178AB1D8BD2BE71D90CA9F5BBC3284E4D04ED4";
+
+    private Context mContext;
+    private boolean mHasFeature;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getContext();
+        mHasFeature = isAtLeastP();
+        runCommand("rm " + SDCARD_CONFIG_PATH);
+        runCommand("rm " + TMP_CONFIG_PATH);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        runCommand("rm " + SDCARD_CONFIG_PATH);
+        runCommand("rm " + TMP_CONFIG_PATH);
+    }
+
+    private boolean isAtLeastP() throws Exception {
+        // TODO: replace with ApiLevelUtil.isAtLeast(Build.VERSION_CODES.P) when the P API level
+        // constant is defined.
+        return ApiLevelUtil.getCodename().compareToIgnoreCase("P") >= 0;
+    }
+
+    /**
+     * Test if ConnectivityManager.getNetworkWatchlistConfigHash() correctly
+     * returns the hash of config we set.
+     */
+    public void testGetWatchlistConfigHash() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // TODO: Test watchlist config does not exist case
+        // Save test watchlist config to sdcard as app can't access /data/local/tmp
+        saveResourceToFile(TEST_WATCHLIST_XML, SDCARD_CONFIG_PATH);
+        // Copy test watchlist config from sdcard to /data/local/tmp as systerm service
+        // can't access /sdcard
+        runCommand("cp " + SDCARD_CONFIG_PATH + " " + TMP_CONFIG_PATH);
+        // Set test watchlist config to system
+        final String cmdResult = runCommand(
+                "cmd network_watchlist set-test-config " + TMP_CONFIG_PATH).trim();
+        assertTrue(cmdResult.contains("Success"));
+        // Test if watchlist config hash value is correct
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) getContext().getSystemService(
+                        Context.CONNECTIVITY_SERVICE);
+        byte[] result = connectivityManager.getNetworkWatchlistConfigHash();
+        Assert.assertEquals(TEST_WATCHLIST_CONFIG_HASH, byteArrayToHexString(result));
+    }
+
+    private static String byteArrayToHexString(byte[] bytes) {
+        Formatter formatter = new Formatter();
+        for (byte b : bytes) {
+            formatter.format("%02X", b);
+        }
+        return formatter.toString();
+    }
+
+    private void saveResourceToFile(String res, String filePath) throws IOException {
+        InputStream in = getClass().getClassLoader().getResourceAsStream(res);
+        FileUtils.copyToFileOrThrow(in, new File(filePath));
+    }
+
+    private static String runCommand(String command) throws IOException {
+        return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
index 930c742..a8743fa 100755
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -81,26 +81,6 @@
         return packetCount * (20 + 32 + bytes);
     }
 
-    private void accessOwnTrafficStats() throws IOException {
-        final int ownAppUid = getContext().getApplicationInfo().uid;
-        Log.d(LOG_TAG, "accesOwnTrafficStatsWithTags(): about to read qtaguid stats for own uid " + ownAppUid);
-
-        boolean foundOwnDetailedStats = false;
-        try {
-            BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
-            String line;
-            while ((line = qtaguidReader.readLine()) != null) {
-                String tokens[] = line.split(" ");
-                if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
-                    Log.d(LOG_TAG, "accessOwnTrafficStatsWithTags(): got own stats: " + line);
-                }
-            }
-            qtaguidReader.close();
-        } catch (FileNotFoundException e) {
-            fail("Was not able to access qtaguid/stats: " + e);
-        }
-    }
-
     public void testTrafficStatsForLocalhost() throws IOException {
         final long mobileTxPacketsBefore = TrafficStats.getMobileTxPackets();
         final long mobileRxPacketsBefore = TrafficStats.getMobileRxPackets();
@@ -132,7 +112,6 @@
                     byte[] buf = new byte[byteCount];
                     TrafficStats.setThreadStatsTag(0x42);
                     TrafficStats.tagSocket(socket);
-                    accessOwnTrafficStats();
                     for (int i = 0; i < packetCount; i++) {
                         out.write(buf);
                         out.flush();
@@ -145,7 +124,6 @@
                     }
                     out.close();
                     socket.close();
-                    accessOwnTrafficStats();
                 } catch (IOException e) {
                     Log.i(LOG_TAG, "Badness during writes to socket: " + e);
                 }
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 87e22d8..7277553 100644
--- a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -20,7 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.location.LocationManager;
 import android.net.ConnectivityManager;
+import android.net.MacAddress;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.wifi.WifiManager;
@@ -37,14 +39,11 @@
 import android.net.wifi.aware.WifiAwareSession;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.provider.Settings;
 import android.test.AndroidTestCase;
-import android.util.Log;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -76,13 +75,6 @@
     // used to store any WifiAwareSession allocated during tests - will clean-up after tests
     private List<WifiAwareSession> mSessions = new ArrayList<>();
 
-    // Return true if location is enabled.
-    private boolean isLocationEnabled() {
-        return Settings.Secure.getInt(getContext().getContentResolver(),
-                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) !=
-                Settings.Secure.LOCATION_MODE_OFF;
-    }
-
     private class WifiAwareBroadcastReceiver extends BroadcastReceiver {
         private CountDownLatch mBlocker = new CountDownLatch(1);
 
@@ -358,6 +350,10 @@
             return;
         }
 
+        assertTrue("Wi-Fi Aware requires Location to be Enabled",
+                ((LocationManager) getContext().getSystemService(
+                        Context.LOCATION_SERVICE)).isLocationEnabled());
+
         mWifiAwareManager = (WifiAwareManager) getContext().getSystemService(
                 Context.WIFI_AWARE_SERVICE);
         assertNotNull("Wi-Fi Aware Manager", mWifiAwareManager);
@@ -431,18 +427,6 @@
             return;
         }
 
-        if (isLocationEnabled()) {
-            /* Can't execute this test with location on since it means that Aware will not get
-             * disabled even if we disable Wi-Fi (which when location is enabled does not correspond
-             * to disabling the Wi-Fi chip).
-             *
-             * Considering other tests may require locationing to be enable we can't also fail the
-             * test in such a case. Hence it is skipped.
-             */
-            Log.d(TAG, "Skipping test since location scans are enabled");
-            return;
-        }
-
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
 
@@ -717,82 +701,14 @@
     }
 
     /**
-     * Request an Aware data-path (open) on a Publish discovery session (which can be done with a
-     * null peer - to accept all requests). Validate that times-out.
-     */
-    public void testDataPathOpenInContextOfDiscoveryFail() {
-        if (!TestUtils.shouldTestWifiAware(getContext())) {
-            return;
-        }
-
-        WifiAwareSession session = attachAndGetSession();
-
-        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
-                "ValidName").build();
-        DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
-        NetworkCallbackTest networkCb = new NetworkCallbackTest();
-
-        // 1. publish
-        session.publish(publishConfig, discoveryCb, mHandler);
-        assertTrue("Publish started",
-                discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
-        PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
-        assertNotNull("Publish session", discoverySession);
-
-        // 2. request an AWARE network
-        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
-                NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
-                discoverySession.createNetworkSpecifierOpen(null)).build();
-        mConnectivityManager.requestNetwork(nr, networkCb, 2000);
-        assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
-
-        discoverySession.close();
-        session.close();
-    }
-
-    /**
-     * Request an Aware data-path (encrypted) on a Publish discovery session (which can be done
-     * with a null peer - to accept all requests). Validate that times-out.
-     */
-    public void testDataPathPassphraseInContextOfDiscoveryFail() {
-        if (!TestUtils.shouldTestWifiAware(getContext())) {
-            return;
-        }
-
-        WifiAwareSession session = attachAndGetSession();
-
-        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
-                "ValidName").build();
-        DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
-        NetworkCallbackTest networkCb = new NetworkCallbackTest();
-
-        // 1. publish
-        session.publish(publishConfig, discoveryCb, mHandler);
-        assertTrue("Publish started",
-                discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
-        PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
-        assertNotNull("Publish session", discoverySession);
-
-        // 2. request an AWARE network
-        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
-                NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
-                discoverySession.createNetworkSpecifierPassphrase(null,
-                        "Some very long but not very good passphrase")).build();
-        mConnectivityManager.requestNetwork(nr, networkCb, 2000);
-        assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
-
-        discoverySession.close();
-        session.close();
-    }
-
-    /**
-     * Request an Aware data-path (open) as a Responder with no peer MAC address (i.e. accept any
-     * peer request). Validate that times-out.
+     * Request an Aware data-path (open) as a Responder with an arbitrary peer MAC address. Validate
+     * that times-out.
      */
     public void testDataPathOpenOutOfBandFail() {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
+        MacAddress mac = MacAddress.fromString("00:01:02:03:04:05");
 
         WifiAwareSession session = attachAndGetSession();
 
@@ -805,7 +721,8 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
                 session.createNetworkSpecifierOpen(
-                        WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, null)).build();
+                        WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                        mac.toByteArray())).build();
         mConnectivityManager.requestNetwork(nr, networkCb, 2000);
         assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
 
@@ -813,13 +730,14 @@
     }
 
     /**
-     * Request an Aware data-path (encrypted) as a Responder with no peer MAC address (i.e.
-     * accept any peer request). Validate that times-out.
+     * Request an Aware data-path (encrypted) as a Responder with an arbitrary peer MAC address.
+     * Validate that times-out.
      */
     public void testDataPathPassphraseOutOfBandFail() {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
+        MacAddress mac = MacAddress.fromString("00:01:02:03:04:05");
 
         WifiAwareSession session = attachAndGetSession();
 
@@ -832,7 +750,7 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
                 session.createNetworkSpecifierPassphrase(
-                        WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, null,
+                        WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, mac.toByteArray(),
                         "abcdefghihk")).build();
         mConnectivityManager.requestNetwork(nr, networkCb, 2000);
         assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
index 4c68423..55fa4d1 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
@@ -22,9 +22,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    ctstestserver \
-    org.apache.http.legacy \
-    legacy-android-test
+    ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src common)
 
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
index 49385f8..d4ce39a 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
@@ -22,6 +22,7 @@
   <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
   <application>
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
index 584afd2..033d7ea 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
@@ -22,9 +22,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    ctstestserver \
-    org.apache.http.legacy \
-    legacy-android-test
+    ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src common)
 
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
index be698f2..fe31e80 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
@@ -22,6 +22,7 @@
   <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
   <application>
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
index 9a613e7..c10a19a 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
@@ -22,9 +22,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    ctstestserver \
-    org.apache.http.legacy \
-    legacy-android-test
+    ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src common)
 
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
index 7bd8742..c6b65c0 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
@@ -22,6 +22,7 @@
   <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
   <application>
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
index 10018ce..4af2de7 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
index 9356074..972052e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigAttributeTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
index e6ee7c6..cfad3e4 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigAttributeTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
index 161dbd3..95e14ef 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
index 565f23a..3e5fe25 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigBasicDomainConfigTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
index 36eb72a..bd43bac 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigBasicDomainConfigTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
new file mode 100644
index 0000000..6dc6c5d
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
@@ -0,0 +1,36 @@
+# 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_PACKAGE_NAME := CtsNetSecConfigPrePCleartextTrafficTestCases
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res/
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := 26
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml
new file mode 100644
index 0000000..bec926e
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases">
+  <application android:networkSecurityConfig="@xml/network_security_config">
+      <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
+  </application>
+
+  <uses-permission android:name="android.permission.INTERNET" />
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                   android:targetPackage="android.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases"
+                   android:label="">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
new file mode 100644
index 0000000..cde3314
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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 CtsNetSecConfigPrePCleartextTraffic test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <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="CtsNetSecConfigPrePCleartextTrafficTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases" />
+        <option name="runtime-hint" value="8m10s" />
+    </test>
+</configuration>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml
new file mode 100644
index 0000000..987b178
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <domain-config cleartextTrafficPermitted="false">
+    <domain includeSubdomains="true">android.com</domain>
+    <domain-config cleartextTrafficPermitted="true">
+      <domain>developer.android.com</domain>
+    </domain-config>
+  </domain-config>
+</network-security-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java
new file mode 100644
index 0000000..b4b400c
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.security.net.config.cts;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509TrustManager;
+import junit.framework.TestCase;
+
+public class CleartextPermittedTest extends TestCase {
+    public void testDefaultAllowed() throws Exception {
+        TestUtils.assertCleartextConnectionSucceeds("example.com", 80);
+        TestUtils.assertTlsConnectionSucceeds("example.com", 443);
+    }
+
+    public void testCleartextBlocked() throws Exception {
+        TestUtils.assertCleartextConnectionFails("android.com", 80);
+        TestUtils.assertTlsConnectionSucceeds("android.com", 443);
+        // subdomains of android.com are also disallowed.
+        TestUtils.assertCleartextConnectionFails("www.android.com", 80);
+        TestUtils.assertTlsConnectionSucceeds("www.android.com", 443);
+    }
+
+    public void testNestedCleartextPermitted() throws Exception {
+        // developer.android.com is explicitly permitted.
+        TestUtils.assertCleartextConnectionSucceeds("developer.android.com", 80);
+        TestUtils.assertTlsConnectionSucceeds("developer.android.com", 443);
+    }
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
index 927374c..278d634 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
index b387ffa..8ee5482 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigCleartextTrafficTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
index f2e3f35..d5c33be 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigCleartextTraffic test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
index 987b178..6d41bba 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <network-security-config>
-  <domain-config cleartextTrafficPermitted="false">
+  <domain-config cleartextTrafficPermitted="true">
     <domain includeSubdomains="true">android.com</domain>
-    <domain-config cleartextTrafficPermitted="true">
+    <domain-config cleartextTrafficPermitted="false">
       <domain>developer.android.com</domain>
     </domain-config>
   </domain-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
index db8e40f..9592178 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
@@ -22,22 +22,22 @@
 import junit.framework.TestCase;
 
 public class CleartextPermittedTest extends TestCase {
-    public void testDefaultAllowed() throws Exception {
-        TestUtils.assertCleartextConnectionSucceeds("example.com", 80);
+    public void testDefaultDenied() throws Exception {
+        TestUtils.assertCleartextConnectionFails("example.com", 80);
         TestUtils.assertTlsConnectionSucceeds("example.com", 443);
     }
 
-    public void testCleartextBlocked() throws Exception {
-        TestUtils.assertCleartextConnectionFails("android.com", 80);
+    public void testCleartextAllowed() throws Exception {
+        TestUtils.assertCleartextConnectionSucceeds("android.com", 80);
         TestUtils.assertTlsConnectionSucceeds("android.com", 443);
         // subdomains of android.com are also disallowed.
-        TestUtils.assertCleartextConnectionFails("www.android.com", 80);
+        TestUtils.assertCleartextConnectionSucceeds("www.android.com", 80);
         TestUtils.assertTlsConnectionSucceeds("www.android.com", 443);
     }
 
-    public void testNestedCleartextPermitted() throws Exception {
-        // developer.android.com is explicitly permitted.
-        TestUtils.assertCleartextConnectionSucceeds("developer.android.com", 80);
+    public void testNestedCleartextDenied() throws Exception {
+        // developer.android.com is explicitly denied.
+        TestUtils.assertCleartextConnectionFails("developer.android.com", 80);
         TestUtils.assertTlsConnectionSucceeds("developer.android.com", 443);
     }
 }
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
index aa0eefd..fd5f419 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
index 3f15f81..28bba5b 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
@@ -21,6 +21,7 @@
   <application android:debuggable="false"
                android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
index 14e4eb4..666ed68 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigBasicDebugDisabledTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
index be9174e..d808928 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
index 6d98702..b667271 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
@@ -21,6 +21,7 @@
   <application android:debuggable="true"
                android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
index 198f30e..a92726e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigBasicDebugEnabledTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
index 84e72b0..4a40d2a 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
@@ -22,9 +22,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    org.apache.http.legacy \
-    android-support-test \
-    legacy-android-test
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
index e18ff4d..cc67cca 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
index fb26391..0d1b2a2 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigDownloadManagerTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
index 4764cab..3d66c53 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
index b3b32b5..bf6e369 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigInvalidPinTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
index 7f3c7fd..c2c54f3 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigInvalidPinTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
index 5448f34..e96ae6a 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
@@ -20,7 +20,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
index 1790150..75247d3 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigNestedDomainConfigTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
index 0209025..ca37091 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigNestedDomainConfigTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
index 924f393..48bbfaf 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
@@ -22,9 +22,13 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := \
     org.apache.http.legacy \
-    android-support-test \
-    legacy-android-test
+    android.test.runner.stubs \
+    android.test.base.stubs \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
index 46787160..4884458 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
@@ -20,6 +20,7 @@
         package="android.security.net.config.cts.CtsNetSecConfigResourcesSrcTestCases">
   <application android:networkSecurityConfig="@xml/network_security_config">
       <uses-library android:name="android.test.runner"/>
+      <uses-library android:name="org.apache.http.legacy" />
   </application>
 
   <uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
index 671633e..1dd8db7 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS CtsNetSecConfigInvalidPinTestCases test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/neuralnetworks/AndroidTest.xml b/tests/tests/neuralnetworks/AndroidTest.xml
index cd1a0f0..131db6a 100644
--- a/tests/tests/neuralnetworks/AndroidTest.xml
+++ b/tests/tests/neuralnetworks/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Native NNAPI Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="neuralnetworks" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/opengl/Android.mk b/tests/tests/opengl/Android.mk
index d623932..f9a36eb 100644
--- a/tests/tests/opengl/Android.mk
+++ b/tests/tests/opengl/Android.mk
@@ -29,7 +29,9 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libopengltest_jni
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/opengl/AndroidTest.xml b/tests/tests/opengl/AndroidTest.xml
index fc24223..4414182 100644
--- a/tests/tests/opengl/AndroidTest.xml
+++ b/tests/tests/opengl/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS OpenGL test cases">
+    <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" />
diff --git a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
index 4225de0..83243bc 100644
--- a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
@@ -191,7 +191,7 @@
             "EGL_KHR_wait_sync",
         };
 
-        for (int i = 0; i < requiredList.length; ++i) {
+        for (int i = 0; i < requiredEglList.length; ++i) {
             assertTrue("Required EGL extension for VR high-performance is missing: " +
                 requiredEglList[i], hasExtension(extensions, requiredEglList[i]));
         }
@@ -363,8 +363,8 @@
     }
 
     /**
-     * Return whether the system supports FEATURE_VR_MODE and
-     * FEATURE_VR_MODE_HIGH_PERFORMANCE. This is used to skip some tests.
+     * Return whether the system supports FEATURE_VR_MODE_HIGH_PERFORMANCE.
+     * This is used to skip some tests.
      */
     private boolean supportsVrHighPerformance() {
         PackageManager pm = mActivity.getPackageManager();
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
index 7669204..78a25fc 100644
--- a/tests/tests/openglperf/Android.mk
+++ b/tests/tests/openglperf/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni libnativehelper_compat_libc++
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/openglperf/AndroidTest.xml b/tests/tests/openglperf/AndroidTest.xml
index dc6b2a8..48cceec 100644
--- a/tests/tests/openglperf/AndroidTest.xml
+++ b/tests/tests/openglperf/AndroidTest.xml
@@ -13,6 +13,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS OpenGL Performance test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="graphics" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index 1eaab35..9cd0b87 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -28,9 +28,9 @@
     android-support-test \
     compatibility-device-util \
     ctstestrunner \
+    truth-prebuilt \
     guava \
-    junit \
-    legacy-android-test
+    junit
 
 LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libctsos_jni libnativehelper_compat_libc++
 
@@ -50,7 +50,8 @@
 # uncomment when b/13282254 is fixed
 #LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 # Do not compress minijail policy files.
 LOCAL_AAPT_FLAGS := -0 .policy
diff --git a/tests/tests/os/AndroidTest.xml b/tests/tests/os/AndroidTest.xml
index 27f235a..b66c292 100644
--- a/tests/tests/os/AndroidTest.xml
+++ b/tests/tests/os/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for OS 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" />
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index f62b470..7336fc0 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -16,13 +16,16 @@
 
 package android.os.cts;
 
+import static android.os.Build.VERSION.CODENAME;
+import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
 
 import android.os.Build;
-import android.os.SystemProperties;
 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;
@@ -31,11 +34,6 @@
 import java.util.Scanner;
 import java.util.regex.Pattern;
 
-import junit.framework.TestCase;
-
-import static android.os.Build.VERSION.CODENAME;
-import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
-
 public class BuildTest extends TestCase {
 
     private static final String RO_PRODUCT_CPU_ABILIST = "ro.product.cpu.abilist";
@@ -254,6 +252,25 @@
         }
     }
 
+    /**
+     * Verify that SDK versions are bounded by both high and low expected
+     * values.
+     */
+    public void testSdkInt() {
+        assertTrue(
+                "Current SDK version " + Build.VERSION.SDK_INT
+                        + " is invalid; must be at least VERSION_CODES.BASE",
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE);
+        assertTrue(
+                "First SDK version " + Build.VERSION.FIRST_SDK_INT
+                        + " is invalid; must be at least VERSION_CODES.BASE",
+                Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.BASE);
+        assertTrue(
+                "Current SDK version " + Build.VERSION.SDK_INT
+                        + " must be at least first SDK version " + Build.VERSION.FIRST_SDK_INT,
+                Build.VERSION.SDK_INT >= Build.VERSION.FIRST_SDK_INT);
+    }
+
     static final String RO_DEBUGGABLE = "ro.debuggable";
     private static final String RO_SECURE = "ro.secure";
 
diff --git a/tests/tests/os/src/android/os/cts/CtsRemoteService.java b/tests/tests/os/src/android/os/cts/CtsRemoteService.java
index daae49e..a0d5974 100644
--- a/tests/tests/os/src/android/os/cts/CtsRemoteService.java
+++ b/tests/tests/os/src/android/os/cts/CtsRemoteService.java
@@ -20,8 +20,10 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.Process;
+import java.io.File;
+import java.io.IOException;
 
-public class CtsRemoteService extends Service{
+public class CtsRemoteService extends Service {
 
     @Override
     public void onCreate() {
@@ -41,6 +43,15 @@
         public String getTimeZoneID() {
             return java.util.TimeZone.getDefault().getID();
         }
+
+        public boolean performDiskWrite() {
+            try {
+                File tempFile = File.createTempFile("foo", "bar");
+                return tempFile.delete();
+            } catch (IOException exception) {
+                return false;
+            }
+        }
     };
 
     @Override
@@ -50,5 +61,4 @@
         }
         return null;
     }
-
 }
diff --git a/tests/tests/os/src/android/os/cts/ISecondary.aidl b/tests/tests/os/src/android/os/cts/ISecondary.aidl
index 2c60149..183e2d5 100644
--- a/tests/tests/os/src/android/os/cts/ISecondary.aidl
+++ b/tests/tests/os/src/android/os/cts/ISecondary.aidl
@@ -23,4 +23,6 @@
     long getElapsedCpuTime();
 
     String getTimeZoneID();
+
+    boolean performDiskWrite();
 }
diff --git a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
index 4679a99..38651d7 100644
--- a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
@@ -16,6 +16,13 @@
 
 package android.os.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
@@ -24,13 +31,20 @@
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.Parcelable;
 import android.os.cts.ParcelFileDescriptorPeer.FutureCloseListener;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 import android.test.MoreAsserts;
 
 import com.google.common.util.concurrent.AbstractFuture;
 
 import junit.framework.ComparisonFailure;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -43,9 +57,15 @@
 import java.net.Socket;
 import java.util.concurrent.TimeUnit;
 
-public class ParcelFileDescriptorTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ParcelFileDescriptorTest {
     private static final long DURATION = 100l;
 
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Test
     public void testConstructorAndOpen() throws Exception {
         ParcelFileDescriptor tempFile = makeParcelFileDescriptor(getContext());
 
@@ -73,6 +93,7 @@
         }
     }
 
+    @Test
     public void testFromSocket() throws Throwable {
         final int PORT = 12222;
         final int DATA = 1;
@@ -111,6 +132,7 @@
         done.get(5, TimeUnit.SECONDS);
     }
 
+    @Test
     public void testFromData() throws IOException {
         assertNull(ParcelFileDescriptor.fromData(null, null));
         byte[] data = new byte[] { 0 };
@@ -143,6 +165,7 @@
         }
     }
 
+    @Test
     public void testFromDataSkip() throws IOException {
         byte[] data = new byte[] { 40, 41, 42, 43, 44, 45, 46 };
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null);
@@ -164,11 +187,13 @@
         }
     }
 
+    @Test
     public void testToString() {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
         assertNotNull(pfd.toString());
     }
 
+    @Test
     public void testWriteToParcel() throws Exception {
         ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
 
@@ -188,6 +213,7 @@
         }
     }
 
+    @Test
     public void testClose() throws Exception {
         ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
         AutoCloseInputStream in1 = new AutoCloseInputStream(pf);
@@ -210,11 +236,13 @@
         }
     }
 
+    @Test
     public void testGetStatSize() throws Exception {
         ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
         assertTrue(pf.getStatSize() >= 0);
     }
 
+    @Test
     public void testGetFileDescriptor() {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
         assertNotNull(pfd.getFileDescriptor());
@@ -223,6 +251,7 @@
         assertSame(pfd.getFileDescriptor(), p.getFileDescriptor());
     }
 
+    @Test
     public void testDescribeContents() {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
         assertTrue((Parcelable.CONTENTS_FILE_DESCRIPTOR & pfd.describeContents()) != 0);
@@ -247,6 +276,7 @@
         return new FileInputStream(pfd.getFileDescriptor()).read();
     }
 
+    @Test
     public void testPipeNormal() throws Exception {
         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
         final ParcelFileDescriptor red = pipe[0];
@@ -262,6 +292,7 @@
 
     // Reading should be done via AutoCloseInputStream if possible, rather than
     // recreating a FileInputStream from a raw FD, what's done in read(PFD).
+    @Test
     public void testPipeError_Discouraged() throws Exception {
         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
         final ParcelFileDescriptor red = pipe[0];
@@ -281,6 +312,7 @@
         }
     }
 
+    @Test
     public void testPipeError() throws Exception {
         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
         final ParcelFileDescriptor red = pipe[0];
@@ -298,6 +330,7 @@
         }
     }
 
+    @Test
     public void testFileNormal() throws Exception {
         final Handler handler = new Handler(Looper.getMainLooper());
         final FutureCloseListener listener = new FutureCloseListener();
@@ -312,6 +345,7 @@
         assertEquals(null, listener.get());
     }
 
+    @Test
     public void testFileError() throws Exception {
         final Handler handler = new Handler(Looper.getMainLooper());
         final FutureCloseListener listener = new FutureCloseListener();
@@ -326,6 +360,7 @@
         assertContains("OMG BANANAS", listener.get().getMessage());
     }
 
+    @Test
     public void testFileDetach() throws Exception {
         final Handler handler = new Handler(Looper.getMainLooper());
         final FutureCloseListener listener = new FutureCloseListener();
@@ -339,6 +374,7 @@
         assertContains("DETACHED", listener.get().getMessage());
     }
 
+    @Test
     public void testSocketErrorAfterClose() throws Exception {
         final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
         final ParcelFileDescriptor red = pair[0];
@@ -361,6 +397,7 @@
         blue.checkError();
     }
 
+    @Test
     public void testSocketMultipleCheck() throws Exception {
         final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
         final ParcelFileDescriptor red = pair[0];
@@ -382,6 +419,7 @@
     }
 
     // http://b/21578056
+    @Test
     public void testFileNamesWithNonBmpChars() throws Exception {
         final File file = File.createTempFile("treble_clef_\ud834\udd1e", ".tmp");
         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
@@ -390,6 +428,57 @@
         pfd.close();
     }
 
+    @Test
+    public void testCheckFinalizerBehavior() throws Exception {
+        final Runtime runtime = Runtime.getRuntime();
+        ParcelFileDescriptor pfd = makeParcelFileDescriptor(getContext());
+        assertTrue(checkIsValid(pfd.getFileDescriptor()));
+
+        ParcelFileDescriptor wrappedPfd = new ParcelFileDescriptor(pfd);
+        assertTrue(checkIsValid(wrappedPfd.getFileDescriptor()));
+
+        FileDescriptor fd = pfd.getFileDescriptor();
+        int rawFd = pfd.getFd();
+        pfd = null;
+        assertNull(pfd); // To keep tools happy - yes we are using the write to null
+        runtime.gc(); runtime.runFinalization();
+        assertTrue("Wrapped PFD failed to hold reference",
+                checkIsValid(wrappedPfd.getFileDescriptor()));
+        assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
+
+        wrappedPfd = null;
+        assertNull(wrappedPfd); // To keep tools happy - yes we are using the write to null
+        runtime.gc(); runtime.runFinalization();
+        // TODO: Enable this once b/65027998 is fixed
+        //assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
+
+        fd = null;
+        assertNull(fd); // To keep tools happy - yes we are using the write to null
+        runtime.gc(); runtime.runFinalization();
+
+        try {
+            ParcelFileDescriptor.fromFd(rawFd);
+            fail("FD leaked");
+        } catch (IOException ex) {
+            // Success
+        }
+    }
+
+    boolean checkIsValid(FileDescriptor fd) {
+        try {
+            Os.fstat(fd);
+            return true;
+        } catch (ErrnoException e) {
+            if (e.errno == OsConstants.EBADF) {
+                return false;
+            } else {
+                fail(e.getMessage());
+                // not reached
+                return false;
+            }
+        }
+    }
+
     static ParcelFileDescriptor makeParcelFileDescriptor(Context con) throws Exception {
         final String fileName = "testParcelFileDescriptor";
 
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 21730c6..06d2dae 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -16,340 +16,487 @@
 
 package android.os.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.StrictMode;
-import android.os.StrictMode.ViolationListener;
+import android.os.StrictMode.ThreadPolicy.Builder;
+import android.os.StrictMode.ViolationInfo;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.system.Os;
 import android.system.OsConstants;
-import android.test.InstrumentationTestCase;
 import android.util.Log;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.Socket;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-/**
- * Tests for {@link StrictMode}
- */
-public class StrictModeTest extends InstrumentationTestCase {
+/** Tests for {@link StrictMode} */
+@RunWith(AndroidJUnit4.class)
+public class StrictModeTest {
     private static final String TAG = "StrictModeTest";
+    private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
 
     private StrictMode.ThreadPolicy mThreadPolicy;
     private StrictMode.VmPolicy mVmPolicy;
 
     private Context getContext() {
-        return getInstrumentation().getContext();
+        return InstrumentationRegistry.getContext();
     }
 
-    @Override
-    protected void setUp() {
+    @Before
+    public void setUp() {
         mThreadPolicy = StrictMode.getThreadPolicy();
         mVmPolicy = StrictMode.getVmPolicy();
     }
 
-    @Override
-    protected void tearDown() {
+    @After
+    public void tearDown() {
         StrictMode.setThreadPolicy(mThreadPolicy);
         StrictMode.setVmPolicy(mVmPolicy);
     }
 
     public interface ThrowingRunnable {
-        public void run() throws Exception;
+        void run() throws Exception;
     }
 
-    /**
-     * Insecure connection should be detected
-     */
+    @Test
+    public void testThreadBuilder() throws Exception {
+        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build();
+        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
+
+        final File test = File.createTempFile("foo", "bar");
+        inspectViolation(
+                test::exists,
+                violation -> {
+                    assertThat(violation.getViolationDetails()).isNull();
+                    assertThat(violation.getStackTrace()).contains("DiskReadViolation");
+                });
+    }
+
+    @Test
+    public void testUnclosedCloseable() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectLeakedClosableObjects().build());
+
+        inspectViolation(
+                () -> leakCloseable("leaked.txt"),
+                info -> {
+                    assertThat(info.getViolationDetails())
+                            .isEqualTo(
+                                    "A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.");
+                    assertThat(info.getStackTrace())
+                            .contains("Explicit termination method 'close' not called");
+                    assertThat(info.getStackTrace()).contains("leakCloseable");
+                    assertPolicy(info, StrictMode.DETECT_VM_CLOSABLE_LEAKS);
+                });
+    }
+
+    private void leakCloseable(String fileName) throws InterruptedException {
+        final CountDownLatch finalizedSignal = new CountDownLatch(1);
+        try {
+            new FileOutputStream(new File(getContext().getFilesDir(), fileName)) {
+                @Override
+                protected void finalize() throws IOException {
+                    super.finalize();
+                    finalizedSignal.countDown();
+                }
+            };
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        // Sometimes it needs extra prodding.
+        if (!finalizedSignal.await(5, TimeUnit.SECONDS)) {
+            Runtime.getRuntime().gc();
+            Runtime.getRuntime().runFinalization();
+        }
+    }
+
+    @Test
+    public void testClassInstanceLimit() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .setClassInstanceLimit(LimitedClass.class, 1)
+                        .build());
+        List<LimitedClass> references = new ArrayList<>();
+        assertNoViolation(() -> references.add(new LimitedClass()));
+        references.add(new LimitedClass());
+        inspectViolation(
+                StrictMode::conditionallyCheckInstanceCounts,
+                info -> assertPolicy(info, StrictMode.DETECT_VM_INSTANCE_LEAKS));
+    }
+
+    private static final class LimitedClass {}
+
+    /** Insecure connection should be detected */
+    @Test
     public void testCleartextNetwork() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testCleartextNetwork() ignored on device without Internet");
             return;
         }
 
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectCleartextNetwork()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
 
-        assertViolation("Detected cleartext network traffic from UID", () -> {
-            ((HttpURLConnection) new URL("http://example.com/").openConnection())
-                    .getResponseCode();
-        });
+        inspectViolation(
+                () ->
+                        ((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);
+                });
     }
 
-    /**
-     * Secure connection should be ignored
-     */
+    /** Secure connection should be ignored */
+    @Test
     public void testEncryptedNetwork() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testEncryptedNetwork() ignored on device without Internet");
             return;
         }
 
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectCleartextNetwork()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
 
-        assertNoViolation(() -> {
-            ((HttpURLConnection) new URL("https://example.com/").openConnection())
-                    .getResponseCode();
-        });
+        assertNoViolation(
+                () ->
+                        ((HttpURLConnection) new URL("https://example.com/").openConnection())
+                                .getResponseCode());
     }
 
+    @Test
     public void testFileUriExposure() throws Exception {
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectFileUriExposure()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectFileUriExposure().penaltyLog().build());
 
         final Uri badUri = Uri.fromFile(new File("/sdcard/meow.jpg"));
-        assertViolation(badUri + " exposed beyond app", () -> {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setDataAndType(badUri, "image/jpeg");
-            getContext().startActivity(intent);
-        });
+        inspectViolation(
+                () -> {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setDataAndType(badUri, "image/jpeg");
+                    getContext().startActivity(intent);
+                },
+                violation -> {
+                    assertThat(violation.getStackTrace()).contains(badUri + " exposed beyond app");
+                });
 
         final Uri goodUri = Uri.parse("content://com.example/foobar");
-        assertNoViolation(() -> {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setDataAndType(goodUri, "image/jpeg");
-            getContext().startActivity(intent);
-        });
+        assertNoViolation(
+                () -> {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setDataAndType(goodUri, "image/jpeg");
+                    getContext().startActivity(intent);
+                });
     }
 
+    @Test
     public void testContentUriWithoutPermission() throws Exception {
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectContentUriWithoutPermission()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectContentUriWithoutPermission()
+                        .penaltyLog()
+                        .build());
 
         final Uri uri = Uri.parse("content://com.example/foobar");
-        assertViolation(uri + " exposed beyond app", () -> {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setDataAndType(uri, "image/jpeg");
-            getContext().startActivity(intent);
-        });
+        inspectViolation(
+                () -> {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setDataAndType(uri, "image/jpeg");
+                    getContext().startActivity(intent);
+                },
+                violation ->
+                        assertThat(violation.getStackTrace())
+                                .contains(uri + " exposed beyond app"));
 
-        assertNoViolation(() -> {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setDataAndType(uri, "image/jpeg");
-            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            getContext().startActivity(intent);
-        });
+        assertNoViolation(
+                () -> {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setDataAndType(uri, "image/jpeg");
+                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    getContext().startActivity(intent);
+                });
     }
 
+    @Test
     public void testUntaggedSocketsHttp() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
             return;
         }
 
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectUntaggedSockets()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
 
-        assertViolation("Untagged socket detected", () -> {
-            ((HttpURLConnection) new URL("http://example.com/").openConnection())
-                    .getResponseCode();
-        });
+        inspectViolation(
+                () ->
+                        ((HttpURLConnection) new URL("http://example.com/").openConnection())
+                                .getResponseCode(),
+                violation ->
+                        assertThat(violation.getStackTrace())
+                                .contains(UntaggedSocketViolation.MESSAGE));
 
-        assertNoViolation(() -> {
-            TrafficStats.setThreadStatsTag(0xDECAFBAD);
-            try {
-                ((HttpURLConnection) new URL("http://example.com/").openConnection())
-                        .getResponseCode();
-            } finally {
-                TrafficStats.clearThreadStatsTag();
-            }
-        });
+        assertNoViolation(
+                () -> {
+                    TrafficStats.setThreadStatsTag(0xDECAFBAD);
+                    try {
+                        ((HttpURLConnection) new URL("http://example.com/").openConnection())
+                                .getResponseCode();
+                    } finally {
+                        TrafficStats.clearThreadStatsTag();
+                    }
+                });
     }
 
+    @Test
     public void testUntaggedSocketsRaw() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
             return;
         }
 
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-                .detectUntaggedSockets()
-                .penaltyLog()
-                .build());
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
 
-        assertNoViolation(() -> {
-            TrafficStats.setThreadStatsTag(0xDECAFBAD);
-            try (Socket socket = new Socket("example.com", 80)) {
-                socket.getOutputStream().close();
-            } finally {
-                TrafficStats.clearThreadStatsTag();
-            }
-        });
+        assertNoViolation(
+                () -> {
+                    TrafficStats.setThreadStatsTag(0xDECAFBAD);
+                    try (Socket socket = new Socket("example.com", 80)) {
+                        socket.getOutputStream().close();
+                    } finally {
+                        TrafficStats.clearThreadStatsTag();
+                    }
+                });
 
-        assertViolation("Untagged socket detected", () -> {
-            try (Socket socket = new Socket("example.com", 80)) {
-                socket.getOutputStream().close();
-            }
-        });
+        inspectViolation(
+                () -> {
+                    try (Socket socket = new Socket("example.com", 80)) {
+                        socket.getOutputStream().close();
+                    }
+                },
+                violation ->
+                        assertThat(violation.getStackTrace())
+                                .contains(UntaggedSocketViolation.MESSAGE));
     }
 
+    private static final int PERMISSION_USER_ONLY = 0600;
+
+    @Test
     public void testRead() throws Exception {
         final File test = File.createTempFile("foo", "bar");
         final File dir = test.getParentFile();
 
         final FileInputStream is = new FileInputStream(test);
-        final FileDescriptor fd = Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
+        final FileDescriptor fd =
+                Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY);
 
-        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                .detectDiskReads()
-                .penaltyLog()
-                .build());
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build());
+        inspectViolation(
+                test::exists,
+                violation -> {
+                    assertThat(violation.getViolationDetails()).isNull();
+                    assertThat(violation.getStackTrace()).contains("DiskReadViolation");
+                });
 
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            test.exists();
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            test.length();
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            dir.list();
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            new FileInputStream(test);
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            is.read();
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
-        });
-        assertViolation("StrictModeDiskReadViolation", () -> {
-            Os.read(fd, new byte[10], 0, 1);
-        });
+        Consumer<ViolationInfo> assertDiskReadPolicy =
+                violation -> assertPolicy(violation, StrictMode.DETECT_DISK_READ);
+        inspectViolation(test::exists, assertDiskReadPolicy);
+        inspectViolation(test::length, assertDiskReadPolicy);
+        inspectViolation(dir::list, assertDiskReadPolicy);
+        inspectViolation(is::read, assertDiskReadPolicy);
+
+        inspectViolation(() -> new FileInputStream(test), assertDiskReadPolicy);
+        inspectViolation(
+                () -> Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY),
+                assertDiskReadPolicy);
+        inspectViolation(() -> Os.read(fd, new byte[10], 0, 1), assertDiskReadPolicy);
     }
 
+    @Test
     public void testWrite() throws Exception {
         File file = File.createTempFile("foo", "bar");
 
         final FileOutputStream os = new FileOutputStream(file);
-        final FileDescriptor fd = Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, 0600);
+        final FileDescriptor fd =
+                Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY);
 
-        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                .detectDiskWrites()
-                .penaltyLog()
-                .build());
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectDiskWrites().penaltyLog().build());
 
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            File.createTempFile("foo", "bar");
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            file.delete();
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            file.createNewFile();
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            new FileOutputStream(file);
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            os.write(32);
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, 0600);
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            Os.write(fd, new byte[10], 0, 1);
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            Os.fsync(fd);
-        });
-        assertViolation("StrictModeDiskWriteViolation", () -> {
-            file.renameTo(new File(file.getParent(), "foobar"));
-        });
+        inspectViolation(
+                file::createNewFile,
+                violation -> {
+                    assertThat(violation.getViolationDetails()).isNull();
+                    assertThat(violation.getStackTrace()).contains("DiskWriteViolation");
+                });
+
+        Consumer<ViolationInfo> assertDiskWritePolicy =
+                violation -> assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
+
+        inspectViolation(() -> File.createTempFile("foo", "bar"), assertDiskWritePolicy);
+        inspectViolation(() -> new FileOutputStream(file), assertDiskWritePolicy);
+        inspectViolation(file::delete, assertDiskWritePolicy);
+        inspectViolation(file::createNewFile, assertDiskWritePolicy);
+        inspectViolation(() -> os.write(32), assertDiskWritePolicy);
+
+        inspectViolation(
+                () -> Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY),
+                assertDiskWritePolicy);
+        inspectViolation(() -> Os.write(fd, new byte[10], 0, 1), assertDiskWritePolicy);
+        inspectViolation(() -> Os.fsync(fd), assertDiskWritePolicy);
+        inspectViolation(
+                () -> file.renameTo(new File(file.getParent(), "foobar")), assertDiskWritePolicy);
     }
 
+    @Test
     public void testNetwork() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
             return;
         }
 
-        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                .detectNetwork()
-                .penaltyLog()
-                .build());
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectNetwork().penaltyLog().build());
 
-        assertViolation("StrictModeNetworkViolation", () -> {
-            try (Socket socket = new Socket("example.com", 80)) {
-                socket.getOutputStream().close();
-            }
-        });
+        inspectViolation(
+                () -> {
+                    try (Socket socket = new Socket("example.com", 80)) {
+                        socket.getOutputStream().close();
+                    }
+                },
+                violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+        inspectViolation(
+                () ->
+                        ((HttpURLConnection) new URL("http://example.com/").openConnection())
+                                .getResponseCode(),
+                violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+    }
 
-        assertViolation("StrictModeNetworkViolation", () -> {
-            ((HttpURLConnection) new URL("http://example.com/").openConnection())
-                    .getResponseCode();
-        });
+    @Test
+    public void testViolationAcrossBinder() throws Exception {
+        runWithRemoteServiceBound(
+                getContext(),
+                service -> {
+                    StrictMode.setThreadPolicy(
+                            new Builder().detectDiskWrites().penaltyLog().build());
+
+                    try {
+                        inspectViolation(
+                                () -> service.performDiskWrite(),
+                                (violation) -> {
+                                    assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
+                                    assertThat(violation.getViolationDetails())
+                                            .isNull(); // Disk write has no message.
+                                    assertThat(violation.getStackTrace())
+                                            .contains("DiskWriteViolation");
+                                    assertThat(violation.getStackTrace())
+                                            .contains(
+                                                    "at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk");
+                                    assertThat(violation.getStackTrace())
+                                            .contains("# via Binder call with stack:");
+                                    assertThat(violation.getStackTrace())
+                                            .contains(
+                                                    "at android.os.cts.ISecondary$Stub$Proxy.performDiskWrite");
+                                });
+                        assertNoViolation(() -> service.getPid());
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+    }
+
+    private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
+            throws ExecutionException, InterruptedException, RemoteException {
+        BlockingQueue<IBinder> binderHolder = new ArrayBlockingQueue<>(1);
+        ServiceConnection secondaryConnection =
+                new ServiceConnection() {
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        binderHolder.add(service);
+                    }
+
+                    public void onServiceDisconnected(ComponentName className) {
+                        binderHolder.drainTo(new ArrayList<>());
+                    }
+                };
+        Intent intent = new Intent(REMOTE_SERVICE_ACTION);
+        intent.setPackage(context.getPackageName());
+
+        Intent secondaryIntent = new Intent(ISecondary.class.getName());
+        secondaryIntent.setPackage(context.getPackageName());
+        assertThat(
+                        context.bindService(
+                                secondaryIntent, secondaryConnection, Context.BIND_AUTO_CREATE))
+                .isTrue();
+        IBinder binder = binderHolder.take();
+        assertThat(binder.queryLocalInterface(binder.getInterfaceDescriptor())).isNull();
+        consumer.accept(ISecondary.Stub.asInterface(binder));
+        context.unbindService(secondaryConnection);
+        context.stopService(intent);
     }
 
     private static void assertViolation(String expected, ThrowingRunnable r) throws Exception {
-        final LinkedBlockingQueue<String> violations = new LinkedBlockingQueue<>();
-        StrictMode.setViolationListener(new ViolationListener() {
-            @Override
-            public void onViolation(String message) {
-                violations.add(message);
-            }
-        });
-
-        try {
-            r.run();
-            while (true) {
-                final String violation = violations.poll(5, TimeUnit.SECONDS);
-                if (violation == null) {
-                    fail("Expected violation not found: " + expected);
-                } else if (violation.contains(expected)) {
-                    return;
-                }
-            }
-        } finally {
-            StrictMode.setViolationListener(null);
-        }
+        inspectViolation(r, violation -> assertThat(violation.getStackTrace()).contains(expected));
     }
 
     private static void assertNoViolation(ThrowingRunnable r) throws Exception {
-        final LinkedBlockingQueue<String> violations = new LinkedBlockingQueue<>();
-        StrictMode.setViolationListener(new ViolationListener() {
-            @Override
-            public void onViolation(String message) {
-                violations.add(message);
-            }
-        });
+        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);
+    }
+
+    private static void inspectViolation(
+            ThrowingRunnable violating, Consumer<ViolationInfo> consume) throws Exception {
+        final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>();
+        StrictMode.setViolationLogger(violations::add);
 
         try {
-            r.run();
-            while (true) {
-                final String violation = violations.poll(5, TimeUnit.SECONDS);
-                if (violation == null) {
-                    return;
-                } else {
-                    fail("Unexpected violation found: " + violation);
-                }
-            }
+            violating.run();
+            consume.accept(violations.poll(5, TimeUnit.SECONDS));
         } finally {
-            StrictMode.setViolationListener(null);
+            StrictMode.setViolationLogger(null);
         }
     }
 
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
new file mode 100644
index 0000000..50237f0
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.os.cts;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+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 {
+    private static final long TEST_TIMING = 100;
+    private static final int TEST_AMPLITUDE = 100;
+
+    private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
+    private static final int[] TEST_AMPLITUDES =
+            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+
+    private static final VibrationEffect TEST_ONE_SHOT =
+            VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
+    private static final VibrationEffect TEST_WAVEFORM =
+            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+    private static final VibrationEffect TEST_WAVEFORM_NO_AMPLITUDES =
+            VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+
+
+    @Test
+    public void testCreateOneShot() {
+        VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+        VibrationEffect.createOneShot(1, 1);
+        VibrationEffect.createOneShot(1000, 255);
+    }
+
+    @Test
+    public void testCreateOneShotFailsBadTiming() {
+        try {
+            VibrationEffect.createOneShot(0, TEST_AMPLITUDE);
+            fail("Invalid timing, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testCreateOneShotFailsBadAmplitude() {
+        try {
+            VibrationEffect.createOneShot(TEST_TIMING, -2);
+            fail("Invalid amplitude, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            VibrationEffect.createOneShot(TEST_TIMING, 256);
+            fail("Invalid amplitude, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testOneShotEquals() {
+        VibrationEffect otherEffect = VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
+        assertEquals(TEST_ONE_SHOT, otherEffect);
+        assertEquals(TEST_ONE_SHOT.hashCode(), otherEffect.hashCode());
+    }
+
+    @Test
+    public void testOneShotNotEqualsAmplitude() {
+        VibrationEffect otherEffect =
+                VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE - 1);
+        assertNotEquals(TEST_ONE_SHOT, otherEffect);
+    }
+
+    @Test
+    public void testOneShotNotEqualsTiming() {
+        VibrationEffect otherEffect =
+                VibrationEffect.createOneShot(TEST_TIMING - 1, TEST_AMPLITUDE);
+        assertNotEquals(TEST_ONE_SHOT, otherEffect);
+    }
+
+    @Test
+    public void testOneShotEqualsWithDefaultAmplitude() {
+        VibrationEffect effect =
+                VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+        VibrationEffect otherEffect =
+                VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+        assertEquals(effect, otherEffect);
+        assertEquals(effect.hashCode(), otherEffect.hashCode());
+    }
+
+    @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);
+    }
+
+    @Test
+    public void testCreateWaveformFailsDifferentArraySize() {
+        try {
+            VibrationEffect.createWaveform(
+                    Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1),
+                    TEST_AMPLITUDES, -1);
+            fail("Timing and amplitudes arrays are different sizes, " +
+                    "should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            VibrationEffect.createWaveform(
+                    TEST_TIMINGS,
+                    Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length - 1), -1);
+            fail("Timing and amplitudes arrays are different sizes, " +
+                    "should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testCreateWaveformFailsRepeatIndexOutOfBounds() {
+        try {
+            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -2);
+            fail("Repeat index is < -1, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length);
+            fail("Repeat index is >= array length, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testCreateWaveformFailsBadTimingValues() {
+        try {
+            final long[] badTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
+            badTimings[1] = -1;
+            VibrationEffect.createWaveform(badTimings,TEST_AMPLITUDES, -1);
+            fail("Has a timing < 0, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            final long[] badTimings = new long[TEST_TIMINGS.length];
+            VibrationEffect.createWaveform(badTimings, TEST_AMPLITUDES, -1);
+            fail("Has no non-zero timings, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testCreateWaveformFailsBadAmplitudeValues() {
+        try {
+            final int[] badAmplitudes = new int[TEST_TIMINGS.length];
+            badAmplitudes[1] = -2;
+            VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
+            fail("Has an amplitude < VibrationEffect.DEFAULT_AMPLITUDE, " +
+                    "should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            final int[] badAmplitudes = new int[TEST_TIMINGS.length];
+            badAmplitudes[1] = 256;
+            VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
+            fail("Has an amplitude > 255, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testCreateWaveformWithNoAmplitudes() {
+        VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+        VibrationEffect.createWaveform(TEST_TIMINGS, 0);
+        VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length - 1);
+    }
+
+    @Test
+    public void testCreateWaveformWithNoAmplitudesFailsRepeatIndexOutOfBounds() {
+        try {
+            VibrationEffect.createWaveform(TEST_TIMINGS, -2);
+            fail("Repeat index is < -1, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length);
+            fail("Repeat index is >= timings array length, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    public void testWaveformEquals() {
+        VibrationEffect effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+        VibrationEffect otherEffect =
+                VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+        assertEquals(effect, otherEffect);
+        assertEquals(effect.hashCode(), otherEffect.hashCode());
+    }
+
+    @Test
+    public void testWaveformNotEqualsDifferentRepeatIndex() {
+        VibrationEffect otherEffect =
+                VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
+        assertNotEquals(TEST_WAVEFORM, otherEffect);
+    }
+
+    @Test
+    public void testWaveformNotEqualsDifferentTimingArrayValue() {
+        long[] newTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
+        newTimings[0] = 200;
+        VibrationEffect otherEffect =
+                VibrationEffect.createWaveform(newTimings, TEST_AMPLITUDES, -1);
+        assertNotEquals(TEST_WAVEFORM, otherEffect);
+    }
+
+    @Test
+    public void testWaveformNotEqualsDifferentAmplitudeArrayValue() {
+        int[] newAmplitudes = Arrays.copyOf(TEST_AMPLITUDES, TEST_AMPLITUDES.length);
+        newAmplitudes[0] = 1;
+        VibrationEffect otherEffect =
+                VibrationEffect.createWaveform(TEST_TIMINGS, newAmplitudes, -1);
+        assertNotEquals(TEST_WAVEFORM, otherEffect);
+    }
+
+    @Test
+    public void testWaveformNotEqualsDifferentArrayLength() {
+        long[] newTimings = Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1);
+        int[] newAmplitudes = Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length -1);
+        VibrationEffect otherEffect =
+                VibrationEffect.createWaveform(newTimings, newAmplitudes, -1);
+        assertNotEquals(TEST_WAVEFORM, otherEffect);
+        assertNotEquals(otherEffect, TEST_WAVEFORM);
+    }
+
+    @Test
+    public void testWaveformWithNoAmplitudesEquals() {
+        VibrationEffect otherEffect = VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+        assertEquals(TEST_WAVEFORM_NO_AMPLITUDES, otherEffect);
+        assertEquals(TEST_WAVEFORM_NO_AMPLITUDES.hashCode(), otherEffect.hashCode());
+    }
+
+    @Test
+    public void testWaveformWithNoAmplitudesNotEqualsDifferentRepeatIndex() {
+        VibrationEffect otherEffect = VibrationEffect.createWaveform(TEST_TIMINGS, 0);
+        assertNotEquals(TEST_WAVEFORM_NO_AMPLITUDES, otherEffect);
+    }
+
+    @Test
+    public void testWaveformWithNoAmplitudesNotEqualsDifferentArrayLength() {
+        long[] newTimings = Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1);
+        VibrationEffect otherEffect = VibrationEffect.createWaveform(newTimings, -1);
+        assertNotEquals(TEST_WAVEFORM_NO_AMPLITUDES, otherEffect);
+    }
+
+    @Test
+    public void testWaveformWithNoAmplitudesNotEqualsDifferentTimingValue() {
+        long[] newTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
+        newTimings[0] = 1;
+        VibrationEffect otherEffect = VibrationEffect.createWaveform(newTimings, -1);
+        assertNotEquals(TEST_WAVEFORM_NO_AMPLITUDES, otherEffect);
+    }
+
+    @Test
+    public void testParceling() {
+        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_WAVEFORM.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_WAVEFORM, parceledEffect);
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index 1577985..0af18fa 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -17,54 +17,58 @@
 package android.os.cts;
 
 import android.content.Context;
+import android.media.AudioAttributes;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-public class VibratorTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class VibratorTest {
+    private static final AudioAttributes AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                .build();
 
     private Vibrator mVibrator;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
+    @Before
+    public void setUp() {
+        mVibrator = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
     }
 
+    @Test
     public void testVibratorCancel() {
-        try {
-            mVibrator.vibrate(1000);
-        } catch (Exception e) {
-            fail("testVibratorCancel failed!");
-        }
-        sleep();
-        try {
-            mVibrator.cancel();
-        } catch (Exception e) {
-            fail("testVibratorCancel failed!");
-        }
+        mVibrator.vibrate(1000);
+        sleep(500);
+        mVibrator.cancel();
     }
 
+    @Test
     public void testVibratePattern() {
         long[] pattern = {100, 200, 400, 800, 1600};
-        try {
-            mVibrator.vibrate(pattern, 3);
-        } catch (Exception e) {
-            fail("vibrate failed!");
-        }
+        mVibrator.vibrate(pattern, 3);
         try {
             mVibrator.vibrate(pattern, 10);
             fail("Should throw ArrayIndexOutOfBoundsException");
-        } catch (ArrayIndexOutOfBoundsException e) {
-        }
-        sleep();
+        } catch (ArrayIndexOutOfBoundsException expected) { }
+        mVibrator.cancel();
     }
 
+    @Test
     public void testVibrateMultiThread() {
-        Log.d("*******VibratorTest", "MultiTreadTest");
         new Thread(new Runnable() {
             public void run() {
-                Log.d("*******VibratorTest", "Thread 1");
                 try {
                     mVibrator.vibrate(100);
                 } catch (Exception e) {
@@ -74,7 +78,6 @@
         }).start();
         new Thread(new Runnable() {
             public void run() {
-                Log.d("*******VibratorTest", "Thread 2");
                 try {
                     // This test only get two threads to run vibrator at the same time
                     // for a functional test,
@@ -85,13 +88,50 @@
                 }
             }
         }).start();
-        sleep();
+        sleep(1500);
     }
 
-    private void sleep() {
+    @Test
+    public void testVibrateOneShot() {
+        VibrationEffect oneShot =
+                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+        mVibrator.vibrate(oneShot);
+        sleep(100);
+
+        oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
+        mVibrator.vibrate(oneShot);
+        sleep(100);
+        mVibrator.cancel();
+
+        oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
+        mVibrator.vibrate(oneShot, AUDIO_ATTRIBUTES);
+        sleep(100);
+    }
+
+    @Test
+    public void testVibrateWaveform() {
+        final long[] timings = new long[] {100, 200, 300, 400, 500};
+        final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
+        VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
+        mVibrator.vibrate(waveform);
+        sleep(1500);
+
+        waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+        mVibrator.vibrate(waveform, AUDIO_ATTRIBUTES);
+        sleep(2000);
+        mVibrator.cancel();
+    }
+
+    @Test
+    public void testVibratorHasAmplitudeControl() {
+        // Just make sure it doesn't crash when this is called; we don't really have a way to test
+        // if the amplitude control works or not.
+        mVibrator.hasAmplitudeControl();
+    }
+
+    private static void sleep(long millis) {
         try {
-            Thread.sleep(10000);
-        } catch (Exception e) {
-        }
+            Thread.sleep(millis);
+        } catch (InterruptedException ignored) { }
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
index ff9d693..e7df7be 100644
--- a/tests/tests/os/src/android/os/cts/WorkSourceTest.java
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -35,8 +35,6 @@
     private Object[] mAddReturningNewbsArgs = new Object[1];
     private Method mSetReturningDiffs;
     private Object[] mSetReturningDiffsArgs = new Object[1];
-    private Method mStripNames;
-    private Object[] mStripNamesArgs = new Object[0];
 
     @Override
     protected void setUp() throws Exception {
@@ -46,7 +44,6 @@
         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 });
-        mStripNames = WorkSource.class.getMethod("stripNames", new Class[] {  });
     }
 
     private WorkSource wsNew(int uid) throws IllegalArgumentException,
@@ -100,11 +97,6 @@
         return (WorkSource[])mSetReturningDiffs.invoke(ws, mSetReturningDiffsArgs);
     }
 
-    private WorkSource wsStripNames(WorkSource ws) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        return (WorkSource)mStripNames.invoke(ws);
-    }
-
     private void printArrays(StringBuilder sb, int[] uids, String[] names) {
         sb.append("{ ");
         for (int i=0; i<uids.length; i++) {
@@ -529,31 +521,4 @@
                 new int[] { },
                 true);
     }
-
-    private void doTestStripNames(int[] uids, String[] names, int[] expected) throws Exception {
-        WorkSource ws1 = wsNew(uids, names);
-        WorkSource res = wsStripNames(ws1);
-        checkWorkSource("StripNames", res, expected);
-    }
-
-    public void testStripNamesSimple() throws Exception {
-        doTestStripNames(
-                new int[]    { 10,  20,  30,  40 },
-                new String[] { "A", "A", "A", "A" },
-                new int[]    { 10, 20, 30, 40 });
-    }
-
-    public void testStripNamesFull() throws Exception {
-        doTestStripNames(
-                new int[]    { 10,  10,  10,  10 },
-                new String[] { "A", "B", "C", "D" },
-                new int[]    { 10 });
-    }
-
-    public void testStripNamesComplex() throws Exception {
-        doTestStripNames(
-                new int[]    { 10,  20,  20,  30,  40,  40 },
-                new String[] { "A", "A", "B", "A", "A", "B" },
-                new int[]    { 10, 20, 30, 40 });
-    }
 }
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 a820ac0..841ecad 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -617,6 +617,38 @@
         looperThread.join();
     }
 
+    public void testOpenProxyFileDescriptor_largeFile() throws Exception {
+        final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() {
+            @Override
+            public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+                for (int i = 0; i < size; i++) {
+                    data[i] = 'L';
+                }
+                return size;
+            }
+
+            @Override
+            public long onGetSize() throws ErrnoException {
+                return 8L * 1024L * 1024L * 1024L;  // 8GB
+            }
+
+            @Override
+            public void onRelease() {}
+        };
+        final byte[] bytes = new byte[128];
+        try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor(
+                ParcelFileDescriptor.MODE_READ_ONLY, callback)) {
+            assertEquals(8L * 1024L * 1024L * 1024L, fd.getStatSize());
+
+            final int readBytes = Os.pread(
+                    fd.getFileDescriptor(), bytes, 0, bytes.length, fd.getStatSize() - 64L);
+            assertEquals(64, readBytes);
+            for (int i = 0; i < 64; i++) {
+                assertEquals('L', bytes[i]);
+            }
+        }
+    }
+
     private void assertStorageVolumesEquals(StorageVolume volume, StorageVolume clone)
             throws Exception {
         // Asserts equals() method.
@@ -696,7 +728,6 @@
 
     private List<File> getTargetFiles() {
         final List<File> targets = new ArrayList<File>();
-        targets.add(mContext.getFilesDir());
         for (File dir : mContext.getObbDirs()) {
             assertNotNull("Valid media must be inserted during CTS", dir);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/Android.mk b/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
index 0e24292..fd0c1da 100755
--- a/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
+++ b/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
@@ -29,8 +29,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	ub-uiautomator \
 	android-support-test \
-	android-support-v4 \
-	legacy-android-test
+	android-support-v4
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
index 3f09b25..2e4a4fe 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
@@ -15,6 +15,7 @@
 -->
 
 <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" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/tests/packageinstaller/emptytestapp/Android.mk b/tests/tests/packageinstaller/emptytestapp/Android.mk
index d098318..7574d1f 100644
--- a/tests/tests/packageinstaller/emptytestapp/Android.mk
+++ b/tests/tests/packageinstaller/emptytestapp/Android.mk
@@ -25,6 +25,6 @@
 LOCAL_SDK_VERSION := current
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/packageinstaller/externalsources/AndroidTest.xml b/tests/tests/packageinstaller/externalsources/AndroidTest.xml
index 2d782d8..a39baab 100644
--- a/tests/tests/packageinstaller/externalsources/AndroidTest.xml
+++ b/tests/tests/packageinstaller/externalsources/AndroidTest.xml
@@ -15,6 +15,7 @@
 -->
 
 <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">
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
index 921f5f1..06e858e 100644
--- a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
+++ b/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
@@ -41,7 +41,6 @@
     private PackageManager mPm;
     private String mPackageName;
     private UiDevice mUiDevice;
-    private boolean mHasFeature;
 
     @Before
     public void setUp() throws Exception {
@@ -49,7 +48,6 @@
         mPm = mContext.getPackageManager();
         mPackageName = mContext.getPackageName();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        mHasFeature = !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
 
     private void setAppOpsMode(String mode) throws IOException {
@@ -86,9 +84,6 @@
 
     @Test
     public void testManageUnknownSourcesExists() {
-        if (!mHasFeature) {
-            return;
-        }
         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);
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index da443fe..1a4eec6 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -33,7 +33,7 @@
     ctstestrunner \
     guava \
     android-ex-camera2 \
-    legacy-android-test
+    compatibility-device-util
 
 LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni libnativehelper_compat_libc++
 
@@ -44,7 +44,8 @@
 # uncomment when b/13249777 is fixed
 #LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index b99cbc2..4fbcc10 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Permission 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" />
diff --git a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
index c29d5f5..7285fd0 100644
--- a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
@@ -16,59 +16,294 @@
 
 package android.permission.cts;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_READ_CALENDAR;
+import static android.app.AppOpsManager.OPSTR_READ_SMS;
+import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
+import static com.android.compatibility.common.util.AppOpsUtils.allowedOperationLogged;
+import static com.android.compatibility.common.util.AppOpsUtils.rejectedOperationLogged;
+import static com.android.compatibility.common.util.AppOpsUtils.setOpMode;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.Manifest.permission;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpChangedListener;
 import android.content.Context;
-import android.test.AndroidTestCase;
+import android.os.Process;
+import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.AttributeSet;
-import junit.framework.AssertionFailedError;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import com.android.compatibility.common.util.AppOpsUtils;
 
-public class AppOpsTest extends AndroidTestCase {
-    static final Class<?>[] sSetModeSignature = new Class[] {
-            Context.class, AttributeSet.class};
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
+public class AppOpsTest extends InstrumentationTestCase {
     private AppOpsManager mAppOps;
+    private Context mContext;
+    private String mOpPackageName;
+    private int mMyUid;
+
+    // These permissions and opStrs must map to the same op codes.
+    private static Map<String, String> permissionToOpStr = new HashMap<>();
+
+    static {
+        permissionToOpStr.put(permission.ACCESS_COARSE_LOCATION,
+                AppOpsManager.OPSTR_COARSE_LOCATION);
+        permissionToOpStr.put(permission.ACCESS_FINE_LOCATION, AppOpsManager.OPSTR_FINE_LOCATION);
+        permissionToOpStr.put(permission.READ_CONTACTS, AppOpsManager.OPSTR_READ_CONTACTS);
+        permissionToOpStr.put(permission.WRITE_CONTACTS, AppOpsManager.OPSTR_WRITE_CONTACTS);
+        permissionToOpStr.put(permission.READ_CALL_LOG, AppOpsManager.OPSTR_READ_CALL_LOG);
+        permissionToOpStr.put(permission.WRITE_CALL_LOG, AppOpsManager.OPSTR_WRITE_CALL_LOG);
+        permissionToOpStr.put(permission.READ_CALENDAR, AppOpsManager.OPSTR_READ_CALENDAR);
+        permissionToOpStr.put(permission.WRITE_CALENDAR, AppOpsManager.OPSTR_WRITE_CALENDAR);
+        permissionToOpStr.put(permission.CALL_PHONE, AppOpsManager.OPSTR_CALL_PHONE);
+        permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
+        permissionToOpStr.put(permission.RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS);
+        permissionToOpStr.put(permission.RECEIVE_MMS, AppOpsManager.OPSTR_RECEIVE_MMS);
+        permissionToOpStr.put(permission.RECEIVE_WAP_PUSH, AppOpsManager.OPSTR_RECEIVE_WAP_PUSH);
+        permissionToOpStr.put(permission.SEND_SMS, AppOpsManager.OPSTR_SEND_SMS);
+        permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
+        permissionToOpStr.put(permission.WRITE_SETTINGS, AppOpsManager.OPSTR_WRITE_SETTINGS);
+        permissionToOpStr.put(permission.SYSTEM_ALERT_WINDOW,
+                AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+        permissionToOpStr.put(permission.ACCESS_NOTIFICATIONS,
+                AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+        permissionToOpStr.put(permission.CAMERA, AppOpsManager.OPSTR_CAMERA);
+        permissionToOpStr.put(permission.RECORD_AUDIO, AppOpsManager.OPSTR_RECORD_AUDIO);
+        permissionToOpStr.put(permission.READ_PHONE_STATE, AppOpsManager.OPSTR_READ_PHONE_STATE);
+        permissionToOpStr.put(permission.ADD_VOICEMAIL, AppOpsManager.OPSTR_ADD_VOICEMAIL);
+        permissionToOpStr.put(permission.USE_SIP, AppOpsManager.OPSTR_USE_SIP);
+        permissionToOpStr.put(permission.PROCESS_OUTGOING_CALLS,
+                AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS);
+        permissionToOpStr.put(permission.BODY_SENSORS, AppOpsManager.OPSTR_BODY_SENSORS);
+        permissionToOpStr.put(permission.READ_CELL_BROADCASTS,
+                AppOpsManager.OPSTR_READ_CELL_BROADCASTS);
+        permissionToOpStr.put(permission.READ_EXTERNAL_STORAGE,
+                AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE);
+        permissionToOpStr.put(permission.WRITE_EXTERNAL_STORAGE,
+                AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE);
+    }
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mAppOps = (AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mContext = getInstrumentation().getContext();
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mOpPackageName = mContext.getOpPackageName();
+        mMyUid = Process.myUid();
         assertNotNull(mAppOps);
+
+        // Reset app ops state for this test package to the system default.
+        AppOpsUtils.reset(mOpPackageName);
+    }
+
+    public void testNoteOpAndCheckOp() throws Exception {
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+        assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_ALLOWED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_ALLOWED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_ALLOWED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED);
+        assertEquals(MODE_IGNORED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_IGNORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_IGNORED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_IGNORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT);
+        assertEquals(MODE_DEFAULT, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_DEFAULT, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_DEFAULT, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_DEFAULT, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
+        assertEquals(MODE_ERRORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_ERRORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        try {
+            mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+        try {
+            mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testStartOpAndFinishOp() throws Exception {
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+        assertEquals(MODE_ALLOWED, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        mAppOps.finishOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+        assertEquals(MODE_ALLOWED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        mAppOps.finishOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED);
+        assertEquals(MODE_IGNORED, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_IGNORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT);
+        assertEquals(MODE_DEFAULT, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        assertEquals(MODE_DEFAULT, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
+        assertEquals(MODE_ERRORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+        try {
+            mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testCheckPackagePassesCheck() throws Exception {
+        mAppOps.checkPackage(mMyUid, mOpPackageName);
+        mAppOps.checkPackage(Process.SYSTEM_UID, "android");
+    }
+
+    public void testCheckPackageDoesntPassCheck() throws Exception {
+        try {
+            // Package name doesn't match UID.
+            mAppOps.checkPackage(Process.SYSTEM_UID, mOpPackageName);
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            // Package name doesn't match UID.
+            mAppOps.checkPackage(mMyUid, "android");
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            // Package name missing
+            mAppOps.checkPackage(mMyUid, "");
+            fail("SecurityException expected");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testWatchingMode() throws Exception {
+        OnOpChangedListener watcher = mock(OnOpChangedListener.class);
+        try {
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+
+            mAppOps.startWatchingMode(OPSTR_READ_SMS, mOpPackageName, watcher);
+
+            // Make a change to the app op's mode.
+            reset(watcher);
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
+            verify(watcher).onOpChanged(OPSTR_READ_SMS, mOpPackageName);
+
+            // Make another change to the app op's mode.
+            reset(watcher);
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+            verify(watcher).onOpChanged(OPSTR_READ_SMS, mOpPackageName);
+
+            // Set mode to the same value as before - expect no call to the listener.
+            reset(watcher);
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+            verifyZeroInteractions(watcher);
+
+            mAppOps.stopWatchingMode(watcher);
+
+            // Make a change to the app op's mode. Since we already stopped watching the mode, the
+            // listener shouldn't be called.
+            reset(watcher);
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
+            verifyZeroInteractions(watcher);
+        } finally {
+            // Clean up registered watcher.
+            mAppOps.stopWatchingMode(watcher);
+        }
+    }
+
+    @SmallTest
+    public void 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._NUM_OP, opStrs.size());
+    }
+
+    @SmallTest
+    public void testOpCodesUnique() {
+        String[] opStrs = AppOpsManager.getOpStrs();
+        Set<Integer> opCodes = new HashSet<>();
+        for (String opStr : opStrs) {
+            opCodes.add(AppOpsManager.strOpToOp(opStr));
+        }
+        assertEquals("Not all app op codes are unique", opStrs.length, opCodes.size());
+    }
+
+    @SmallTest
+    public void testPermissionMapping() {
+        for (String permission : permissionToOpStr.keySet()) {
+            testPermissionMapping(permission, permissionToOpStr.get(permission));
+        }
+    }
+
+    private void testPermissionMapping(String permission, String opStr) {
+        // Do the public value => internal op code lookups.
+        String mappedOpStr = AppOpsManager.permissionToOp(permission);
+        assertEquals(mappedOpStr, opStr);
+        int mappedOpCode = AppOpsManager.permissionToOpCode(permission);
+        int mappedOpCode2 = AppOpsManager.strOpToOp(opStr);
+        assertEquals(mappedOpCode, mappedOpCode2);
+
+        // Do the internal op code => public value lookup (reverse lookup).
+        String permissionMappedBack = AppOpsManager.opToPermission(mappedOpCode);
+        assertEquals(permission, permissionMappedBack);
     }
 
     /**
      * Test that the app can not change the app op mode for itself.
      */
     @SmallTest
-    public void testSetMode() {
-        boolean gotToTest = false;
+    public void testCantSetModeForSelf() {
         try {
-            Method setMode = mAppOps.getClass().getMethod("setMode", int.class, int.class,
-                    String.class, int.class);
-            int writeSmsOp = mAppOps.getClass().getField("OP_WRITE_SMS").getInt(mAppOps);
-            gotToTest = true;
-            setMode.invoke(mAppOps, writeSmsOp, android.os.Process.myUid(),
-                    getContext().getPackageName(), AppOpsManager.MODE_ALLOWED);
+            int writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS");
+            mAppOps.setMode(writeSmsOp, mMyUid, mOpPackageName, AppOpsManager.MODE_ALLOWED);
             fail("Was able to set mode for self");
-        } catch (NoSuchFieldException e) {
-            throw new AssertionError("Unable to find OP_WRITE_SMS", e);
-        } catch (NoSuchMethodException e) {
-            throw new AssertionError("Unable to find setMode method", e);
-        } catch (InvocationTargetException e) {
-            if (!gotToTest) {
-                throw new AssertionError("Whoops", e);
-            }
-            // If we got to the test, we want it to have thrown a security exception.
-            // We need to look inside of the wrapper exception to see.
-            Throwable t = e.getCause();
-            if (!(t instanceof SecurityException)) {
-                throw new AssertionError("Did not throw SecurityException", e);
-            }
-        } catch (IllegalAccessException e) {
-            throw new AssertionError("Whoops", e);
+        } catch (SecurityException expected) {
         }
     }
+
+    @SmallTest
+    public void testGetOpsForPackage_opsAreLogged() throws Exception {
+        // This test checks if operations get logged by the system. It needs to start with a clean
+        // slate, i.e. these ops can't have been logged previously for this test package. The reason
+        // is that there's no API for clearing the app op logs before a test run. However, the op
+        // logs are cleared when this test package is reinstalled between test runs. To make sure
+        // that other test methods in this class don't affect this test method, here we use
+        // operations that are not used by any other test cases.
+        String mustNotBeLogged = "Operation mustn't be logged before the test runs";
+        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
+        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
+
+        setOpMode(mOpPackageName, OPSTR_RECORD_AUDIO, MODE_ALLOWED);
+        setOpMode(mOpPackageName, OPSTR_READ_CALENDAR, MODE_ERRORED);
+
+        // Note an op that's allowed.
+        mAppOps.noteOp(OPSTR_RECORD_AUDIO, mMyUid, mOpPackageName);
+        String mustBeLogged = "Operation must be logged";
+        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
+
+        // Note another op that's not allowed.
+        mAppOps.noteOpNoThrow(OPSTR_READ_CALENDAR, mMyUid, mOpPackageName);
+        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
+        assertTrue(mustBeLogged, rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index ae6123e..4c42718 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -257,7 +257,7 @@
     @MediumTest
     public void testDevQtaguidSane() throws Exception {
         File f = new File("/dev/xt_qtaguid");
-        assertTrue(f.canRead());
+        assertFalse(f.canRead());
         assertFalse(f.canWrite());
         assertFalse(f.canExecute());
 
@@ -268,8 +268,8 @@
     @MediumTest
     public void testProcQtaguidCtrlSane() throws Exception {
         File f = new File("/proc/net/xt_qtaguid/ctrl");
-        assertTrue(f.canRead());
-        assertTrue(f.canWrite());
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
         assertFalse(f.canExecute());
 
         assertFileOwnedBy(f, "root");
@@ -279,7 +279,7 @@
     @MediumTest
     public void testProcQtaguidStatsSane() throws Exception {
         File f = new File("/proc/net/xt_qtaguid/stats");
-        assertTrue(f.canRead());
+        assertFalse(f.canRead());
         assertFalse(f.canWrite());
         assertFalse(f.canExecute());
 
diff --git a/tests/tests/permission2/Android.mk b/tests/tests/permission2/Android.mk
index 062aeb7..3df1bc9 100755
--- a/tests/tests/permission2/Android.mk
+++ b/tests/tests/permission2/Android.mk
@@ -24,12 +24,11 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	ctstestrunner \
-	legacy-android-test
+	ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index 2021b88..d4fb85f 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Permission 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" />
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 74e8024..7cf9971 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -53,6 +53,7 @@
     <protected-broadcast android:name="android.intent.action.UID_REMOVED" />
     <protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
     <protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.SPLIT_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BATTERY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BATTERY_LOW" />
@@ -159,6 +160,8 @@
     <protected-broadcast
         android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
@@ -171,8 +174,14 @@
     <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" />
     <protected-broadcast
+        android:name="android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" />
@@ -183,15 +192,23 @@
     <protected-broadcast
         android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
+    <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
+    <protected-broadcast
         android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
     <protected-broadcast
-        android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+        android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
@@ -299,6 +316,8 @@
     <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
     <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
 
+    <protected-broadcast android:name="com.android.server.stats.action.TRIGGER_COLLECTION" />
+
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
@@ -309,11 +328,21 @@
     <protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
     <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
     <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
+    <protected-broadcast android:name="com.android.server.net.action.SNOOZE_RAPID" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE" />
+    <protected-broadcast android:name="com.android.server.wifi.wakeup.DISMISS_NOTIFICATION" />
+    <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES" />
+    <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS" />
+    <protected-broadcast android:name="com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE" />
     <protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
     <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+    <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
     <protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -392,6 +421,7 @@
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
     <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
+    <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
 
     <!-- Added in N -->
     <protected-broadcast android:name="android.intent.action.ANR" />
@@ -470,7 +500,6 @@
     <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
     <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
     <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
-    <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
     <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
@@ -489,6 +518,9 @@
     <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -533,8 +565,6 @@
     <protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
 
     <!-- Added in O -->
-    <!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
-    <protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
     <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
     <protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
     <protected-broadcast android:name="android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED" />
@@ -547,6 +577,21 @@
     <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
     <protected-broadcast android:name="com.android.server.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" />
+    <protected-broadcast android:name="com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK" />
+
+    <!-- Made protected in P (was introduced in JB-MR2) -->
+    <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+    <protected-broadcast android:name="android.telephony.euicc.action.OTA_STATUS_CHANGED" />
+
+    <!-- Added in P -->
+    <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
+    <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+    <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" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -563,6 +608,7 @@
         android:icon="@drawable/perm_group_contacts"
         android:label="@string/permgrouplab_contacts"
         android:description="@string/permgroupdesc_contacts"
+        android:request="@string/permgrouprequest_contacts"
         android:priority="100" />
 
     <!-- Allows an application to read the user's contacts data.
@@ -593,6 +639,7 @@
         android:icon="@drawable/perm_group_calendar"
         android:label="@string/permgrouplab_calendar"
         android:description="@string/permgroupdesc_calendar"
+        android:request="@string/permgrouprequest_calendar"
         android:priority="200" />
 
     <!-- Allows an application to read the user's calendar data.
@@ -623,6 +670,7 @@
         android:icon="@drawable/perm_group_sms"
         android:label="@string/permgrouplab_sms"
         android:description="@string/permgroupdesc_sms"
+        android:request="@string/permgrouprequest_sms"
         android:priority="300" />
 
     <!-- Allows an application to send SMS messages.
@@ -699,6 +747,7 @@
         android:icon="@drawable/perm_group_storage"
         android:label="@string/permgrouplab_storage"
         android:description="@string/permgroupdesc_storage"
+        android:request="@string/permgrouprequest_storage"
         android:priority="900" />
 
     <!-- Allows an application to read from external storage.
@@ -760,6 +809,7 @@
         android:icon="@drawable/perm_group_location"
         android:label="@string/permgrouplab_location"
         android:description="@string/permgroupdesc_location"
+        android:request="@string/permgrouprequest_location"
         android:priority="400" />
 
     <!-- Allows an app to access precise location.
@@ -792,6 +842,7 @@
         android:icon="@drawable/perm_group_phone_calls"
         android:label="@string/permgrouplab_phone"
         android:description="@string/permgroupdesc_phone"
+        android:request="@string/permgrouprequest_phone"
         android:priority="500" />
 
     <!-- Allows read only access to phone state, including the phone number of the device,
@@ -931,6 +982,23 @@
                 android:description="@string/permdesc_manageOwnCalls"
                 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
+         which are involved in the handover; the initiating and receiving devices.  The initiating
+         device is where the request to handover the call was started, and the receiving device is
+         where the handover request is confirmed by the other party.<p>
+         This permission protects access to the
+         {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} which
+         the receiving side of the handover uses to accept a handover.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.ACCEPT_HANDOVER"
+                android:permissionGroup="android.permission-group.PHONE"
+                android.label="@string/permlab_acceptHandover"
+                android:description="@string/permdesc_acceptHandovers"
+                android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device microphone                        -->
     <!-- ====================================================================== -->
@@ -943,6 +1011,7 @@
         android:icon="@drawable/perm_group_microphone"
         android:label="@string/permgrouplab_microphone"
         android:description="@string/permgroupdesc_microphone"
+        android:request="@string/permgrouprequest_microphone"
         android:priority="600" />
 
     <!-- Allows an application to record audio.
@@ -985,6 +1054,7 @@
         android:icon="@drawable/perm_group_camera"
         android:label="@string/permgrouplab_camera"
         android:description="@string/permgroupdesc_camera"
+        android:request="@string/permgrouprequest_camera"
         android:priority="700" />
 
     <!-- Required to be able to access the camera device.
@@ -1009,11 +1079,12 @@
     <eat-comment />
 
     <!-- Used for permissions that are associated with accessing
-         camera or capturing images/video from the device. -->
+         body or environmental sensors. -->
     <permission-group android:name="android.permission-group.SENSORS"
         android:icon="@drawable/perm_group_sensors"
         android:label="@string/permgrouplab_sensors"
         android:description="@string/permgroupdesc_sensors"
+        android:request="@string/permgrouprequest_sensors"
         android:priority="800" />
 
     <!-- Allows an application to access data from sensors that the user uses to
@@ -1349,6 +1420,12 @@
     <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows internal management of Wi-Fi connectivity state when on
+         permission review mode.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_WIFI_WHEN_PERMISSION_REVIEW_REQUIRED"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -1396,6 +1473,12 @@
         android:label="@string/permlab_nfc"
         android:protectionLevel="normal" />
 
+    <!-- Allows applications to receive NFC transaction events.
+         <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.NFC_TRANSACTION_EVENT"
+        android:protectionLevel="normal" />
+
     <!-- @SystemApi Allows an internal user to use privileged ConnectivityManager APIs.
          @hide -->
     <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
@@ -1426,6 +1509,11 @@
     <permission android:name="android.permission.NFC_HANDOVER_STATUS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows internal management of Bluetooth state when on permission review mode.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_PERMISSION_REVIEW_REQUIRED"
+        android:protectionLevel="signature" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -1591,6 +1679,11 @@
     <permission android:name="android.permission.ACCESS_PDB_STATE"
         android:protectionLevel="signature" />
 
+    <!-- Allows testing if a passwords is forbidden by the admins.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows system update service to notify device owner about pending updates.
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
@@ -1705,14 +1798,38 @@
     <permission android:name="android.permission.SEND_EMBMS_INTENTS"
         android:protectionLevel="signature|privileged" />
 
+
+    <!-- Allows internal management of the sensor framework
+         @hide -->
+    <permission android:name="android.permission.MANAGE_SENSORS"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by an ImsService to ensure that only the
          system can bind to it.
-         <p>Protection level: signature|privileged
+         <p>Protection level: signature|privileged|vendorPrivileged
          @SystemApi
          @hide
     -->
     <permission android:name="android.permission.BIND_IMS_SERVICE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+    <!-- Must be required by a telephony data service to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
+        android:protectionLevel="signature" />
+
+    <!-- Must be required by a NetworkService to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE"
+                android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to manage embedded subscriptions (those on a eUICC)
          through EuiccManager APIs.
@@ -1764,6 +1881,15 @@
     <permission android:name="android.permission.ALLOCATE_AGGRESSIVE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide
+         Allows an application to use reserved disk space.
+         <p>Not for use by third-party applications.  Should only be requested by
+         apps that provide core system functionality, to ensure system stability
+         when disk is otherwise completely full.
+    -->
+    <permission android:name="android.permission.USE_RESERVED_DISK"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ================================== -->
     <!-- Permissions for screenlock         -->
     <!-- ================================== -->
@@ -1861,11 +1987,11 @@
 
     <!-- @SystemApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|development" />
 
     <!-- @SystemApi @hide Allows an application to embed other activities -->
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
-                android:protectionLevel="signature|privileged" />
+                android:protectionLevel="signature|privileged|development" />
 
     <!-- Allows an application to start any activity, regardless of permission
          protection or exported state.
@@ -2031,6 +2157,11 @@
     <eat-comment />
 
     <!-- Allows an application to install a shortcut in Launcher.
+         <p>In Android O (API level 26) and higher, the <code>INSTALL_SHORTCUT</code> broadcast no
+         longer has any effect on your app because it's a private, implicit
+         broadcast. Instead, you should create an app shortcut by using the
+         {@link android.content.pm.ShortcutManager#requestPinShortcut requestPinShortcut()}
+         method from the {@link android.content.pm.ShortcutManager} class.
          <p>Protection level: normal
     -->
     <permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
@@ -2252,6 +2383,11 @@
     <permission android:name="android.permission.RECOVERY"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to read system update info.
+         @hide -->
+    <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO"
+        android:protectionLevel="signature" />
+
     <!-- Allows the system to bind to an application's task services
          @hide -->
     <permission android:name="android.permission.BIND_JOB_SERVICE"
@@ -2271,7 +2407,7 @@
          <p>An application requesting this permission is responsible for
          verifying the source and integrity of the update before passing
          it off to the installer components.
-         @hide -->
+         @SystemApi @hide -->
     <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
         android:protectionLevel="signature|privileged" />
 
@@ -2391,7 +2527,8 @@
     <permission android:name="android.permission.UPDATE_DEVICE_STATS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi @hide Allows an application to collect battery statistics -->
+    <!-- @SystemApi @hide Allows an application to collect application operation statistics.
+         Not for use by third party apps. -->
     <permission android:name="android.permission.GET_APP_OPS_STATS"
         android:protectionLevel="signature|privileged|development" />
 
@@ -2416,10 +2553,10 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to use
-        {@link android.view.WindowManager.LayoutsParams#PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
-        to hide non-system-overlay windows.
-        <p>Not for use by third-party applications.
-        @hide
+         {@link android.view.WindowManager.LayoutsParams#PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
+         to hide non-system-overlay windows.
+         <p>Not for use by third-party applications.
+         @hide
     -->
     <permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
                 android:protectionLevel="signature|installer" />
@@ -2626,10 +2763,30 @@
     <permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
         android:protectionLevel="signature" />
 
-    <!-- @hide TODO(b/37563972): remove once clients use BIND_AUTOFILL_SERVICE -->
+   <!-- Alternative version of android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE.
+        This permission was renamed during the O previews but it was supported on the final O
+        release, so we need to carry it over.
+        <p>Protection level: signature
+        @hide
+    -->
     <permission android:name="android.permission.BIND_AUTOFILL"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an {@link android.service.autofill.AutofillFieldClassificationService}
+         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_AUTOFILL_FIELD_CLASSIFICATION_SERVICE"
+                android:protectionLevel="signature" />
+
+    <!-- Must be required by a android.service.textclassifier.TextClassifierService,
+         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_TEXTCLASSIFIER_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> -->
@@ -2755,8 +2912,9 @@
         android:protectionLevel="signature|appop" />
 
     <!-- Allows an application to request deleting packages. Apps
-         targeting APIs greater than 25 must hold this permission in
-         order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE}.
+         targeting APIs {@link android.os.Build.VERSION_CODES#P} or greater must hold this
+         permission in order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE} or
+         {@link android.content.pm.PackageInstaller#uninstall}.
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.REQUEST_DELETE_PACKAGES"
@@ -2767,6 +2925,22 @@
     <!-- @SystemApi Allows an application to install packages.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.INSTALL_PACKAGES"
+      android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Allows an application to install self updates. This is a limited version
+         of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+        <p>Not for use by third-party applications.
+        @hide
+    -->
+    <permission android:name="android.permission.INSTALL_SELF_UPDATES"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Allows an application to install updates. This is a limited version
+         of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+        <p>Not for use by third-party applications.
+        @hide
+    -->
+    <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES"
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows an application to clear user data.
@@ -2791,11 +2965,26 @@
         android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to delete cache files.
-    <p>Not for use by third-party applications. -->
+    <!-- @hide
+         Allows an application to change the status of Scoped Access Directory requests granted or
+         rejected by the user.
+         <p>This permission should <em>only</em> be requested by the platform
+         settings app.  This permission cannot be granted to third-party apps.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Old permission for deleting an app's cache files, no longer used,
+         but signals for us to quietly ignore calls instead of throwing an exception. -->
     <permission android:name="android.permission.DELETE_CACHE_FILES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to delete cache files.
+         @hide -->
+    <permission android:name="android.permission.INTERNAL_DELETE_CACHE_FILES"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to delete packages.
          <p>Not for use by third-party applications.
          <p>Starting in {@link android.os.Build.VERSION_CODES#N}, user confirmation is requested
@@ -2878,6 +3067,31 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to collect usage infomation about brightness slider changes.
+         <p>Not for use by third-party applications.</p>
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE"
+        android:protectionLevel="signature|privileged|development" />
+
+    <!-- Allows an application to collect ambient light stats.
+         <p>Not for use by third party applications.</p>
+         TODO: Make a system API
+         @hide -->
+    <permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS"
+        android:protectionLevel="signature|privileged|development" />
+
+    <!-- Allows an application to modify the display brightness configuration
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
+        android:protectionLevel="signature|privileged|development" />
+
+    <!-- Allows an application to control the system's display brightness
+         @hide -->
+    <permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -3033,10 +3247,10 @@
         android:protectionLevel="signature|privileged|development|appop" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- @hide Allows an application to change the app idle state of an app.
+    <!-- @hide @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" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
          access the network and acquire wakelocks.
@@ -3057,12 +3271,24 @@
     <permission android:name="android.permission.BATTERY_STATS"
         android:protectionLevel="signature|privileged|development" />
 
+    <!--Allows an application to manage statscompanion.
+    <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.STATSCOMPANION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control the backup and restore process.
     <p>Not for use by third-party applications.
          @hide pending API council -->
     <permission android:name="android.permission.BACKUP"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows application to manage RecoverableKeyStoreLoader.
+    <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.RECOVER_KEYSTORE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows a package to launch the secure full-backup confirmation UI.
          ONLY the system process may hold this permission.
          @hide -->
@@ -3083,6 +3309,17 @@
     <permission android:name="android.permission.BIND_APPWIDGET"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows sysui to manage user grants of slice permissions. -->
+    <permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to bind app's slices and get their
+         content. This content will be surfaced to the
+         user and not to leave the device.
+         <p>Not for use by third-party applications.-->
+    <permission android:name="android.permission.BIND_SLICE"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- @SystemApi Private permission, to restrict who can bring up a dialog to add a new
          keyguard widget
          @hide -->
@@ -3127,6 +3364,11 @@
     <permission android:name="android.permission.READ_SEARCH_INDEXABLES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Internal permission to allows an application to bind to suggestion service.
+        @hide -->
+    <permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @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. -->
@@ -3200,6 +3442,10 @@
     <permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
+    <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
+        android:protectionLevel="signature|verifier" />
+
     <!-- @SystemApi @hide Intent filter verifier needs to have this permission before the
          PackageManager will trust it to verify intent filters.
     -->
@@ -3286,6 +3532,12 @@
     <permission android:name="android.permission.PROVIDE_TRUST_AGENT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to show a message
+         on the keyguard when asking to dismiss it.
+         @hide For security reasons, this is a platform-only permission. -->
+    <permission android:name="android.permission.SHOW_KEYGUARD_MESSAGE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to launch the trust agent settings activity.
         @hide -->
     <permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS"
@@ -3431,6 +3683,7 @@
         @hide -->
     <permission android:name="android.permission.LOCAL_MAC_ADDRESS"
                 android:protectionLevel="signature|privileged" />
+    <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>
 
     <!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
         @hide -->
@@ -3459,11 +3712,19 @@
     @hide -->
     <permission android:name="android.permission.ACCESS_INSTANT_APPS"
             android:protectionLevel="signature|installer|verifier" />
+    <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
 
     <!-- Allows the holder to view the instant applications on the device.
     @hide -->
     <permission android:name="android.permission.VIEW_INSTANT_APPS"
-            android:protectionLevel="signature|preinstalled" />
+                android:protectionLevel="signature|preinstalled" />
+
+    <!-- Allows the holder to manage whether the system can bind to services
+         provided by instant apps. This permission is intended to protect
+         test/development fucntionality and should be used only in such cases.
+    @hide -->
+    <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+                android:protectionLevel="signature" />
 
     <!-- Allows receiving the usage of media resource e.g. video/audio codec and
          graphic memory.
@@ -3549,6 +3810,41 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
+    <!-- @SystemApi Allows to access all app shortcuts.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_SHORTCUTS"
+        android:protectionLevel="signature|textClassifier" />
+
+    <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
+         @hide -->
+    <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
+        android:protectionLevel="signature|textClassifier" />
+
+    <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_RUNTIME_PROFILES"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- @hide Allows audio policy management. -->
+    <permission android:name="android.permission.MANAGE_AUDIO_POLICY"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an application to turn on / off quiet mode.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MODIFY_QUIET_MODE"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- Allows internal management of the camera framework
+         @hide -->
+    <permission android:name="android.permission.MANAGE_CAMERA"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to control remote animations. See
+         {@link ActivityOptions#makeRemoteAnimation}
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
+        android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -3597,6 +3893,7 @@
                 android:excludeFromRecents="true"
                 android:label="@string/user_owner_label"
                 android:exported="true"
+                android:visibleToInstantApps="true"
                 >
         </activity>
         <activity-alias android:name="com.android.internal.app.ForwardIntentToParent"
@@ -3606,7 +3903,7 @@
         </activity-alias>
         <activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
                 android:targetActivity="com.android.internal.app.IntentForwarderActivity"
-                android:icon="@drawable/ic_corp_icon"
+                android:icon="@drawable/ic_corp_badge"
                 android:exported="true"
                 android:label="@string/managed_profile_label">
         </activity-alias>
@@ -3742,6 +4039,14 @@
                   android:excludeFromRecents="true">
         </activity>
 
+        <activity android:name="com.android.internal.app.HarmfulAppWarningActivity"
+                  android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+                  android:excludeFromRecents="true"
+                  android:process=":ui"
+                  android:label="@string/harmful_app_warning_title"
+                  android:exported="false">
+        </activity>
+
         <receiver android:name="com.android.server.BootReceiver"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
@@ -3773,6 +4078,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+                  android:permission="android.permission.UPDATE_CONFIG">
+            <intent-filter>
+                <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
@@ -3789,14 +4102,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.server.updates.TzDataInstallReceiver"
-                android:permission="android.permission.UPDATE_CONFIG">
-            <intent-filter>
-                <action android:name="android.intent.action.UPDATE_TZDATA" />
-                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
-            </intent-filter>
-        </receiver>
-
         <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
@@ -3821,6 +4126,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
+                  android:permission="android.permission.UPDATE_CONFIG">
+            <intent-filter>
+                <action android:name="com.android.internal.intent.action.UPDATE_CARRIER_ID_DB" />
+                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="com.android.server.MasterClearReceiver"
             android:permission="android.permission.MASTER_CLEAR">
             <intent-filter
@@ -3837,6 +4150,16 @@
             </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$PollingAlarmReceiver"
+                  android:permission="android.permission.STATSCOMPANION"
+                  android:exported="false">
+        </receiver>
+
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
@@ -3888,6 +4211,27 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-    </application>
+        <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.display.BrightnessIdleJob"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service
+                android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
+                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+                android:exported="true">
+            <meta-data
+                    android:name="android.accessibilityservice"
+                    android:resource="@xml/autofill_compat_accessibility_service" />
+        </service>
+
+</application>
 
 </manifest>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
index 4a68183..d32b547 100644
--- a/tests/tests/permission2/res/raw/automotive_android_manifest.xml
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -111,7 +111,11 @@
         android:protectionLevel="system|signature"
         android:label="@string/car_permission_label_vms_subscriber"
         android:description="@string/car_permission_desc_vms_subscriber" />
-
+    <permission
+        android:name="android.car.permission.CAR_DRIVING_STATE"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_driving_state"
+        android:description="@string/car_permission_desc_driving_state" />
     <!--  may replace this with system permission if proper one is defined. -->
     <permission
         android:name="android.car.permission.CONTROL_APP_BLOCKING"
@@ -177,15 +181,16 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
     <application android:label="Car service"
                  android:directBootAware="true"
                  android:allowBackup="false"
                  android:persistent="true">
 
-       
         <uses-library android:name="android.test.runner" />
- <service android:name=".CarService"
+        <service android:name=".CarService"
                 android:singleUser="true">
             <intent-filter>
                 <action android:name="android.car.ICar" />
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 50a5bce..4b8aedfc 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -117,10 +117,8 @@
             }
 
             // OEMs cannot change permission protection flags
-            final int expectedProtectionFlags = expectedPermission.protectionLevel
-                    & PermissionInfo.PROTECTION_MASK_FLAGS;
-            final int declaredProtectionFlags = declaredPermission.protectionLevel
-                    & PermissionInfo.PROTECTION_MASK_FLAGS;
+            final int expectedProtectionFlags = expectedPermission.getProtectionFlags();
+            final int declaredProtectionFlags = declaredPermission.getProtectionFlags();
             if (expectedProtectionFlags != declaredProtectionFlags) {
                 offendingList.add(
                         String.format(
@@ -147,7 +145,11 @@
         // OEMs cannot define permissions in the platform namespace
         for (String permission : declaredPermissionsMap.keySet()) {
             if (permission.startsWith(PLATFORM_ROOT_NAMESPACE)) {
-                offendingList.add("Cannot define permission in android namespace:" + permission);
+                final PermissionInfo permInfo = declaredPermissionsMap.get(permission);
+                offendingList.add(
+                        "Cannot define permission " + permission
+                        + ", package " + permInfo.packageName
+                        + " in android namespace");
             }
         }
 
@@ -158,11 +160,9 @@
                     if (declaredGroup.packageName.equals(PLATFORM_PACKAGE_NAME)
                             && declaredGroup.name.startsWith(PLATFORM_ROOT_NAMESPACE)) {
                         offendingList.add(
-                                "Cannot define group "
-                                        + declaredGroup.name
-                                        + ", package "
-                                        + declaredGroup.packageName
-                                        + " in android namespace");
+                                "Cannot define group " + declaredGroup.name
+                                + ", package " + declaredGroup.packageName
+                                + " in android namespace");
                     }
                 }
             }
@@ -254,9 +254,15 @@
                 case "privileged": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
                 } break;
+                case "vendorPrivileged": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
+                } break;
                 case "setup": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SETUP;
                 } break;
+                case "textClassifier": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
+                } break;
                 case "instant": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTANT;
                 } break;
diff --git a/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
index f85314e..585f9c6 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
@@ -35,6 +35,7 @@
 import java.util.TreeSet;
 
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 
 /**
  * Tests enforcement of signature|privileged permission whitelist:
@@ -64,16 +65,24 @@
         }
 
         List<PackageInfo> installedPackages = pm
-                .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES | GET_PERMISSIONS);
+                .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES);
 
         for (PackageInfo pkg : installedPackages) {
-            Set<String> requestedPrivPermissions = new TreeSet<>();
-            Set<String> grantedPrivPermissions = new TreeSet<>();
-            String[] requestedPermissions = pkg.requestedPermissions;
+            String packageName = pkg.packageName;
             if (!pkg.applicationInfo.isPrivilegedApp()
-                    || PLATFORM_PACKAGE_NAME.equals(pkg.packageName)) {
+                    || PLATFORM_PACKAGE_NAME.equals(packageName)) {
                 continue;
             }
+
+            Set<String> requestedPrivPermissions = new TreeSet<>();
+            Set<String> grantedPrivPermissions = new TreeSet<>();
+            PackageInfo factoryPackageInfo = pm
+                    .getPackageInfo(packageName, MATCH_FACTORY_ONLY | GET_PERMISSIONS);
+
+            assertNotNull("No system image version found for " + packageName, factoryPackageInfo);
+            String[] requestedPermissions = factoryPackageInfo.requestedPermissions;
+            int[] requestedPermissionsFlags = factoryPackageInfo.requestedPermissionsFlags;
+
             if (requestedPermissions == null || requestedPermissions.length == 0) {
                 continue;
             }
@@ -82,7 +91,7 @@
                 String permission = requestedPermissions[i];
                 if (platformPrivPermissions.contains(permission)) {
                     requestedPrivPermissions.add(permission);
-                    if ((pkg.requestedPermissionsFlags[i]
+                    if ((requestedPermissionsFlags[i]
                             & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
                         grantedPrivPermissions.add(permission);
                     }
@@ -93,9 +102,9 @@
             if (!requestedPrivPermissions.isEmpty()) {
                 Set<String> notGranted = new TreeSet<>(requestedPrivPermissions);
                 notGranted.removeAll(grantedPrivPermissions);
-                Set<String> whitelist = getPrivAppPermissions(pkg.packageName);
-                Set<String> denylist = getPrivAppDenyPermissions(pkg.packageName);
-                Log.i(TAG, "Application " + pkg.packageName + "."
+                Set<String> whitelist = getPrivAppPermissions(packageName);
+                Set<String> denylist = getPrivAppDenyPermissions(packageName);
+                Log.i(TAG, "Application " + packageName + "."
                         + " Requested permissions: " + requestedPrivPermissions + "."
                         + " Granted permissions: " + grantedPrivPermissions + "."
                         + " Not granted: " + notGranted + "."
@@ -108,11 +117,11 @@
                 notGrantedNotInDenylist.removeAll(denylist);
 
                 assertTrue("Not whitelisted permissions are granted for package "
-                                + pkg.packageName + ": " + grantedNotInWhitelist,
+                                + packageName + ": " + grantedNotInWhitelist,
                         grantedNotInWhitelist.isEmpty());
 
                 assertTrue("Requested permissions not granted for package "
-                                + pkg.packageName + ": " + notGrantedNotInDenylist,
+                                + packageName + ": " + notGrantedNotInDenylist,
                         notGrantedNotInDenylist.isEmpty());
             }
         }
diff --git a/tests/tests/preference/Android.mk b/tests/tests/preference/Android.mk
index 9223ce8..dd0ebf7 100644
--- a/tests/tests/preference/Android.mk
+++ b/tests/tests/preference/Android.mk
@@ -23,7 +23,9 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/preference/AndroidTest.xml b/tests/tests/preference/AndroidTest.xml
index f26d282..584bbc8 100644
--- a/tests/tests/preference/AndroidTest.xml
+++ b/tests/tests/preference/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/preference/OWNERS b/tests/tests/preference/OWNERS
new file mode 100644
index 0000000..d20511f
--- /dev/null
+++ b/tests/tests/preference/OWNERS
@@ -0,0 +1,2 @@
+pavlis@google.com
+clarabayarri@google.com
diff --git a/tests/tests/preference2/Android.mk b/tests/tests/preference2/Android.mk
index 7529f43..8f1c3a2 100644
--- a/tests/tests/preference2/Android.mk
+++ b/tests/tests/preference2/Android.mk
@@ -29,8 +29,9 @@
     ctstestrunner \
     compatibility-device-util \
     mockito-target-minus-junit4 \
-    ub-uiautomator \
-    legacy-android-test
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/preference2/AndroidTest.xml b/tests/tests/preference2/AndroidTest.xml
index 691c45b3..b13eba5 100644
--- a/tests/tests/preference2/AndroidTest.xml
+++ b/tests/tests/preference2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Preference test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/print/Android.mk b/tests/tests/print/Android.mk
index c9b036a..aba1b80 100644
--- a/tests/tests/print/Android.mk
+++ b/tests/tests/print/Android.mk
@@ -16,7 +16,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
@@ -27,8 +27,9 @@
 
 LOCAL_PACKAGE_NAME := CtsPrintTestCases
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target-minus-junit4 ctstestrunner ub-uiautomator compatibility-device-util android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := print-test-util-lib
 
 LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
index c5148c8..ef86c30 100644
--- a/tests/tests/print/AndroidManifest.xml
+++ b/tests/tests/print/AndroidManifest.xml
@@ -25,10 +25,12 @@
 
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.print.cts.PrintDocumentActivity"/>
+        <activity
+            android:name="android.print.test.PrintDocumentActivity"
+            android:theme="@style/NoAnimation" />
 
         <service
-            android:name="android.print.cts.services.FirstPrintService"
+            android:name="android.print.test.services.FirstPrintService"
             android:permission="android.permission.BIND_PRINT_SERVICE">
             <intent-filter>
                 <action android:name="android.printservice.PrintService" />
@@ -40,7 +42,7 @@
         </service>
 
         <service
-            android:name="android.print.cts.services.SecondPrintService"
+            android:name="android.print.test.services.SecondPrintService"
             android:permission="android.permission.BIND_PRINT_SERVICE">
             <intent-filter>
                 <action android:name="android.printservice.PrintService" />
@@ -52,25 +54,28 @@
         </service>
 
         <activity
-            android:name="android.print.cts.services.SettingsActivity"
+            android:name="android.print.test.services.SettingsActivity"
+            android:theme="@style/NoAnimation"
             android:exported="true">
         </activity>
 
         <activity
-            android:name="android.print.cts.services.AddPrintersActivity"
+            android:name="android.print.test.services.AddPrintersActivity"
+            android:theme="@style/NoAnimation"
             android:exported="true">
         </activity>
 
         <activity
-                android:name="android.print.cts.services.InfoActivity"
-                android:exported="true">
+            android:name="android.print.test.services.InfoActivity"
+            android:theme="@style/NoAnimation"
+            android:exported="true">
         </activity>
 
         <activity
-            android:name="android.print.cts.services.CustomPrintOptionsActivity"
+            android:name="android.print.test.services.CustomPrintOptionsActivity"
             android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
             android:exported="true"
-            android:theme="@style/Theme.Translucent">
+            android:theme="@style/NoAnimationTranslucent">
         </activity>
 
   </application>
diff --git a/tests/tests/print/AndroidTest.xml b/tests/tests/print/AndroidTest.xml
index 1c771d6..5ad9bb7 100644
--- a/tests/tests/print/AndroidTest.xml
+++ b/tests/tests/print/AndroidTest.xml
@@ -14,8 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Print test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="not-shardable" value="true" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="component" value="print" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/print/printTestUtilLib/Android.mk b/tests/tests/print/printTestUtilLib/Android.mk
new file mode 100644
index 0000000..358861b
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/Android.mk
@@ -0,0 +1,29 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := print-test-util-lib
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target-minus-junit4 ctstestrunner ub-uiautomator compatibility-device-util android-support-test
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
new file mode 100644
index 0000000..fbfff33
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
@@ -0,0 +1,1150 @@
+/*
+ * 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.print.test;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.print.test.Utils.getPrintManager;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.pdf.PdfDocument;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.pdf.PrintedPdfDocument;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.StubbablePrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintJob;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+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.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+import org.mockito.InOrder;
+import org.mockito.stubbing.Answer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This is the base class for print tests.
+ */
+public abstract class BasePrintTest {
+    private static final String LOG_TAG = "BasePrintTest";
+
+    protected static final long OPERATION_TIMEOUT_MILLIS = 60000;
+    protected static final String PRINT_JOB_NAME = "Test";
+    static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
+
+    private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
+    private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
+    private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
+    private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
+    private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
+    private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
+    private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler";
+
+    private static final AtomicInteger sLastTestID = new AtomicInteger();
+    private int mTestId;
+    private PrintDocumentActivity mActivity;
+
+    private static String sDisabledPrintServicesBefore;
+
+    private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>();
+
+    public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity();
+
+    /**
+     * Return the UI device
+     *
+     * @return the UI device
+     */
+    public UiDevice getUiDevice() {
+        return UiDevice.getInstance(getInstrumentation());
+    }
+
+    private CallCounter mCancelOperationCounter;
+    private CallCounter mLayoutCallCounter;
+    private CallCounter mWriteCallCounter;
+    private CallCounter mWriteCancelCallCounter;
+    private CallCounter mStartCallCounter;
+    private CallCounter mFinishCallCounter;
+    private CallCounter mPrintJobQueuedCallCounter;
+    private CallCounter mCreateSessionCallCounter;
+    private CallCounter mDestroySessionCallCounter;
+    private CallCounter mDestroyActivityCallCounter = new CallCounter();
+    private CallCounter mCreateActivityCallCounter = new CallCounter();
+
+    private static String[] sEnabledImes;
+
+    private static String[] getEnabledImes() throws IOException {
+        List<String> imeList = new ArrayList<>();
+
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
+        try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                imeList.add(line);
+            }
+        }
+
+        String[] imeArray = new String[imeList.size()];
+        imeList.toArray(imeArray);
+
+        return imeArray;
+    }
+
+    private static void disableImes() throws Exception {
+        sEnabledImes = getEnabledImes();
+        for (String ime : sEnabledImes) {
+            String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
+            SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
+        }
+    }
+
+    private static void enableImes() throws Exception {
+        for (String ime : sEnabledImes) {
+            String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
+            SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
+        }
+        sEnabledImes = null;
+    }
+
+    protected static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Log.d(LOG_TAG, "setUpClass()");
+
+        Instrumentation instrumentation = getInstrumentation();
+
+        // Make sure we start with a clean slate.
+        Log.d(LOG_TAG, "clearPrintSpoolerData()");
+        clearPrintSpoolerData();
+        Log.d(LOG_TAG, "disableImes()");
+        disableImes();
+        Log.d(LOG_TAG, "disablePrintServices()");
+        disablePrintServices(instrumentation.getTargetContext().getPackageName());
+
+        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+        // Dexmaker is used by mockito.
+        System.setProperty("dexmaker.dexcache", instrumentation
+                .getTargetContext().getCacheDir().getPath());
+
+        Log.d(LOG_TAG, "setUpClass() done");
+    }
+
+    /**
+     * Disable all print services beside the ones we want to leave enabled.
+     *
+     * @param packageToLeaveEnabled The package of the services to leave enabled.
+     */
+    private static void disablePrintServices(@NonNull String packageToLeaveEnabled)
+            throws IOException {
+        Instrumentation instrumentation = getInstrumentation();
+
+        sDisabledPrintServicesBefore = SystemUtil.runShellCommand(instrumentation,
+                "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
+
+        Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
+        List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
+                .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
+
+        StringBuilder builder = new StringBuilder();
+        for (ResolveInfo service : installedServices) {
+            if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append(":");
+            }
+            builder.append(new ComponentName(service.serviceInfo.packageName,
+                    service.serviceInfo.name).flattenToString());
+        }
+
+        SystemUtil.runShellCommand(instrumentation, "settings put secure "
+                + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
+    }
+
+    /**
+     * Revert {@link #disablePrintServices(String)}
+     */
+    private static  void enablePrintServices() throws IOException {
+        SystemUtil.runShellCommand(getInstrumentation(),
+                "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
+                        + sDisabledPrintServicesBefore);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(LOG_TAG, "setUp()");
+
+        assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_PRINTING));
+
+        // Initialize the latches.
+        Log.d(LOG_TAG, "init counters");
+        mCancelOperationCounter = new CallCounter();
+        mLayoutCallCounter = new CallCounter();
+        mStartCallCounter = new CallCounter();
+        mFinishCallCounter = new CallCounter();
+        mWriteCallCounter = new CallCounter();
+        mWriteCancelCallCounter = new CallCounter();
+        mFinishCallCounter = new CallCounter();
+        mPrintJobQueuedCallCounter = new CallCounter();
+        mCreateSessionCallCounter = new CallCounter();
+        mDestroySessionCallCounter = new CallCounter();
+
+        mTestId = sLastTestID.incrementAndGet();
+        sIdToTest.put(mTestId, this);
+
+        // Create the activity if needed
+        if (!mShouldStartActivityRule.mNoActivity) {
+            createActivity();
+        }
+
+        Log.d(LOG_TAG, "setUp() done");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Log.d(LOG_TAG, "tearDown()");
+
+        finishActivity();
+
+        sIdToTest.remove(mTestId);
+
+        Log.d(LOG_TAG, "tearDown() done");
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        Log.d(LOG_TAG, "tearDownClass()");
+
+        Instrumentation instrumentation = getInstrumentation();
+
+        Log.d(LOG_TAG, "enablePrintServices()");
+        enablePrintServices();
+
+        Log.d(LOG_TAG, "enableImes()");
+        enableImes();
+
+        // Make sure the spooler is cleaned, this also un-approves all services
+        Log.d(LOG_TAG, "clearPrintSpoolerData()");
+        clearPrintSpoolerData();
+
+        SystemUtil.runShellCommand(instrumentation, "settings put secure "
+                    + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
+
+        Log.d(LOG_TAG, "tearDownClass() done");
+    }
+
+    protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
+            final PrintAttributes attributes) {
+        android.print.PrintJob[] printJob = new android.print.PrintJob[1];
+        // Initiate printing as if coming from the app.
+        getInstrumentation().runOnMainSync(() -> {
+            PrintManager printManager = (PrintManager) getActivity()
+                    .getSystemService(Context.PRINT_SERVICE);
+            printJob[0] = printManager.print("Print job", adapter, attributes);
+        });
+
+        return printJob[0];
+    }
+
+    protected void print(PrintDocumentAdapter adapter) {
+        print(adapter, (PrintAttributes) null);
+    }
+
+    protected void print(PrintDocumentAdapter adapter, String printJobName) {
+        print(adapter, printJobName, null);
+    }
+
+    /**
+     * Start printing
+     *
+     * @param adapter      Adapter supplying data to print
+     * @param printJobName The name of the print job
+     */
+    protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName,
+            @Nullable PrintAttributes attributes) {
+        // Initiate printing as if coming from the app.
+        getInstrumentation()
+                .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter,
+                        attributes));
+    }
+
+    protected void onCancelOperationCalled() {
+        mCancelOperationCounter.call();
+    }
+
+    public void onStartCalled() {
+        mStartCallCounter.call();
+    }
+
+    protected void onLayoutCalled() {
+        mLayoutCallCounter.call();
+    }
+
+    protected int getWriteCallCount() {
+        return mWriteCallCounter.getCallCount();
+    }
+
+    protected void onWriteCalled() {
+        mWriteCallCounter.call();
+    }
+
+    protected void onWriteCancelCalled() {
+        mWriteCancelCallCounter.call();
+    }
+
+    protected void onFinishCalled() {
+        mFinishCallCounter.call();
+    }
+
+    protected void onPrintJobQueuedCalled() {
+        mPrintJobQueuedCallCounter.call();
+    }
+
+    protected void onPrinterDiscoverySessionCreateCalled() {
+        mCreateSessionCallCounter.call();
+    }
+
+    protected void onPrinterDiscoverySessionDestroyCalled() {
+        mDestroySessionCallCounter.call();
+    }
+
+    protected void waitForCancelOperationCallbackCalled() {
+        waitForCallbackCallCount(mCancelOperationCounter, 1,
+                "Did not get expected call to onCancel for the current operation.");
+    }
+
+    protected void waitForPrinterDiscoverySessionCreateCallbackCalled() {
+        waitForCallbackCallCount(mCreateSessionCallCounter, 1,
+                "Did not get expected call to onCreatePrinterDiscoverySession.");
+    }
+
+    public void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
+        waitForCallbackCallCount(mDestroySessionCallCounter, count,
+                "Did not get expected call to onDestroyPrinterDiscoverySession.");
+    }
+
+    protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
+        waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
+                "Did not get expected call to onPrintJobQueued.");
+    }
+
+    protected void waitForAdapterStartCallbackCalled() {
+        waitForCallbackCallCount(mStartCallCounter, 1,
+                "Did not get expected call to start.");
+    }
+
+    protected void waitForAdapterFinishCallbackCalled() {
+        waitForCallbackCallCount(mFinishCallCounter, 1,
+                "Did not get expected call to finish.");
+    }
+
+    protected void waitForLayoutAdapterCallbackCount(int count) {
+        waitForCallbackCallCount(mLayoutCallCounter, count,
+                "Did not get expected call to layout.");
+    }
+
+    public void waitForWriteAdapterCallback(int count) {
+        waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
+    }
+
+    protected void waitForWriteCancelCallback(int count) {
+        waitForCallbackCallCount(mWriteCancelCallCounter, count,
+                "Did not get expected cancel of write.");
+    }
+
+    private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
+        try {
+            counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
+        } catch (TimeoutException te) {
+            fail(message);
+        }
+    }
+
+    /**
+     * Indicate the print activity was created.
+     */
+    static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) {
+        synchronized (sIdToTest) {
+            BasePrintTest test = sIdToTest.get(testId);
+            if (test != null) {
+                test.mActivity = activity;
+                test.mCreateActivityCallCounter.call();
+            }
+        }
+    }
+
+    /**
+     * Indicate the print activity was destroyed.
+     */
+    static void onActivityDestroyCalled(int testId) {
+        synchronized (sIdToTest) {
+            BasePrintTest test = sIdToTest.get(testId);
+            if (test != null) {
+                test.mDestroyActivityCallCounter.call();
+            }
+        }
+    }
+
+    private void finishActivity() {
+        Activity activity = mActivity;
+
+        if (activity != null) {
+            if (!activity.isFinishing()) {
+                activity.finish();
+            }
+
+            while (!activity.isDestroyed()) {
+                int creates = mCreateActivityCallCounter.getCallCount();
+                waitForCallbackCallCount(mDestroyActivityCallCounter, creates,
+                        "Activity was not destroyed");
+            }
+        }
+    }
+
+    /**
+     * Get the number of ties the print activity was destroyed.
+     *
+     * @return The number of onDestroy calls on the print activity.
+     */
+    protected int getActivityDestroyCallbackCallCount() {
+        return mDestroyActivityCallCounter.getCallCount();
+    }
+
+    /**
+     * Get the number of ties the print activity was created.
+     *
+     * @return The number of onCreate calls on the print activity.
+     */
+    private int getActivityCreateCallbackCallCount() {
+        return mCreateActivityCallCounter.getCallCount();
+    }
+
+    /**
+     * Wait until create was called {@code count} times.
+     *
+     * @param count The number of create calls to expect.
+     */
+    private void waitForActivityCreateCallbackCalled(int count) {
+        waitForCallbackCallCount(mCreateActivityCallCounter, count,
+                "Did not get expected call to create.");
+    }
+
+    /**
+     * Reset all counters.
+     */
+    public void resetCounters() {
+        mCancelOperationCounter.reset();
+        mLayoutCallCounter.reset();
+        mWriteCallCounter.reset();
+        mWriteCancelCallCounter.reset();
+        mStartCallCounter.reset();
+        mFinishCallCounter.reset();
+        mPrintJobQueuedCallCounter.reset();
+        mCreateSessionCallCounter.reset();
+        mDestroySessionCallCounter.reset();
+        mDestroyActivityCallCounter.reset();
+        mCreateActivityCallCounter.reset();
+    }
+
+    /**
+     * Wait until the message is shown that indicates that a printer is unavailable.
+     *
+     * @throws Exception If anything was unexpected.
+     */
+    protected void waitForPrinterUnavailable() throws Exception {
+        final String printerUnavailableMessage =
+                getPrintSpoolerString("print_error_printer_unavailable");
+
+        UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.printspooler:id/message"));
+        if (!message.getText().equals(printerUnavailableMessage)) {
+            throw new Exception("Wrong message: " + message.getText() + " instead of "
+                    + printerUnavailableMessage);
+        }
+    }
+
+    protected void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
+        try {
+            long delay = 1;
+            while (true) {
+                try {
+                    UiDevice uiDevice = getUiDevice();
+                    UiObject destinationSpinner = uiDevice.findObject(new UiSelector()
+                            .resourceId("com.android.printspooler:id/destination_spinner"));
+
+                    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);
+                }
+
+                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) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
+        UiObject button;
+        if (confirm) {
+            button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button1"));
+        } else {
+            button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button2"));
+        }
+        button.click();
+    }
+
+    protected void changeOrientation(String orientation) throws UiObjectNotFoundException,
+            IOException {
+        try {
+            UiDevice uiDevice = getUiDevice();
+            UiObject orientationSpinner = uiDevice.findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/orientation_spinner"));
+            orientationSpinner.click();
+            UiObject orientationOption = uiDevice.findObject(new UiSelector().text(orientation));
+            orientationOption.click();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    public String getOrientation() throws UiObjectNotFoundException, IOException {
+        try {
+            UiObject orientationSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/orientation_spinner"));
+            return orientationSpinner.getText();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException, IOException {
+        try {
+            UiDevice uiDevice = getUiDevice();
+            UiObject mediaSizeSpinner = uiDevice.findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/paper_size_spinner"));
+            mediaSizeSpinner.click();
+            UiObject mediaSizeOption = uiDevice.findObject(new UiSelector().text(mediaSize));
+            mediaSizeOption.click();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void changeColor(String color) throws UiObjectNotFoundException, IOException {
+        try {
+            UiDevice uiDevice = getUiDevice();
+            UiObject colorSpinner = uiDevice.findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/color_spinner"));
+            colorSpinner.click();
+            UiObject colorOption = uiDevice.findObject(new UiSelector().text(color));
+            colorOption.click();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    public String getColor() throws UiObjectNotFoundException, IOException {
+        try {
+            UiObject colorSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/color_spinner"));
+            return colorSpinner.getText();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void changeDuplex(String duplex) throws UiObjectNotFoundException, IOException {
+        try {
+            UiDevice uiDevice = getUiDevice();
+            UiObject duplexSpinner = uiDevice.findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/duplex_spinner"));
+            duplexSpinner.click();
+            UiObject duplexOption = uiDevice.findObject(new UiSelector().text(duplex));
+            duplexOption.click();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void changeCopies(int newCopies) throws UiObjectNotFoundException, IOException {
+        try {
+            UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/copies_edittext"));
+            copies.setText(Integer.valueOf(newCopies).toString());
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected String getCopies() throws UiObjectNotFoundException, IOException {
+        try {
+            UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/copies_edittext"));
+            return copies.getText();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void assertNoPrintButton() throws UiObjectNotFoundException, IOException {
+        assertFalse(getUiDevice().hasObject(By.res("com.android.printspooler:id/print_button")));
+    }
+
+    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) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void clickRetryButton() throws UiObjectNotFoundException, IOException {
+        try {
+            UiObject retryButton = getUiDevice().findObject(new UiSelector().resourceId(
+                    "com.android.printspooler:id/action_button"));
+            retryButton.click();
+        } catch (UiObjectNotFoundException e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    public void dumpWindowHierarchy() throws IOException {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        getUiDevice().dumpWindowHierarchy(os);
+
+        Log.w(LOG_TAG, "Window hierarchy:");
+        for (String line : os.toString("UTF-8").split("\n")) {
+            Log.w(LOG_TAG, line);
+        }
+    }
+
+    protected PrintDocumentActivity getActivity() {
+        return mActivity;
+    }
+
+    protected void createActivity() throws IOException {
+        Log.d(LOG_TAG, "createActivity()");
+
+        int createBefore = getActivityCreateCallbackCallCount();
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.putExtra(TEST_ID, mTestId);
+
+        Instrumentation instrumentation = getInstrumentation();
+
+        // Unlock screen.
+        SystemUtil.runShellCommand(instrumentation, "input keyevent KEYCODE_WAKEUP");
+
+        intent.setClassName(instrumentation.getTargetContext().getPackageName(),
+                PrintDocumentActivity.class.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        instrumentation.startActivitySync(intent);
+
+        waitForActivityCreateCallbackCalled(createBefore + 1);
+    }
+
+    protected void openPrintOptions() throws UiObjectNotFoundException {
+        UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.printspooler:id/expand_collapse_handle"));
+        expandHandle.click();
+    }
+
+    protected void openCustomPrintOptions() throws UiObjectNotFoundException {
+        UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.printspooler:id/more_options_button"));
+        expandHandle.click();
+    }
+
+    protected static void clearPrintSpoolerData() throws Exception {
+        if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+                  PackageManager.FEATURE_PRINTING)) {
+            assertTrue("failed to clear print spooler data",
+                    SystemUtil.runShellCommand(getInstrumentation(), String.format(
+                            "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
+                            .contains(PM_CLEAR_SUCCESS_OUTPUT));
+        }
+    }
+
+    protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
+            PrintAttributes oldAttributes, PrintAttributes newAttributes,
+            final boolean forPreview) {
+        inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
+                any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
+                        new BaseMatcher<Bundle>() {
+                            @Override
+                            public boolean matches(Object item) {
+                                Bundle bundle = (Bundle) item;
+                                return forPreview == bundle.getBoolean(
+                                        PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
+                            }
+
+                            @Override
+                            public void describeTo(Description description) {
+                                /* do nothing */
+                            }
+                        }));
+    }
+
+    protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
+            Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
+        // Create a mock print adapter.
+        PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
+        if (layoutAnswer != null) {
+            doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
+                    any(PrintAttributes.class), any(CancellationSignal.class),
+                    any(LayoutResultCallback.class), any(Bundle.class));
+        }
+        if (writeAnswer != null) {
+            doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
+                    any(ParcelFileDescriptor.class), any(CancellationSignal.class),
+                    any(WriteResultCallback.class));
+        }
+        if (finishAnswer != null) {
+            doAnswer(finishAnswer).when(adapter).onFinish();
+        }
+        return adapter;
+    }
+
+    /**
+     * Create a mock {@link PrintDocumentAdapter} that provides one empty page.
+     *
+     * @return The mock adapter
+     */
+    public @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) {
+        final PrintAttributes[] printAttributes = new PrintAttributes[1];
+
+        return createMockPrintDocumentAdapter(
+                invocation -> {
+                    PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0];
+                    printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
+                    LayoutResultCallback callback =
+                            (LayoutResultCallback) invocation
+                                    .getArguments()[3];
+
+                    callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+                            .setPageCount(numPages).build(),
+                            !oldAttributes.equals(printAttributes[0]));
+
+                    oldAttributes = printAttributes[0];
+
+                    onLayoutCalled();
+                    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, pages[0].getStart(), pages[0].getEnd());
+                    fd.close();
+                    callback.onWriteFinished(pages);
+
+                    onWriteCalled();
+                    return null;
+                }, invocation -> {
+                    onFinishCalled();
+                    return null;
+                });
+    }
+
+    @SuppressWarnings("unchecked")
+    protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
+            Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
+            Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
+            Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
+            Answer<Void> onDestroy) {
+        PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
+
+        doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
+        when(callbacks.getSession()).thenCallRealMethod();
+
+        if (onStartPrinterDiscovery != null) {
+            doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
+                    any(List.class));
+        }
+        if (onStopPrinterDiscovery != null) {
+            doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
+        }
+        if (onValidatePrinters != null) {
+            doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
+                    any(List.class));
+        }
+        if (onStartPrinterStateTracking != null) {
+            doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
+                    any(PrinterId.class));
+        }
+        if (onRequestCustomPrinterIcon != null) {
+            doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
+                    any(PrinterId.class), any(CancellationSignal.class),
+                    any(CustomPrinterIconCallback.class));
+        }
+        if (onStopPrinterStateTracking != null) {
+            doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
+                    any(PrinterId.class));
+        }
+        if (onDestroy != null) {
+            doAnswer(onDestroy).when(callbacks).onDestroy();
+        }
+
+        return callbacks;
+    }
+
+    protected PrintServiceCallbacks createMockPrintServiceCallbacks(
+            Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
+            Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
+        final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
+
+        doCallRealMethod().when(service).setService(any(StubbablePrintService.class));
+        when(service.getService()).thenCallRealMethod();
+
+        if (onCreatePrinterDiscoverySessionCallbacks != null) {
+            doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
+                    .onCreatePrinterDiscoverySessionCallbacks();
+        }
+        if (onPrintJobQueued != null) {
+            doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
+        }
+        if (onRequestCancelPrintJob != null) {
+            doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
+                    any(PrintJob.class));
+        }
+
+        return service;
+    }
+
+    protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
+            int fromIndex, int toIndex) throws IOException {
+        PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
+        final int pageCount = toIndex - fromIndex + 1;
+        for (int i = 0; i < pageCount; i++) {
+            PdfDocument.Page page = document.startPage(i);
+            document.finishPage(page);
+        }
+        FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
+        document.writeTo(fos);
+        fos.flush();
+        document.close();
+    }
+
+    protected void selectPages(String pages, int totalPages) throws Exception {
+        UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.printspooler:id/range_options_spinner"));
+        pagesSpinner.click();
+
+        UiObject rangeOption = getUiDevice().findObject(new UiSelector().textContains(
+                getPrintSpoolerStringOneParam("template_page_range", totalPages)));
+        rangeOption.click();
+
+        UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.printspooler:id/page_range_edittext"));
+        pagesEditText.setText(pages);
+
+        // Hide the keyboard.
+        getUiDevice().pressBack();
+    }
+
+    private static final class CallCounter {
+        private final Object mLock = new Object();
+
+        private int mCallCount;
+
+        public void call() {
+            synchronized (mLock) {
+                mCallCount++;
+                mLock.notifyAll();
+            }
+        }
+
+        int getCallCount() {
+            synchronized (mLock) {
+                return mCallCount;
+            }
+        }
+
+        public void reset() {
+            synchronized (mLock) {
+                mCallCount = 0;
+            }
+        }
+
+        public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
+            synchronized (mLock) {
+                final long startTimeMillis = SystemClock.uptimeMillis();
+                while (mCallCount < count) {
+                    try {
+                        final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                        final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+                        if (remainingTimeMillis <= 0) {
+                            throw new TimeoutException();
+                        }
+                        mLock.wait(timeoutMillis);
+                    } catch (InterruptedException ie) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Make {@code printerName} the default printer by adding it to the history of printers by
+     * printing once.
+     *
+     * @param adapter The {@link PrintDocumentAdapter} used
+     * @throws Exception If the printer could not be made default
+     */
+    public void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
+            throws Exception {
+        // Perform a full print operation on the printer
+        Log.d(LOG_TAG, "print");
+        print(adapter);
+        Log.d(LOG_TAG, "waitForWriteAdapterCallback");
+        waitForWriteAdapterCallback(1);
+        Log.d(LOG_TAG, "selectPrinter");
+        selectPrinter(printerName);
+        Log.d(LOG_TAG, "clickPrintButton");
+        clickPrintButton();
+        Log.d(LOG_TAG, "answerPrintServicesWarning");
+        answerPrintServicesWarning(true);
+        Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+        // Switch to new activity, which should now use the default printer
+        Log.d(LOG_TAG, "getActivity().finish()");
+        getActivity().finish();
+
+        createActivity();
+    }
+
+    /**
+     * Get a string array from the print spooler's resources.
+     *
+     * @param resourceName The name of the array resource
+     * @return The localized string array
+     *
+     * @throws Exception If anything is unexpected
+     */
+    protected String[] getPrintSpoolerStringArray(String resourceName) throws Exception {
+        PackageManager pm = getActivity().getPackageManager();
+        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+        int id = printSpoolerRes.getIdentifier(resourceName, "array", PRINTSPOOLER_PACKAGE);
+        return printSpoolerRes.getStringArray(id);
+    }
+
+    /**
+     * Get a string from the print spooler's resources.
+     *
+     * @param resourceName The name of the string resource
+     * @return The localized string
+     *
+     * @throws Exception If anything is unexpected
+     */
+    private String getPrintSpoolerString(String resourceName) throws Exception {
+        PackageManager pm = getActivity().getPackageManager();
+        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+        int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
+        return printSpoolerRes.getString(id);
+    }
+
+    /**
+     * Get a string with one parameter from the print spooler's resources.
+     *
+     * @param resourceName The name of the string resource
+     * @return The localized string
+     *
+     * @throws Exception If anything is unexpected
+     */
+    protected String getPrintSpoolerStringOneParam(String resourceName, Object p)
+            throws Exception {
+        PackageManager pm = getActivity().getPackageManager();
+        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+        int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
+        return printSpoolerRes.getString(id, p);
+    }
+
+    /**
+     * Get the default media size for the current locale.
+     *
+     * @return The default media size for the current locale
+     *
+     * @throws Exception If anything is unexpected
+     */
+    protected PrintAttributes.MediaSize getDefaultMediaSize() throws Exception {
+        PackageManager pm = getActivity().getPackageManager();
+        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+        int defaultMediaSizeResId = printSpoolerRes.getIdentifier("mediasize_default", "string",
+                PRINTSPOOLER_PACKAGE);
+        String defaultMediaSizeName = printSpoolerRes.getString(defaultMediaSizeResId);
+
+        switch (defaultMediaSizeName) {
+            case "NA_LETTER":
+                return PrintAttributes.MediaSize.NA_LETTER;
+            case "JIS_B5":
+                return PrintAttributes.MediaSize.JIS_B5;
+            case "ISO_A4":
+                return PrintAttributes.MediaSize.ISO_A4;
+            default:
+                throw new Exception("Unknown default media size " + defaultMediaSizeName);
+        }
+    }
+
+    /**
+     * Annotation used to signal that a test does not need an activity.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    protected @interface NoActivity { }
+
+    /**
+     * Rule that handles the {@link NoActivity} annotation.
+     */
+    private static class ShouldStartActivity implements TestRule {
+        boolean mNoActivity;
+
+        @Override
+        public Statement apply(Statement base, org.junit.runner.Description description) {
+            for (Annotation annotation : description.getAnnotations()) {
+                if (annotation instanceof NoActivity) {
+                    mNoActivity = true;
+                    break;
+                }
+            }
+
+            return base;
+        }
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java
new file mode 100644
index 0000000..cfb644f
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.print.test;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+public class PrintDocumentActivity extends Activity {
+    private static final String LOG_TAG = "PrintDocumentActivity";
+    int mTestId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mTestId = getIntent().getIntExtra(BasePrintTest.TEST_ID, -1);
+        Log.d(LOG_TAG, "onCreate() " + this + " for " + mTestId);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+        KeyguardManager km = getSystemService(KeyguardManager.class);
+        if (km != null) {
+            km.requestDismissKeyguard(this, null);
+        }
+
+        BasePrintTest.onActivityCreateCalled(mTestId, this);
+
+        if (savedInstanceState != null) {
+            Log.d(LOG_TAG,
+                    "We cannot deal with lifecycle. Hence finishing " + this + " for " + mTestId);
+            finish();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(LOG_TAG, "onDestroy() " + this);
+        BasePrintTest.onActivityDestroyCalled(mTestId);
+        super.onDestroy();
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
new file mode 100644
index 0000000..dbc6644
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
@@ -0,0 +1,148 @@
+/*
+ * 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.print.test;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.print.PrintJob;
+import android.print.PrintManager;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+/**
+ * Utilities for print tests
+ */
+public class Utils {
+    private static final String LOG_TAG = "Utils";
+
+    /**
+     * A {@link Runnable} that can throw an {@link Throwable}.
+     */
+    public interface Invokable {
+        void run() throws Throwable;
+    }
+
+    /**
+     * Run a {@link Invokable} and expect and {@link Throwable} of a certain type.
+     *
+     * @param r             The {@link Invokable} to run
+     * @param expectedClass The expected {@link Throwable} type
+     */
+    public static void assertException(@NonNull Invokable r,
+            @NonNull Class<? extends Throwable> expectedClass) throws Throwable {
+        try {
+            r.run();
+        } catch (Throwable e) {
+            if (e.getClass().isAssignableFrom(expectedClass)) {
+                return;
+            } else {
+                Log.e(LOG_TAG, "Expected: " + expectedClass.getName() + ", got: "
+                        + e.getClass().getName());
+                throw e;
+            }
+        }
+
+        throw new AssertionError("No throwable thrown");
+    }
+
+    /**
+     * Run a {@link Invokable} on the main thread and forward the {@link Throwable} if one was
+     * thrown.
+     *
+     * @param r The {@link Invokable} to run
+     *
+     * @throws Throwable If the {@link Runnable} caused an issue
+     */
+    public static void runOnMainThread(@NonNull final Invokable r) throws Throwable {
+        final Object synchronizer = new Object();
+        final Throwable[] thrown = new Throwable[1];
+
+        synchronized (synchronizer) {
+            (new Handler(Looper.getMainLooper())).post(() -> {
+                synchronized (synchronizer) {
+                    try {
+                        r.run();
+                    } catch (Throwable t) {
+                        thrown[0] = t;
+                    }
+
+                    synchronizer.notify();
+                }
+            });
+
+            synchronizer.wait();
+        }
+
+        if (thrown[0] != null) {
+            throw thrown[0];
+        }
+    }
+
+    /**
+     * 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 {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                r.run();
+                break;
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < BasePrintTest.OPERATION_TIMEOUT_MILLIS) {
+                    Log.e(LOG_TAG, "Ignoring exception", e);
+
+                    try {
+                        Thread.sleep(500);
+                    } catch (InterruptedException e1) {
+                        Log.e(LOG_TAG, "Interrupted", e);
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    /**
+     * @param name Name of print job
+     *
+     * @return The print job for the name
+     *
+     * @throws Exception If print job could not be found
+     */
+    public static @NonNull PrintJob getPrintJob(@NonNull PrintManager pm, @NonNull String name)
+            throws Exception {
+        for (android.print.PrintJob job : pm.getPrintJobs()) {
+            if (job.getInfo().getLabel().equals(name)) {
+                return job;
+            }
+        }
+
+        throw new Exception("Print job " + name + " not found in " + pm.getPrintJobs());
+    }
+
+    /**
+     * @return The print manager
+     */
+    public static @NonNull PrintManager getPrintManager(@NonNull Context context) {
+        return (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java
new file mode 100644
index 0000000..5e01d7c
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+public class AddPrintersActivity extends Activity {
+    private static final ArrayList<Runnable> sObservers = new ArrayList<>();
+
+    public static void addObserver(@NonNull Runnable observer) {
+        synchronized (sObservers) {
+            sObservers.add(observer);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        synchronized (sObservers) {
+            for (Runnable sObserver : sObservers) {
+                sObserver.run();
+            }
+        }
+
+        finish();
+    }
+
+    public static void clearObservers() {
+        synchronized (sObservers) {
+            sObservers.clear();
+        }
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java
new file mode 100644
index 0000000..94993c7
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.print.PrintJobInfo;
+import android.print.PrinterInfo;
+import android.printservice.PrintService;
+
+/**
+ * Custom print options activity for both print services
+ */
+public class CustomPrintOptionsActivity extends Activity {
+    /** Lock for {@link #sCallback} */
+    private static final Object sLock = new Object();
+
+    /** Currently registered callback for _both_ first and second print service. */
+    private static CustomPrintOptionsCallback sCallback = null;
+
+    /**
+     * Set a new callback called when the custom options activity is launched.
+     *
+     * @param callback The new callback or null, if the callback should be unregistered.
+     */
+    public static void setCallBack(CustomPrintOptionsCallback callback) {
+        synchronized (sLock) {
+            sCallback = callback;
+        }
+    }
+
+    /**
+     * Callback executed for this activity. Set via {@link #setCallBack}.
+     */
+    public interface CustomPrintOptionsCallback {
+        PrintJobInfo executeCustomPrintOptionsActivity(PrintJobInfo printJob,
+                PrinterInfo printer);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent result = new Intent();
+
+        synchronized (sLock) {
+            if (sCallback != null) {
+                PrintJobInfo printJobInfo = getIntent().getParcelableExtra(
+                        PrintService.EXTRA_PRINT_JOB_INFO);
+                PrinterInfo printerInfo = getIntent().getParcelableExtra(
+                        "android.intent.extra.print.EXTRA_PRINTER_INFO");
+
+                result.putExtra(PrintService.EXTRA_PRINT_JOB_INFO,
+                        sCallback.executeCustomPrintOptionsActivity(printJobInfo, printerInfo));
+            }
+        }
+
+        setResult(Activity.RESULT_OK, result);
+        finish();
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java
new file mode 100644
index 0000000..2c3a4ee
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.print.test.services;
+
+public class FirstPrintService extends StubbablePrintService {
+
+    private static final Object sLock = new Object();
+
+    private static PrintServiceCallbacks sCallbacks;
+
+    public static void setCallbacks(PrintServiceCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    @Override
+    protected PrintServiceCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setService(this);
+            }
+            return sCallbacks;
+        }
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java
new file mode 100644
index 0000000..627fec9
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+public class InfoActivity extends Activity {
+    public interface Observer {
+        void onCreate(Activity createdActivity);
+    }
+
+    private static final ArrayList<Observer> sObservers = new ArrayList<>();
+
+    public static void addObserver(Observer observer) {
+        synchronized (sObservers) {
+            sObservers.add(observer);
+        }
+    }
+
+    public static void clearObservers() {
+        synchronized (sObservers) {
+            sObservers.clear();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        synchronized (sObservers) {
+            ArrayList<Observer> observers = (ArrayList<Observer>) sObservers.clone();
+
+            for (Observer observer : observers) {
+                observer.onCreate(this);
+            }
+        }
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java
new file mode 100644
index 0000000..d2d42f2
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java
@@ -0,0 +1,38 @@
+/*
+ * 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.print.test.services;
+
+import android.printservice.PrintJob;
+
+public abstract class PrintServiceCallbacks {
+
+    private StubbablePrintService mService;
+
+    public StubbablePrintService getService() {
+        return mService;
+    }
+
+    public void setService(StubbablePrintService service) {
+        mService = service;
+    }
+
+    public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
+
+    public abstract void onRequestCancelPrintJob(PrintJob printJob);
+
+    public abstract void onPrintJobQueued(PrintJob printJob);
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java
new file mode 100644
index 0000000..7b7a1b9
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java
@@ -0,0 +1,51 @@
+/*
+ * 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.print.test.services;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+
+import java.util.List;
+
+public abstract class PrinterDiscoverySessionCallbacks {
+
+    private StubbablePrinterDiscoverySession mSession;
+
+    public void setSession(StubbablePrinterDiscoverySession session) {
+        mSession = session;
+    }
+
+    public StubbablePrinterDiscoverySession getSession() {
+        return mSession;
+    }
+
+    public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
+
+    public abstract void onStopPrinterDiscovery();
+
+    public abstract void onValidatePrinters(List<PrinterId> printerIds);
+
+    public abstract void onStartPrinterStateTracking(PrinterId printerId);
+
+    public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
+            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
+
+    public abstract void onStopPrinterStateTracking(PrinterId printerId);
+
+    public abstract void onDestroy();
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java
new file mode 100644
index 0000000..3e8a6a7
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.print.test.services;
+
+public class SecondPrintService extends StubbablePrintService {
+
+    private static final Object sLock = new Object();
+
+    private static PrintServiceCallbacks sCallbacks;
+
+    public static void setCallbacks(PrintServiceCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    @Override
+    protected PrintServiceCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setService(this);
+            }
+            return sCallbacks;
+        }
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java
new file mode 100644
index 0000000..56c63fa
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SettingsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}
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
new file mode 100644
index 0000000..09d1f78
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.print.test.services;
+
+import android.content.Context;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+import java.util.List;
+
+public abstract class StubbablePrintService extends PrintService {
+
+    @Override
+    public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+        PrintServiceCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            return new StubbablePrinterDiscoverySession(this,
+                    getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
+        }
+        return null;
+    }
+
+    @Override
+    public void onRequestCancelPrintJob(PrintJob printJob) {
+        PrintServiceCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onRequestCancelPrintJob(printJob);
+        }
+    }
+
+    @Override
+    public void onPrintJobQueued(PrintJob printJob) {
+        PrintServiceCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onPrintJobQueued(printJob);
+        }
+    }
+
+    protected abstract PrintServiceCallbacks getCallbacks();
+
+    public void callAttachBaseContext(Context base) {
+        attachBaseContext(base);
+    }
+
+    public List<PrintJob> callGetActivePrintJobs() {
+        return getActivePrintJobs();
+    }
+
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java
new file mode 100644
index 0000000..c9fb015
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java
@@ -0,0 +1,95 @@
+/*
+ * 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.print.test.services;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
+    private final PrintService mService;
+    private final PrinterDiscoverySessionCallbacks mCallbacks;
+
+    public StubbablePrinterDiscoverySession(PrintService service,
+            PrinterDiscoverySessionCallbacks callbacks) {
+        mService = service;
+        mCallbacks = callbacks;
+        if (mCallbacks != null) {
+            mCallbacks.setSession(this);
+        }
+    }
+
+    public PrintService getService() {
+        return mService;
+    }
+
+    @Override
+    public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
+        if (mCallbacks != null) {
+            mCallbacks.onStartPrinterDiscovery(priorityList);
+        }
+    }
+
+    @Override
+    public void onStopPrinterDiscovery() {
+        if (mCallbacks != null) {
+            mCallbacks.onStopPrinterDiscovery();
+        }
+    }
+
+    @Override
+    public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
+        if (mCallbacks != null) {
+            mCallbacks.onValidatePrinters(printerIds);
+        }
+    }
+
+    @Override
+    public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
+        if (mCallbacks != null) {
+            mCallbacks.onStartPrinterStateTracking(printerId);
+        }
+    }
+
+    @Override
+    public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+            @NonNull CancellationSignal cancellationSignal,
+            @NonNull CustomPrinterIconCallback callback) {
+        if (mCallbacks != null) {
+            mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
+        }
+    }
+
+    @Override
+    public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
+        if (mCallbacks != null) {
+            mCallbacks.onStopPrinterStateTracking(printerId);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mCallbacks != null) {
+            mCallbacks.onDestroy();
+        }
+    }
+}
diff --git a/tests/tests/print/res/values/themes.xml b/tests/tests/print/res/values/themes.xml
index f5c9b977..07e228c 100644
--- a/tests/tests/print/res/values/themes.xml
+++ b/tests/tests/print/res/values/themes.xml
@@ -17,7 +17,12 @@
 -->
 
 <resources>
-    <style name="Theme.Translucent" parent="@android:style/Theme.DeviceDefault">
+    <style name="NoAnimationTranslucent" parent="@android:style/Theme.DeviceDefault">
         <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
+
+    <style name="NoAnimation" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowAnimationStyle">@null</item>
     </style>
 </resources>
diff --git a/tests/tests/print/res/xml/printservice.xml b/tests/tests/print/res/xml/printservice.xml
index 78c7504..07f7d6c 100644
--- a/tests/tests/print/res/xml/printservice.xml
+++ b/tests/tests/print/res/xml/printservice.xml
@@ -17,6 +17,6 @@
 -->
 
 <print-service xmlns:android="http://schemas.android.com/apk/res/android"
-     android:settingsActivity="android.print.cts.services.SettingsActivity"
-     android:addPrintersActivity="android.print.cts.services.AddPrintersActivity"
-     android:advancedPrintOptionsActivity="android.print.cts.services.CustomPrintOptionsActivity"/>
+     android:settingsActivity="android.print.test.services.SettingsActivity"
+     android:addPrintersActivity="android.print.test.services.AddPrintersActivity"
+     android:advancedPrintOptionsActivity="android.print.test.services.CustomPrintOptionsActivity"/>
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
deleted file mode 100644
index db1efd7..0000000
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ /dev/null
@@ -1,1105 +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.print.cts;
-
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.GET_SERVICES;
-import static android.print.cts.Utils.getPrintManager;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.pdf.PdfDocument;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
-import android.print.PrinterId;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.StubbablePrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
-import android.print.pdf.PrintedPdfDocument;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintJob;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-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.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
-import org.mockito.InOrder;
-import org.mockito.stubbing.Answer;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This is the base class for print tests.
- */
-public abstract class BasePrintTest {
-    private final static String LOG_TAG = "BasePrintTest";
-
-    static final long OPERATION_TIMEOUT_MILLIS = 60000;
-    static final String PRINT_JOB_NAME = "Test";
-    static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
-
-    private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
-    private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
-    private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
-    private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
-    private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
-    private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
-    private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler";
-
-    private static final AtomicInteger sLastTestID = new AtomicInteger();
-    private int mTestId;
-    private PrintDocumentActivity mActivity;
-
-    private static String sDisabledPrintServicesBefore;
-
-    private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>();
-
-    public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity();
-
-    /**
-     * Return the UI device
-     *
-     * @return the UI device
-     */
-    public UiDevice getUiDevice() {
-        return UiDevice.getInstance(getInstrumentation());
-    }
-
-    private CallCounter mCancelOperationCounter;
-    private CallCounter mLayoutCallCounter;
-    private CallCounter mWriteCallCounter;
-    private CallCounter mWriteCancelCallCounter;
-    private CallCounter mFinishCallCounter;
-    private CallCounter mPrintJobQueuedCallCounter;
-    private CallCounter mCreateSessionCallCounter;
-    private CallCounter mDestroySessionCallCounter;
-    private CallCounter mDestroyActivityCallCounter = new CallCounter();
-    private CallCounter mCreateActivityCallCounter = new CallCounter();
-
-    private static String[] sEnabledImes;
-
-    private static String[] getEnabledImes() throws IOException {
-        List<String> imeList = new ArrayList<>();
-
-        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
-                .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
-        try (BufferedReader reader = new BufferedReader(
-                new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
-
-            String line;
-            while ((line = reader.readLine()) != null) {
-                imeList.add(line);
-            }
-        }
-
-        String[] imeArray = new String[imeList.size()];
-        imeList.toArray(imeArray);
-
-        return imeArray;
-    }
-
-    private static void disableImes() throws Exception {
-        sEnabledImes = getEnabledImes();
-        for (String ime : sEnabledImes) {
-            String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
-            SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
-        }
-    }
-
-    private static void enableImes() throws Exception {
-        for (String ime : sEnabledImes) {
-            String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
-            SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
-        }
-        sEnabledImes = null;
-    }
-
-    protected static Instrumentation getInstrumentation() {
-        return InstrumentationRegistry.getInstrumentation();
-    }
-
-    @BeforeClass
-    public static void setUpClass() throws Exception {
-        Log.d(LOG_TAG, "setUpClass()");
-
-        Instrumentation instrumentation = getInstrumentation();
-
-        // Make sure we start with a clean slate.
-        Log.d(LOG_TAG, "clearPrintSpoolerData()");
-        clearPrintSpoolerData();
-        Log.d(LOG_TAG, "disableImes()");
-        disableImes();
-        Log.d(LOG_TAG, "disablePrintServices()");
-        disablePrintServices(instrumentation.getTargetContext().getPackageName());
-
-        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
-        // Dexmaker is used by mockito.
-        System.setProperty("dexmaker.dexcache", instrumentation
-                .getTargetContext().getCacheDir().getPath());
-
-        Log.d(LOG_TAG, "setUpClass() done");
-    }
-
-    /**
-     * Disable all print services beside the ones we want to leave enabled.
-     *
-     * @param packageToLeaveEnabled The package of the services to leave enabled.
-     */
-    private static void disablePrintServices(@NonNull String packageToLeaveEnabled)
-            throws IOException {
-        Instrumentation instrumentation = getInstrumentation();
-
-        sDisabledPrintServicesBefore = SystemUtil.runShellCommand(instrumentation,
-                "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
-
-        Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
-        List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
-                .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
-
-        StringBuilder builder = new StringBuilder();
-        for (ResolveInfo service : installedServices) {
-            if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
-                continue;
-            }
-            if (builder.length() > 0) {
-                builder.append(":");
-            }
-            builder.append(new ComponentName(service.serviceInfo.packageName,
-                    service.serviceInfo.name).flattenToString());
-        }
-
-        SystemUtil.runShellCommand(instrumentation, "settings put secure "
-                + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
-    }
-
-    /**
-     * Revert {@link #disablePrintServices(String)}
-     */
-    private static  void enablePrintServices() throws IOException {
-        SystemUtil.runShellCommand(getInstrumentation(),
-                "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
-                        + sDisabledPrintServicesBefore);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        Log.d(LOG_TAG, "setUp()");
-
-        assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_PRINTING));
-
-        // Initialize the latches.
-        Log.d(LOG_TAG, "init counters");
-        mCancelOperationCounter = new CallCounter();
-        mLayoutCallCounter = new CallCounter();
-        mFinishCallCounter = new CallCounter();
-        mWriteCallCounter = new CallCounter();
-        mWriteCancelCallCounter = new CallCounter();
-        mFinishCallCounter = new CallCounter();
-        mPrintJobQueuedCallCounter = new CallCounter();
-        mCreateSessionCallCounter = new CallCounter();
-        mDestroySessionCallCounter = new CallCounter();
-
-        mTestId = sLastTestID.incrementAndGet();
-        sIdToTest.put(mTestId, this);
-
-        // Create the activity if needed
-        if (!mShouldStartActivityRule.noActivity) {
-            createActivity();
-        }
-
-        Log.d(LOG_TAG, "setUp() done");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        Log.d(LOG_TAG, "tearDown()");
-
-        finishActivity();
-
-        sIdToTest.remove(mTestId);
-
-        Log.d(LOG_TAG, "tearDown() done");
-    }
-
-    @AfterClass
-    public static void tearDownClass() throws Exception {
-        Log.d(LOG_TAG, "tearDownClass()");
-
-        Instrumentation instrumentation = getInstrumentation();
-
-        Log.d(LOG_TAG, "enablePrintServices()");
-        enablePrintServices();
-
-        Log.d(LOG_TAG, "enableImes()");
-        enableImes();
-
-        // Make sure the spooler is cleaned, this also un-approves all services
-        Log.d(LOG_TAG, "clearPrintSpoolerData()");
-        clearPrintSpoolerData();
-
-        SystemUtil.runShellCommand(instrumentation, "settings put secure "
-                    + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
-
-        Log.d(LOG_TAG, "tearDownClass() done");
-    }
-
-    protected void print(final PrintDocumentAdapter adapter, final PrintAttributes attributes) {
-        print(adapter, "Print job", attributes);
-    }
-
-    protected void print(PrintDocumentAdapter adapter) {
-        print(adapter, (PrintAttributes) null);
-    }
-
-    protected void print(PrintDocumentAdapter adapter, String printJobName) {
-        print(adapter, printJobName, null);
-    }
-
-    /**
-     * Start printing
-     *
-     * @param adapter      Adapter supplying data to print
-     * @param printJobName The name of the print job
-     */
-    protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName,
-            @Nullable PrintAttributes attributes) {
-        // Initiate printing as if coming from the app.
-        getInstrumentation()
-                .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter,
-                        attributes));
-    }
-
-    void onCancelOperationCalled() {
-        mCancelOperationCounter.call();
-    }
-
-    void onLayoutCalled() {
-        mLayoutCallCounter.call();
-    }
-
-    int getWriteCallCount() {
-        return mWriteCallCounter.getCallCount();
-    }
-
-    protected void onWriteCalled() {
-        mWriteCallCounter.call();
-    }
-
-    protected void onWriteCancelCalled() {
-        mWriteCancelCallCounter.call();
-    }
-
-    void onFinishCalled() {
-        mFinishCallCounter.call();
-    }
-
-    void onPrintJobQueuedCalled() {
-        mPrintJobQueuedCallCounter.call();
-    }
-
-    void onPrinterDiscoverySessionCreateCalled() {
-        mCreateSessionCallCounter.call();
-    }
-
-    protected void onPrinterDiscoverySessionDestroyCalled() {
-        mDestroySessionCallCounter.call();
-    }
-
-    void waitForCancelOperationCallbackCalled() {
-        waitForCallbackCallCount(mCancelOperationCounter, 1,
-                "Did not get expected call to onCancel for the current operation.");
-    }
-
-    void waitForPrinterDiscoverySessionCreateCallbackCalled() {
-        waitForCallbackCallCount(mCreateSessionCallCounter, 1,
-                "Did not get expected call to onCreatePrinterDiscoverySession.");
-    }
-
-    void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
-        waitForCallbackCallCount(mDestroySessionCallCounter, count,
-                "Did not get expected call to onDestroyPrinterDiscoverySession.");
-    }
-
-    void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
-        waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
-                "Did not get expected call to onPrintJobQueued.");
-    }
-
-    void waitForAdapterFinishCallbackCalled() {
-        waitForCallbackCallCount(mFinishCallCounter, 1,
-                "Did not get expected call to finish.");
-    }
-
-    void waitForLayoutAdapterCallbackCount(int count) {
-        waitForCallbackCallCount(mLayoutCallCounter, count,
-                "Did not get expected call to layout.");
-    }
-
-    void waitForWriteAdapterCallback(int count) {
-        waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
-    }
-
-    void waitForWriteCancelCallback(int count) {
-        waitForCallbackCallCount(mWriteCancelCallCounter, count,
-                "Did not get expected cancel of write.");
-    }
-
-    private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
-        try {
-            counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
-        } catch (TimeoutException te) {
-            fail(message);
-        }
-    }
-
-    /**
-     * Indicate the print activity was created.
-     */
-    static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) {
-        synchronized (sIdToTest) {
-            BasePrintTest test = sIdToTest.get(testId);
-            if (test != null) {
-                test.mActivity = activity;
-                test.mCreateActivityCallCounter.call();
-            }
-        }
-    }
-
-    /**
-     * Indicate the print activity was destroyed.
-     */
-    static void onActivityDestroyCalled(int testId) {
-        synchronized (sIdToTest) {
-            BasePrintTest test = sIdToTest.get(testId);
-            if (test != null) {
-                test.mDestroyActivityCallCounter.call();
-            }
-        }
-    }
-
-    private void finishActivity() {
-        Activity activity = mActivity;
-
-        if (activity != null) {
-            if (!activity.isFinishing()) {
-                activity.finish();
-            }
-
-            while (!activity.isDestroyed()) {
-                int creates = mCreateActivityCallCounter.getCallCount();
-                waitForCallbackCallCount(mDestroyActivityCallCounter, creates,
-                        "Activity was not destroyed");
-            }
-        }
-    }
-
-    /**
-     * Get the number of ties the print activity was destroyed.
-     *
-     * @return The number of onDestroy calls on the print activity.
-     */
-    int getActivityDestroyCallbackCallCount() {
-        return mDestroyActivityCallCounter.getCallCount();
-    }
-
-    /**
-     * Get the number of ties the print activity was created.
-     *
-     * @return The number of onCreate calls on the print activity.
-     */
-    private int getActivityCreateCallbackCallCount() {
-        return mCreateActivityCallCounter.getCallCount();
-    }
-
-    /**
-     * Wait until create was called {@code count} times.
-     *
-     * @param count The number of create calls to expect.
-     */
-    private void waitForActivityCreateCallbackCalled(int count) {
-        waitForCallbackCallCount(mCreateActivityCallCounter, count,
-                "Did not get expected call to create.");
-    }
-
-    /**
-     * Reset all counters.
-     */
-    void resetCounters() {
-        mCancelOperationCounter.reset();
-        mLayoutCallCounter.reset();
-        mWriteCallCounter.reset();
-        mWriteCancelCallCounter.reset();
-        mFinishCallCounter.reset();
-        mPrintJobQueuedCallCounter.reset();
-        mCreateSessionCallCounter.reset();
-        mDestroySessionCallCounter.reset();
-        mDestroyActivityCallCounter.reset();
-        mCreateActivityCallCounter.reset();
-    }
-
-    void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
-        try {
-            long delay = 1;
-            while (true) {
-                try {
-                    UiDevice uiDevice = getUiDevice();
-                    UiObject destinationSpinner = uiDevice.findObject(new UiSelector()
-                            .resourceId("com.android.printspooler:id/destination_spinner"));
-
-                    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);
-                }
-
-                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) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
-        UiObject button;
-        if (confirm) {
-            button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button1"));
-        } else {
-            button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button2"));
-        }
-        button.click();
-    }
-
-    void changeOrientation(String orientation) throws UiObjectNotFoundException, IOException {
-        try {
-            UiDevice uiDevice = getUiDevice();
-            UiObject orientationSpinner = uiDevice.findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/orientation_spinner"));
-            orientationSpinner.click();
-            UiObject orientationOption = uiDevice.findObject(new UiSelector().text(orientation));
-            orientationOption.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    protected String getOrientation() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject orientationSpinner = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/orientation_spinner"));
-            return orientationSpinner.getText();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void changeMediaSize(String mediaSize) throws UiObjectNotFoundException, IOException {
-        try {
-            UiDevice uiDevice = getUiDevice();
-            UiObject mediaSizeSpinner = uiDevice.findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/paper_size_spinner"));
-            mediaSizeSpinner.click();
-            UiObject mediaSizeOption = uiDevice.findObject(new UiSelector().text(mediaSize));
-            mediaSizeOption.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void changeColor(String color) throws UiObjectNotFoundException, IOException {
-        try {
-            UiDevice uiDevice = getUiDevice();
-            UiObject colorSpinner = uiDevice.findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/color_spinner"));
-            colorSpinner.click();
-            UiObject colorOption = uiDevice.findObject(new UiSelector().text(color));
-            colorOption.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    protected String getColor() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject colorSpinner = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/color_spinner"));
-            return colorSpinner.getText();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void changeDuplex(String duplex) throws UiObjectNotFoundException, IOException {
-        try {
-            UiDevice uiDevice = getUiDevice();
-            UiObject duplexSpinner = uiDevice.findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/duplex_spinner"));
-            duplexSpinner.click();
-            UiObject duplexOption = uiDevice.findObject(new UiSelector().text(duplex));
-            duplexOption.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void changeCopies(int newCopies) throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/copies_edittext"));
-            copies.setText(Integer.valueOf(newCopies).toString());
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    protected String getCopies() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/copies_edittext"));
-            return copies.getText();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void assertNoPrintButton() throws UiObjectNotFoundException, IOException {
-        assertFalse(getUiDevice().hasObject(By.res("com.android.printspooler:id/print_button")));
-    }
-
-    void clickPrintButton() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject printButton = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/print_button"));
-            printButton.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void clickRetryButton() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject retryButton = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/action_button"));
-            retryButton.click();
-        } catch (UiObjectNotFoundException e) {
-            dumpWindowHierarchy();
-            throw e;
-        }
-    }
-
-    void dumpWindowHierarchy() throws IOException {
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        getUiDevice().dumpWindowHierarchy(os);
-
-        Log.w(LOG_TAG, "Window hierarchy:");
-        for (String line : os.toString("UTF-8").split("\n")) {
-            Log.w(LOG_TAG, line);
-        }
-    }
-
-    protected PrintDocumentActivity getActivity() {
-        return mActivity;
-    }
-
-    protected void createActivity() {
-        Log.d(LOG_TAG, "createActivity()");
-
-        int createBefore = getActivityCreateCallbackCallCount();
-
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.putExtra(TEST_ID, mTestId);
-
-        Instrumentation instrumentation = getInstrumentation();
-        intent.setClassName(instrumentation.getTargetContext().getPackageName(),
-                PrintDocumentActivity.class.getName());
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        instrumentation.startActivitySync(intent);
-
-        waitForActivityCreateCallbackCalled(createBefore + 1);
-    }
-
-    void openPrintOptions() throws UiObjectNotFoundException {
-        UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/expand_collapse_handle"));
-        expandHandle.click();
-    }
-
-    void openCustomPrintOptions() throws UiObjectNotFoundException {
-        UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/more_options_button"));
-        expandHandle.click();
-    }
-
-    static void clearPrintSpoolerData() throws Exception {
-        if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_PRINTING)) {
-            assertTrue("failed to clear print spooler data",
-                    SystemUtil.runShellCommand(getInstrumentation(), String.format(
-                            "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
-                            .contains(PM_CLEAR_SUCCESS_OUTPUT));
-        }
-    }
-
-    void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
-            PrintAttributes oldAttributes, PrintAttributes newAttributes,
-            final boolean forPreview) {
-        inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
-                any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
-                        new BaseMatcher<Bundle>() {
-                            @Override
-                            public boolean matches(Object item) {
-                                Bundle bundle = (Bundle) item;
-                                return forPreview == bundle.getBoolean(
-                                        PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
-                            }
-
-                            @Override
-                            public void describeTo(Description description) {
-                                /* do nothing */
-                            }
-                        }));
-    }
-
-    PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
-            Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
-        // Create a mock print adapter.
-        PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
-        if (layoutAnswer != null) {
-            doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
-                    any(PrintAttributes.class), any(CancellationSignal.class),
-                    any(LayoutResultCallback.class), any(Bundle.class));
-        }
-        if (writeAnswer != null) {
-            doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
-                    any(ParcelFileDescriptor.class), any(CancellationSignal.class),
-                    any(WriteResultCallback.class));
-        }
-        if (finishAnswer != null) {
-            doAnswer(finishAnswer).when(adapter).onFinish();
-        }
-        return adapter;
-    }
-
-    /**
-     * Create a mock {@link PrintDocumentAdapter} that provides one empty page.
-     *
-     * @return The mock adapter
-     */
-    @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) {
-        final PrintAttributes[] printAttributes = new PrintAttributes[1];
-
-        return createMockPrintDocumentAdapter(
-                invocation -> {
-                    PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0];
-                    printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
-                    PrintDocumentAdapter.LayoutResultCallback callback =
-                            (PrintDocumentAdapter.LayoutResultCallback) invocation
-                                    .getArguments()[3];
-
-                    callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
-                            .setPageCount(numPages).build(),
-                            !oldAttributes.equals(printAttributes[0]));
-
-                    oldAttributes = printAttributes[0];
-
-                    onLayoutCalled();
-                    return null;
-                }, invocation -> {
-                    Object[] args = invocation.getArguments();
-                    PageRange[] pages = (PageRange[]) args[0];
-                    ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
-                    PrintDocumentAdapter.WriteResultCallback callback =
-                            (PrintDocumentAdapter.WriteResultCallback) args[3];
-
-                    writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
-                    fd.close();
-                    callback.onWriteFinished(pages);
-
-                    onWriteCalled();
-                    return null;
-                }, invocation -> {
-                    onFinishCalled();
-                    return null;
-                });
-    }
-
-    @SuppressWarnings("unchecked")
-    protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
-            Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
-            Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
-            Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
-            Answer<Void> onDestroy) {
-        PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
-
-        doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
-        when(callbacks.getSession()).thenCallRealMethod();
-
-        if (onStartPrinterDiscovery != null) {
-            doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
-                    any(List.class));
-        }
-        if (onStopPrinterDiscovery != null) {
-            doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
-        }
-        if (onValidatePrinters != null) {
-            doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
-                    any(List.class));
-        }
-        if (onStartPrinterStateTracking != null) {
-            doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onRequestCustomPrinterIcon != null) {
-            doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
-                    any(PrinterId.class), any(CancellationSignal.class),
-                    any(CustomPrinterIconCallback.class));
-        }
-        if (onStopPrinterStateTracking != null) {
-            doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onDestroy != null) {
-            doAnswer(onDestroy).when(callbacks).onDestroy();
-        }
-
-        return callbacks;
-    }
-
-    protected PrintServiceCallbacks createMockPrintServiceCallbacks(
-            Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
-            Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
-        final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
-
-        doCallRealMethod().when(service).setService(any(StubbablePrintService.class));
-        when(service.getService()).thenCallRealMethod();
-
-        if (onCreatePrinterDiscoverySessionCallbacks != null) {
-            doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
-                    .onCreatePrinterDiscoverySessionCallbacks();
-        }
-        if (onPrintJobQueued != null) {
-            doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
-        }
-        if (onRequestCancelPrintJob != null) {
-            doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
-                    any(PrintJob.class));
-        }
-
-        return service;
-    }
-
-    protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
-            int fromIndex, int toIndex) throws IOException {
-        PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
-        final int pageCount = toIndex - fromIndex + 1;
-        for (int i = 0; i < pageCount; i++) {
-            PdfDocument.Page page = document.startPage(i);
-            document.finishPage(page);
-        }
-        FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
-        document.writeTo(fos);
-        fos.flush();
-        document.close();
-    }
-
-    protected void selectPages(String pages, int totalPages) throws Exception {
-        UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/range_options_spinner"));
-        pagesSpinner.click();
-
-        UiObject rangeOption = getUiDevice().findObject(new UiSelector().textContains(
-                getPrintSpoolerStringOneParam("template_page_range", totalPages)));
-        rangeOption.click();
-
-        UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/page_range_edittext"));
-        pagesEditText.setText(pages);
-
-        // Hide the keyboard.
-        getUiDevice().pressBack();
-    }
-
-    private static final class CallCounter {
-        private final Object mLock = new Object();
-
-        private int mCallCount;
-
-        public void call() {
-            synchronized (mLock) {
-                mCallCount++;
-                mLock.notifyAll();
-            }
-        }
-
-        int getCallCount() {
-            synchronized (mLock) {
-                return mCallCount;
-            }
-        }
-
-        public void reset() {
-            synchronized (mLock) {
-                mCallCount = 0;
-            }
-        }
-
-        public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
-            synchronized (mLock) {
-                final long startTimeMillis = SystemClock.uptimeMillis();
-                while (mCallCount < count) {
-                    try {
-                        final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
-                        final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
-                        if (remainingTimeMillis <= 0) {
-                            throw new TimeoutException();
-                        }
-                        mLock.wait(timeoutMillis);
-                    } catch (InterruptedException ie) {
-                        /* ignore */
-                    }
-                }
-            }
-        }
-    }
-
-
-    /**
-     * Make {@code printerName} the default printer by adding it to the history of printers by
-     * printing once.
-     *
-     * @param adapter The {@link PrintDocumentAdapter} used
-     * @throws Exception If the printer could not be made default
-     */
-    void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
-            throws Exception {
-        // Perform a full print operation on the printer
-        Log.d(LOG_TAG, "print");
-        print(adapter);
-        Log.d(LOG_TAG, "waitForWriteAdapterCallback");
-        waitForWriteAdapterCallback(1);
-        Log.d(LOG_TAG, "selectPrinter");
-        selectPrinter(printerName);
-        Log.d(LOG_TAG, "clickPrintButton");
-        clickPrintButton();
-        Log.d(LOG_TAG, "answerPrintServicesWarning");
-        answerPrintServicesWarning(true);
-        Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
-
-        // Switch to new activity, which should now use the default printer
-        Log.d(LOG_TAG, "getActivity().finish()");
-        getActivity().finish();
-
-        createActivity();
-    }
-
-    /**
-     * Get a string array from the print spooler's resources.
-     *
-     * @param resourceName The name of the array resource
-     * @return The localized string array
-     *
-     * @throws Exception If anything is unexpected
-     */
-    String[] getPrintSpoolerStringArray(String resourceName) throws Exception {
-        PackageManager pm = getActivity().getPackageManager();
-        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
-        int id = printSpoolerRes.getIdentifier(resourceName, "array", PRINTSPOOLER_PACKAGE);
-        return printSpoolerRes.getStringArray(id);
-    }
-
-    /**
-     * Get a string from the print spooler's resources.
-     *
-     * @param resourceName The name of the string resource
-     * @return The localized string
-     *
-     * @throws Exception If anything is unexpected
-     */
-    String getPrintSpoolerString(String resourceName) throws Exception {
-        PackageManager pm = getActivity().getPackageManager();
-        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
-        int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
-        return printSpoolerRes.getString(id);
-    }
-
-    /**
-     * Get a string with one parameter from the print spooler's resources.
-     *
-     * @param resourceName The name of the string resource
-     * @return The localized string
-     *
-     * @throws Exception If anything is unexpected
-     */
-    String getPrintSpoolerStringOneParam(String resourceName, Object p)
-            throws Exception {
-        PackageManager pm = getActivity().getPackageManager();
-        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
-        int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
-        return printSpoolerRes.getString(id, p);
-    }
-
-    /**
-     * Get the default media size for the current locale.
-     *
-     * @return The default media size for the current locale
-     *
-     * @throws Exception If anything is unexpected
-     */
-    PrintAttributes.MediaSize getDefaultMediaSize() throws Exception {
-        PackageManager pm = getActivity().getPackageManager();
-        Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
-        int defaultMediaSizeResId = printSpoolerRes.getIdentifier("mediasize_default", "string",
-                PRINTSPOOLER_PACKAGE);
-        String defaultMediaSizeName = printSpoolerRes.getString(defaultMediaSizeResId);
-
-        switch (defaultMediaSizeName) {
-            case "NA_LETTER":
-                return PrintAttributes.MediaSize.NA_LETTER;
-            case "JIS_B5":
-                return PrintAttributes.MediaSize.JIS_B5;
-            case "ISO_A4":
-                return PrintAttributes.MediaSize.ISO_A4;
-            default:
-                throw new Exception("Unknown default media size " + defaultMediaSizeName);
-        }
-    }
-
-    /**
-     * Annotation used to signal that a test does not need an activity.
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target(ElementType.METHOD)
-    @interface NoActivity { }
-
-    /**
-     * Rule that handles the {@link NoActivity} annotation.
-     */
-    private static class ShouldStartActivity implements TestRule {
-        boolean noActivity;
-
-        @Override
-        public Statement apply(Statement base, org.junit.runner.Description description) {
-            for (Annotation annotation : description.getAnnotations()) {
-                if (annotation instanceof NoActivity) {
-                    noActivity = true;
-                    break;
-                }
-            }
-
-            return base;
-        }
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/ClassParametersTest.java b/tests/tests/print/src/android/print/cts/ClassParametersTest.java
index e0c41ea..a5e5d49 100644
--- a/tests/tests/print/src/android/print/cts/ClassParametersTest.java
+++ b/tests/tests/print/src/android/print/cts/ClassParametersTest.java
@@ -16,17 +16,21 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import android.print.PrintAttributes;
 import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Resolution;
 import android.print.PrintDocumentInfo;
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
 /**
  * Test that the print attributes can be constructed correctly. This does not test that the
  * attributes have the desired effect when send to the print framework.
diff --git a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
index eb9806e..3ea14f6 100644
--- a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
+++ b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
@@ -16,6 +16,8 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.eventually;
+
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -30,32 +32,29 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.CustomPrintOptionsActivity;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.CustomPrintOptionsActivity;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+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 android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
 import android.util.Log;
+
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
-import static android.print.cts.Utils.eventually;
-
 /**
  * This test verifies changes to the printer capabilities are applied correctly.
  */
diff --git a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
index 37eab57..da7e82d 100644
--- a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
+++ b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
@@ -16,6 +16,14 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.getPrintJob;
+import static android.print.test.Utils.getPrintManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import android.print.PrintAttributes;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintJob;
@@ -23,23 +31,22 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.support.annotation.NonNull;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
 /**
  * Test interface from the application to the print service.
  */
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
index f128bab..c4fd77e 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
@@ -36,12 +36,13 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
 import android.print.pdf.PrintedPdfDocument;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
 import android.support.annotation.NonNull;
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 0364624..c1abafc 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -16,7 +16,8 @@
 
 package android.print.cts;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
@@ -35,15 +36,16 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
-
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
index e6bc164..7661ff9 100644
--- a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
@@ -16,6 +16,8 @@
 
 package android.print.cts;
 
+import static org.junit.Assert.assertEquals;
+
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -29,15 +31,16 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
-
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,8 +49,6 @@
 import java.util.Arrays;
 import java.util.List;
 
-import static org.junit.Assert.*;
-
 /**
  * Test that the print attributes are correctly propagated through the print framework
  */
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
deleted file mode 100644
index c3e6e96..0000000
--- a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
+++ /dev/null
@@ -1,53 +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.print.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.WindowManager;
-
-public class PrintDocumentActivity extends Activity {
-    private static final String LOG_TAG = "PrintDocumentActivity";
-    int mTestId;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mTestId = getIntent().getIntExtra(BasePrintTest.TEST_ID, -1);
-        Log.d(LOG_TAG, "onCreate() " + this + " for " + mTestId);
-
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-
-        KeyguardManager km = getSystemService(KeyguardManager.class);
-        if (km != null) {
-            km.requestDismissKeyguard(this, null);
-        }
-
-        BasePrintTest.onActivityCreateCalled(mTestId, this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.d(LOG_TAG, "onDestroy() " + this);
-        BasePrintTest.onActivityDestroyCalled(mTestId);
-        super.onDestroy();
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 38ba4dd..4851ba9 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -36,11 +36,12 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
 import android.support.test.runner.AndroidJUnit4;
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
index a9d03c9..63e83e3 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
@@ -16,6 +16,11 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.eventually;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -29,14 +34,16 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,16 +52,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.print.cts.Utils.eventually;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 /**
  * This test verifies that the system respects the {@link PrintDocumentAdapter}
  * contract and invokes all callbacks as expected.
  */
 @RunWith(AndroidJUnit4.class)
-public class PrintDocumentInfoTest extends android.print.cts.BasePrintTest {
+public class PrintDocumentInfoTest extends BasePrintTest {
     private static boolean sIsDefaultPrinterSet;
 
     @Before
diff --git a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
index d5edcb6..75a7847 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
@@ -16,7 +16,7 @@
 
 package android.print.cts;
 
-import static android.print.cts.Utils.eventually;
+import static android.print.test.Utils.eventually;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -30,11 +30,12 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.util.Log;
 
diff --git a/tests/tests/print/src/android/print/cts/PrintJobTest.java b/tests/tests/print/src/android/print/cts/PrintJobTest.java
index f3081fb..1fcfc17 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobTest.java
@@ -16,6 +16,14 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.eventually;
+
+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.print.PrintAttributes;
 import android.print.PrintAttributes.Margins;
 import android.print.PrintAttributes.MediaSize;
@@ -26,23 +34,22 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.CustomPrintOptionsActivity;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.CustomPrintOptionsActivity;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 
-import static android.print.cts.Utils.eventually;
-import static org.junit.Assert.*;
-
 /**
  * Tests all possible states of print jobs.
  */
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 7be5fc1..b5a23e2 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -16,6 +16,16 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.assertException;
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.getPrintJob;
+import static android.print.test.Utils.runOnMainThread;
+
+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.Activity;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -38,13 +48,14 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.InfoActivity;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.InfoActivity;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.CustomPrinterIconCallback;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
@@ -52,15 +63,13 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiSelector;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
 /**
  * Test the interface from a print service to the print manager
  */
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
index 9b277e9..251b371 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
@@ -16,6 +16,8 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.runOnMainThread;
+
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -29,14 +31,14 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.util.Log;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,8 +49,6 @@
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
-import static android.print.cts.Utils.runOnMainThread;
-
 /**
  * This test verifies changes to the printer capabilities are applied correctly.
  */
@@ -154,23 +154,6 @@
                         PrinterInfo.STATUS_UNAVAILABLE)));
     }
 
-    /**
-     * Wait until the message is shown that indicates that a printer is unavilable.
-     *
-     * @throws Exception If anything was unexpected.
-     */
-    private void waitForPrinterUnavailable() throws Exception {
-        final String PRINTER_UNAVAILABLE_MSG =
-                getPrintSpoolerString("print_error_printer_unavailable");
-
-        UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/message"));
-        if (!message.getText().equals(PRINTER_UNAVAILABLE_MSG)) {
-            throw new Exception("Wrong message: " + message.getText() + " instead of " +
-                    PRINTER_UNAVAILABLE_MSG);
-        }
-    }
-
     @Before
     public void setUpPrinting() throws Exception {
         // Create the mSession[0] callbacks that we will be checking.
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
index 3ea03af..3954948 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
@@ -16,6 +16,11 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -29,12 +34,14 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+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.Test;
 import org.junit.runner.RunWith;
 
@@ -42,9 +49,6 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-import static android.print.cts.Utils.assertException;
-import static org.junit.Assert.*;
-
 /**
  * This test verifies changes to the printer capabilities are applied correctly.
  */
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index 499d21e..a21ed18 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -16,8 +16,13 @@
 
 package android.print.cts;
 
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.runOnMainThread;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.inOrder;
 
 import android.print.PrintAttributes;
@@ -28,15 +33,19 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrinterDiscoverySession;
-
+import android.support.annotation.NonNull;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiSelector;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,10 +62,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class PrinterDiscoverySessionLifecycleTest extends BasePrintTest {
-    private static final String FIRST_PRINTER_NAME = "First printer";
-    private static final String SECOND_PRINTER_NAME = "Second printer";
-
-    private static final String FIRST_PRINTER_LOCAL_ID= "first_printer";
+    private static final String FIRST_PRINTER_LOCAL_ID = "first_printer";
     private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
 
     private static StubbablePrinterDiscoverySession sSession;
@@ -66,6 +72,172 @@
         clearPrintSpoolerData();
     }
 
+    /**
+     * Add a printer to {@#sSession}.
+     *
+     * @param localId The id of the printer to add
+     * @param hasCapabilities If the printer has capabilities
+     */
+    private void addPrinter(@NonNull String localId, boolean hasCapabilities) {
+        // Add the first printer.
+        PrinterId firstPrinterId = sSession.getService().generatePrinterId(
+                localId);
+
+        PrinterInfo.Builder printer = new PrinterInfo.Builder(firstPrinterId,
+                localId, PrinterInfo.STATUS_IDLE);
+
+        if (hasCapabilities) {
+            printer.setCapabilities(new PrinterCapabilitiesInfo.Builder(firstPrinterId)
+                    .setMinMargins(new Margins(200, 200, 200, 200))
+                    .addMediaSize(MediaSize.ISO_A0, true)
+                    .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+                    .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+                            PrintAttributes.COLOR_MODE_COLOR)
+                    .build());
+        }
+
+        sSession.addPrinters(Collections.singletonList(printer.build()));
+    }
+
+    /**
+     * Make {@code localPrinterId} the default printer. This requires a full print workflow.
+     *
+     * As a side-effect also approved the print service.
+     *
+     * @param localPrinterId The printer to make default
+     */
+    private void makeDefaultPrinter(String localPrinterId) throws Throwable {
+        PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+
+        print(adapter);
+        waitForWriteAdapterCallback(1);
+
+        runOnMainThread(() -> addPrinter(localPrinterId, true));
+        selectPrinter(localPrinterId);
+        waitForWriteAdapterCallback(2);
+
+        clickPrintButton();
+        answerPrintServicesWarning(true);
+
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        resetCounters();
+    }
+
+    /**
+     * Select a printer in the all printers activity
+     *
+     * @param printerName The name of the printer to select
+     */
+    private void selectInAllPrintersActivity(@NonNull String printerName) throws Exception {
+        while (true) {
+            UiObject printerItem = getUiDevice().findObject(
+                    new UiSelector().text(printerName));
+
+            if (printerItem.isEnabled()) {
+                printerItem.click();
+                break;
+            } else {
+                Thread.sleep(100);
+            }
+        }
+    }
+
+    @Test
+    public void defaultPrinterBecomesAvailableWhileInBackground() throws Throwable {
+        // Create the session callbacks that we will be checking.
+        final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(invocation -> {
+                    sSession =
+                            ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
+
+                    onPrinterDiscoverySessionCreateCalled();
+                    return null;
+                }, null, null, null, null, null, invocation -> {
+                    onPrinterDiscoverySessionDestroyCalled();
+                    return null;
+                });
+
+        // Create the service callbacks for the first print service.
+        PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+                invocation -> firstSessionCallbacks, null, null);
+
+        // Configure the print services.
+        FirstPrintService.setCallbacks(firstServiceCallbacks);
+        SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+        makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
+
+        PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+        print(adapter);
+        waitForPrinterDiscoverySessionCreateCallbackCalled();
+
+        waitForPrinterUnavailable();
+
+        selectPrinter("All printers…");
+        // Let all printers activity start
+        Thread.sleep(500);
+
+        // Add printer
+        runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
+
+        // Select printer once available (this returns to main print activity)
+        selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
+
+        // Wait for preview to load and finish print
+        waitForWriteAdapterCallback(1);
+        clickPrintButton();
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+    }
+
+    @Test
+    public void defaultPrinterBecomesUsableWhileInBackground() throws Throwable {
+        // Create the session callbacks that we will be checking.
+        final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(invocation -> {
+                    sSession =
+                            ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
+
+                    onPrinterDiscoverySessionCreateCalled();
+                    return null;
+                }, null, null, null, null, null, invocation -> {
+                    onPrinterDiscoverySessionDestroyCalled();
+                    return null;
+                });
+
+        // Create the service callbacks for the first print service.
+        PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+                invocation -> firstSessionCallbacks, null, null);
+
+        // Configure the print services.
+        FirstPrintService.setCallbacks(firstServiceCallbacks);
+        SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+        makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
+
+        PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+        print(adapter);
+        waitForPrinterDiscoverySessionCreateCallbackCalled();
+
+        // Add printer but do not enable it (capabilities == null)
+        runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, false));
+        waitForPrinterUnavailable();
+
+        selectPrinter("All printers…");
+        // Let all printers activity start
+        Thread.sleep(500);
+
+        // Enable printer
+        runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
+
+        // Select printer once available (this returns to main print activity)
+        selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
+
+        // Wait for preview to load and finish print
+        waitForWriteAdapterCallback(1);
+        clickPrintButton();
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+    }
+
     @Test
     public void normalLifecycle() throws Throwable {
         // Create the session callbacks that we will be checking.
@@ -99,7 +271,7 @@
         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
 
         // Select the first printer.
-        selectPrinter(FIRST_PRINTER_NAME);
+        selectPrinter(FIRST_PRINTER_LOCAL_ID);
 
         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
                 sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -111,7 +283,7 @@
 
         // Select the second printer (same capabilities as the other
         // one so no layout should happen).
-        selectPrinter(SECOND_PRINTER_NAME);
+        selectPrinter(SECOND_PRINTER_LOCAL_ID);
 
         eventually(() -> runOnMainThread(() -> assertEquals(SECOND_PRINTER_LOCAL_ID,
                 sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -205,7 +377,7 @@
         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
 
         // Select the first printer.
-        selectPrinter(FIRST_PRINTER_NAME);
+        selectPrinter(FIRST_PRINTER_LOCAL_ID);
 
         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
                 sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -295,7 +467,7 @@
         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
 
         // Select the first printer.
-        selectPrinter(FIRST_PRINTER_NAME);
+        selectPrinter(FIRST_PRINTER_LOCAL_ID);
 
         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
                 sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -491,27 +663,9 @@
 
             assertTrue(sSession.isPrinterDiscoveryStarted());
 
-            if (sSession.getPrinters().isEmpty()) {
-                List<PrinterInfo> printers = new ArrayList<>();
+            addPrinter(FIRST_PRINTER_LOCAL_ID, false);
+            addPrinter(SECOND_PRINTER_LOCAL_ID, false);
 
-                // Add the first printer.
-                PrinterId firstPrinterId = sSession.getService().generatePrinterId(
-                        FIRST_PRINTER_LOCAL_ID);
-                PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
-                        FIRST_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
-                    .build();
-                printers.add(firstPrinter);
-
-                // Add the first printer.
-                PrinterId secondPrinterId = sSession.getService().generatePrinterId(
-                        SECOND_PRINTER_LOCAL_ID);
-                PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
-                        SECOND_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
-                    .build();
-                printers.add(secondPrinter);
-
-                sSession.addPrinters(printers);
-            }
             return null;
         }, invocation -> {
             assertFalse(sSession.isPrinterDiscoveryStarted());
diff --git a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
index 9c5c6ab..6a65e00 100644
--- a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
@@ -27,11 +27,12 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+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 android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiSelector;
diff --git a/tests/tests/print/src/android/print/cts/Utils.java b/tests/tests/print/src/android/print/cts/Utils.java
deleted file mode 100644
index 93f5632..0000000
--- a/tests/tests/print/src/android/print/cts/Utils.java
+++ /dev/null
@@ -1,148 +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.print.cts;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.print.PrintJob;
-import android.print.PrintManager;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-/**
- * Utilities for print tests
- */
-public class Utils {
-    private static final String LOG_TAG = "Utils";
-
-    /**
-     * A {@link Runnable} that can throw an {@link Throwable}.
-     */
-    public interface Invokable {
-        void run() throws Throwable;
-    }
-
-    /**
-     * Run a {@link Invokable} and expect and {@link Throwable} of a certain type.
-     *
-     * @param r             The {@link Invokable} to run
-     * @param expectedClass The expected {@link Throwable} type
-     */
-    public static void assertException(@NonNull Invokable r,
-            @NonNull Class<? extends Throwable> expectedClass) throws Throwable {
-        try {
-            r.run();
-        } catch (Throwable e) {
-            if (e.getClass().isAssignableFrom(expectedClass)) {
-                return;
-            } else {
-                Log.e(LOG_TAG, "Expected: " + expectedClass.getName() + ", got: "
-                        + e.getClass().getName());
-                throw e;
-            }
-        }
-
-        throw new AssertionError("No throwable thrown");
-    }
-
-    /**
-     * Run a {@link Invokable} on the main thread and forward the {@link Throwable} if one was
-     * thrown.
-     *
-     * @param r The {@link Invokable} to run
-     *
-     * @throws Throwable If the {@link Runnable} caused an issue
-     */
-    static void runOnMainThread(@NonNull final Invokable r) throws Throwable {
-        final Object synchronizer = new Object();
-        final Throwable[] thrown = new Throwable[1];
-
-        synchronized (synchronizer) {
-            (new Handler(Looper.getMainLooper())).post(() -> {
-                synchronized (synchronizer) {
-                    try {
-                        r.run();
-                    } catch (Throwable t) {
-                        thrown[0] = t;
-                    }
-
-                    synchronizer.notify();
-                }
-            });
-
-            synchronizer.wait();
-        }
-
-        if (thrown[0] != null) {
-            throw thrown[0];
-        }
-    }
-
-    /**
-     * 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 {
-        long start = System.currentTimeMillis();
-
-        while (true) {
-            try {
-                r.run();
-                break;
-            } catch (Throwable e) {
-                if (System.currentTimeMillis() - start < BasePrintTest.OPERATION_TIMEOUT_MILLIS) {
-                    Log.e(LOG_TAG, "Ignoring exception", e);
-
-                    try {
-                        Thread.sleep(500);
-                    } catch (InterruptedException e1) {
-                        Log.e(LOG_TAG, "Interrupted", e);
-                    }
-                } else {
-                    throw e;
-                }
-            }
-        }
-    }
-
-    /**
-     * @param name Name of print job
-     *
-     * @return The print job for the name
-     *
-     * @throws Exception If print job could not be found
-     */
-    static @NonNull PrintJob getPrintJob(@NonNull PrintManager pm, @NonNull String name)
-            throws Exception {
-        for (android.print.PrintJob job : pm.getPrintJobs()) {
-            if (job.getInfo().getLabel().equals(name)) {
-                return job;
-            }
-        }
-
-        throw new Exception("Print job " + name + " not found in " + pm.getPrintJobs());
-    }
-
-    /**
-     * @return The print manager
-     */
-    static @NonNull PrintManager getPrintManager(@NonNull Context context) {
-        return (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java b/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
deleted file mode 100644
index c72d6f9..0000000
--- a/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
+++ /dev/null
@@ -1,28 +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.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class AddPrintersActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java b/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
deleted file mode 100644
index cf52ece..0000000
--- a/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
+++ /dev/null
@@ -1,76 +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.print.cts.services;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.print.PrintJobInfo;
-import android.print.PrinterInfo;
-import android.printservice.PrintService;
-
-/**
- * Custom print options activity for both print services
- */
-public class CustomPrintOptionsActivity extends Activity {
-    /** Lock for {@link #sCallback} */
-    private static final Object sLock = new Object();
-
-    /** Currently registered callback for _both_ first and second print service. */
-    private static CustomPrintOptionsCallback sCallback = null;
-
-    /**
-     * Set a new callback called when the custom options activity is launched.
-     *
-     * @param callback The new callback or null, if the callback should be unregistered.
-     */
-    public static void setCallBack(CustomPrintOptionsCallback callback) {
-        synchronized (sLock) {
-            sCallback = callback;
-        }
-    }
-
-    /**
-     * Callback executed for this activity. Set via {@link #setCallBack}.
-     */
-    public interface CustomPrintOptionsCallback {
-        PrintJobInfo executeCustomPrintOptionsActivity(PrintJobInfo printJob,
-                PrinterInfo printer);
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent result = new Intent();
-
-        synchronized (sLock) {
-            if (sCallback != null) {
-                PrintJobInfo printJobInfo = getIntent().getParcelableExtra(
-                        PrintService.EXTRA_PRINT_JOB_INFO);
-                PrinterInfo printerInfo = getIntent().getParcelableExtra(
-                        "android.intent.extra.print.EXTRA_PRINTER_INFO");
-
-                result.putExtra(PrintService.EXTRA_PRINT_JOB_INFO,
-                        sCallback.executeCustomPrintOptionsActivity(printJobInfo, printerInfo));
-            }
-        }
-
-        setResult(Activity.RESULT_OK, result);
-        finish();
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/FirstPrintService.java b/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
deleted file mode 100644
index a234de4..0000000
--- a/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
+++ /dev/null
@@ -1,40 +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.print.cts.services;
-
-public class FirstPrintService extends StubbablePrintService {
-
-    private static final Object sLock = new Object();
-
-    private static PrintServiceCallbacks sCallbacks;
-
-    public static void setCallbacks(PrintServiceCallbacks callbacks) {
-        synchronized (sLock) {
-            sCallbacks = callbacks;
-        }
-    }
-
-    @Override
-    protected PrintServiceCallbacks getCallbacks() {
-        synchronized (sLock) {
-            if (sCallbacks != null) {
-                sCallbacks.setService(this);
-            }
-            return sCallbacks;
-        }
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/InfoActivity.java b/tests/tests/print/src/android/print/cts/services/InfoActivity.java
deleted file mode 100644
index 22c1bb5..0000000
--- a/tests/tests/print/src/android/print/cts/services/InfoActivity.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package android.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.Observable;
-
-public class InfoActivity extends Activity {
-    public interface Observer {
-        void onCreate(Activity createdActivity);
-    }
-
-    private static final ArrayList<Observer> sObservers = new ArrayList<>();
-
-    public static void addObserver(Observer observer) {
-        synchronized (sObservers) {
-            sObservers.add(observer);
-        }
-    }
-
-    public static void clearObservers() {
-        synchronized (sObservers) {
-            sObservers.clear();
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        synchronized (sObservers) {
-            ArrayList<Observer> observers = (ArrayList<Observer>) sObservers.clone();
-
-            for (Observer observer : observers) {
-                observer.onCreate(this);
-            }
-        }
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
deleted file mode 100644
index 749e8a9..0000000
--- a/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
+++ /dev/null
@@ -1,38 +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.print.cts.services;
-
-import android.printservice.PrintJob;
-
-public abstract class PrintServiceCallbacks {
-
-    private StubbablePrintService mService;
-
-    public StubbablePrintService getService() {
-        return mService;
-    }
-
-    public void setService(StubbablePrintService service) {
-        mService = service;
-    }
-
-    public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
-
-    public abstract void onRequestCancelPrintJob(PrintJob printJob);
-
-    public abstract void onPrintJobQueued(PrintJob printJob);
-}
diff --git a/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
deleted file mode 100644
index ebddda1..0000000
--- a/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
+++ /dev/null
@@ -1,51 +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.print.cts.services;
-
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-
-import java.util.List;
-
-public abstract class PrinterDiscoverySessionCallbacks {
-
-    private StubbablePrinterDiscoverySession mSession;
-
-    public void setSession(StubbablePrinterDiscoverySession session) {
-        mSession = session;
-    }
-
-    public StubbablePrinterDiscoverySession getSession() {
-        return mSession;
-    }
-
-    public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
-
-    public abstract void onStopPrinterDiscovery();
-
-    public abstract void onValidatePrinters(List<PrinterId> printerIds);
-
-    public abstract void onStartPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
-            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
-
-    public abstract void onStopPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onDestroy();
-}
diff --git a/tests/tests/print/src/android/print/cts/services/SecondPrintService.java b/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
deleted file mode 100644
index 1029a8e..0000000
--- a/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
+++ /dev/null
@@ -1,40 +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.print.cts.services;
-
-public class SecondPrintService extends StubbablePrintService {
-
-    private static final Object sLock = new Object();
-
-    private static PrintServiceCallbacks sCallbacks;
-
-    public static void setCallbacks(PrintServiceCallbacks callbacks) {
-        synchronized (sLock) {
-            sCallbacks = callbacks;
-        }
-    }
-
-    @Override
-    protected PrintServiceCallbacks getCallbacks() {
-        synchronized (sLock) {
-            if (sCallbacks != null) {
-                sCallbacks.setService(this);
-            }
-            return sCallbacks;
-        }
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/SettingsActivity.java b/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
deleted file mode 100644
index eb23574..0000000
--- a/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
+++ /dev/null
@@ -1,28 +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.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java b/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
deleted file mode 100644
index 8c3b89b..0000000
--- a/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
+++ /dev/null
@@ -1,64 +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.print.cts.services;
-
-import android.content.Context;
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-import java.util.List;
-
-public abstract class StubbablePrintService extends PrintService {
-
-    @Override
-    public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            return new StubbablePrinterDiscoverySession(this,
-                    getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
-        }
-        return null;
-    }
-
-    @Override
-    public void onRequestCancelPrintJob(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onRequestCancelPrintJob(printJob);
-        }
-    }
-
-    @Override
-    public void onPrintJobQueued(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onPrintJobQueued(printJob);
-        }
-    }
-
-    protected abstract PrintServiceCallbacks getCallbacks();
-
-    public void callAttachBaseContext(Context base) {
-        attachBaseContext(base);
-    }
-
-    public List<PrintJob> callGetActivePrintJobs() {
-        return getActivePrintJobs();
-    }
-
-}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java b/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
deleted file mode 100644
index e0ec1c1..0000000
--- a/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
+++ /dev/null
@@ -1,95 +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.print.cts.services;
-
-import android.support.annotation.NonNull;
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-import java.util.List;
-
-public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
-    private final PrintService mService;
-    private final PrinterDiscoverySessionCallbacks mCallbacks;
-
-    public StubbablePrinterDiscoverySession(PrintService service,
-            PrinterDiscoverySessionCallbacks callbacks) {
-        mService = service;
-        mCallbacks = callbacks;
-        if (mCallbacks != null) {
-            mCallbacks.setSession(this);
-        }
-    }
-
-    public PrintService getService() {
-        return mService;
-    }
-
-    @Override
-    public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterDiscovery(priorityList);
-        }
-    }
-
-    @Override
-    public void onStopPrinterDiscovery() {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterDiscovery();
-        }
-    }
-
-    @Override
-    public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
-        if (mCallbacks != null) {
-            mCallbacks.onValidatePrinters(printerIds);
-        }
-    }
-
-    @Override
-    public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull CustomPrinterIconCallback callback) {
-        if (mCallbacks != null) {
-            mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
-        }
-    }
-
-    @Override
-    public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mCallbacks != null) {
-            mCallbacks.onDestroy();
-        }
-    }
-}
diff --git a/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java b/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
index 344bcfc..4c2b369 100644
--- a/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
+++ b/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
@@ -16,6 +16,10 @@
 
 package android.print.pdf.cts;
 
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.pdf.PdfDocument;
@@ -23,13 +27,11 @@
 import android.print.pdf.PrintedPdfDocument;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static android.print.cts.Utils.assertException;
-import static org.junit.Assert.assertEquals;
-
 /**
  * Tests {@link PrintedPdfDocument}. This class is a subclass of {@link PdfDocument}, hence only the
  * overridden methods are tested.
diff --git a/tests/tests/proto/Android.mk b/tests/tests/proto/Android.mk
index cf8d321..595df7c 100644
--- a/tests/tests/proto/Android.mk
+++ b/tests/tests/proto/Android.mk
@@ -34,7 +34,7 @@
 
 #LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
         ctstestrunner
diff --git a/tests/tests/proto/AndroidTest.xml b/tests/tests/proto/AndroidTest.xml
index 025fc26..05df543 100644
--- a/tests/tests/proto/AndroidTest.xml
+++ b/tests/tests/proto/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Proto Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="metrics" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
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 fb23607..8178b46 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
@@ -79,7 +79,7 @@
         final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -106,7 +106,7 @@
         final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(5000,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 '\u3110');
@@ -138,7 +138,7 @@
                 'a');
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(2,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(3,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -177,7 +177,7 @@
         final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(token);
 
         Assert.assertArrayEquals(new byte[0], po.getBytes());
@@ -199,11 +199,11 @@
         final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(2,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         long token3 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(token3);
         po.endObject(token2);
         po.endObject(token1);
@@ -231,7 +231,7 @@
                 'a');
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(2,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(token);
 
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
@@ -263,11 +263,11 @@
         long token;
 
         token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endRepeatedObject(token);
 
         token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endRepeatedObject(token);
 
         Assert.assertArrayEquals(new byte[] {
@@ -299,7 +299,7 @@
                 'x');
 
         long token = po.startObject(ProtoOutputStream.makeFieldId(2,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(3,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'y');
@@ -308,7 +308,7 @@
                 "abcdefghijkl");
 
         long tokenEmpty = po.startObject(ProtoOutputStream.makeFieldId(500,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(tokenEmpty);
 
         po.endObject(token);
@@ -349,19 +349,19 @@
         final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'a');
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
 
         long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(6,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'c');
@@ -394,13 +394,13 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'a');
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -422,10 +422,10 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         po.endObject(token2);
         try {
@@ -444,13 +444,13 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'a');
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -472,10 +472,10 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         po.endObject(token2);
         try {
@@ -494,13 +494,13 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'a');
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -521,10 +521,10 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         try {
             po.endObject(token1);
@@ -542,20 +542,20 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(2,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'a');
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
         po.endObject(token2);
 
         long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeUInt32(ProtoOutputStream.makeFieldId(4,
                     ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
                 'b');
@@ -576,14 +576,14 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(token2);
 
         long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         try {
             po.endObject(token2);
@@ -601,14 +601,14 @@
         final ProtoOutputStream po = new ProtoOutputStream();
 
         long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.endObject(token2);
 
         long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         try {
             po.endObject(token2);
@@ -638,17 +638,17 @@
         all.nestedField.nested.nested.data = 3;
 
         final long token1 = po.startObject(ProtoOutputStream.makeFieldId(170,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                 1);
         final long token2 = po.startObject(ProtoOutputStream.makeFieldId(10002,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                 2);
         final long token3 = po.startObject(ProtoOutputStream.makeFieldId(10002,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                 3);
@@ -680,17 +680,17 @@
             all.nestedFieldRepeated[i].nested.nested.data = 3;
 
             final long token1 = po.startObject(ProtoOutputStream.makeFieldId(171,
-                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
             po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                     1);
             final long token2 = po.startObject(ProtoOutputStream.makeFieldId(10002,
-                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
             po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                     2);
             final long token3 = po.startObject(ProtoOutputStream.makeFieldId(10002,
-                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                        ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
             po.writeInt32(ProtoOutputStream.makeFieldId(10001,
                         ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
                     3);
@@ -724,7 +724,7 @@
         try {
             final ProtoOutputStream po = new ProtoOutputStream();
             po.startObject(ProtoOutputStream.makeFieldId(1,
-                        ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                        ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         } catch (IllegalArgumentException ex) {
             // good
         }
@@ -744,7 +744,7 @@
         try {
             final ProtoOutputStream po = new ProtoOutputStream();
             po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
-                        ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                        ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
         } catch (IllegalArgumentException ex) {
             // good
         }
@@ -756,7 +756,7 @@
     public void testMismatchedEndObject() {
         final ProtoOutputStream po = new ProtoOutputStream();
         final long token = po.startObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         try {
             po.endRepeatedObject(token);
@@ -771,7 +771,7 @@
     public void testMismatchedEndRepeatedObject() {
         final ProtoOutputStream po = new ProtoOutputStream();
         final long token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
-                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
 
         try {
             po.endObject(token);
@@ -806,7 +806,7 @@
 
         final ProtoOutputStream po = new ProtoOutputStream();
         po.writeObject(ProtoOutputStream.makeFieldId(10,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT),
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE),
                 innerRaw);
 
         final byte[] result = po.getBytes();
@@ -827,7 +827,7 @@
 
         final ProtoOutputStream po = new ProtoOutputStream();
         po.writeObject(ProtoOutputStream.makeFieldId(10,
-                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT),
+                    ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE),
                 innerRaw);
 
         final byte[] result = po.getBytes();
@@ -861,7 +861,7 @@
 
         final ProtoOutputStream po = new ProtoOutputStream();
         po.writeRepeatedObject(ProtoOutputStream.makeFieldId(10,
-                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT),
+                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE),
                 innerRaw);
 
         final byte[] result = po.getBytes();
@@ -882,7 +882,7 @@
 
         final ProtoOutputStream po = new ProtoOutputStream();
         po.writeRepeatedObject(ProtoOutputStream.makeFieldId(10,
-                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT),
+                    ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE),
                 innerRaw);
 
         Assert.assertArrayEquals(new byte[] {
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
index 3b38aba..c408d92 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
@@ -92,6 +92,7 @@
         // Try it in the provided name
         try {
             ProtoOutputStream.checkFieldId(42 | goodCount | badType, goodCount | fieldType);
+            fail("Should have thrown an exception.");
         } catch (IllegalArgumentException ex) {
             assertEquals("writeRepeated" + string
                         + " called for field 42 which should be used"
@@ -102,6 +103,7 @@
         // Try it in the expected name
         try {
             ProtoOutputStream.checkFieldId(43 | goodCount | fieldType, goodCount | badType);
+            fail("Should have thrown an exception.");
         } catch (IllegalArgumentException ex) {
             assertEquals("writeRepeated" + badTypeString
                         + " called for field 43 which should be used"
@@ -126,6 +128,7 @@
         // Try it in the provided name
         try {
             ProtoOutputStream.checkFieldId(44 | badCount | goodType, fieldCount | goodType);
+            fail("Should have thrown an exception.");
         } catch (IllegalArgumentException ex) {
             assertEquals("write" + string
                     + "Fixed32 called for field 44 which should be used"
@@ -136,6 +139,7 @@
         // Try it in the expected name
         try {
             ProtoOutputStream.checkFieldId(45 | fieldCount | goodType, badCount | goodType);
+            fail("Should have thrown an exception.");
         } catch (IllegalArgumentException ex) {
             String extraString = "";
             if (fieldCount == ProtoOutputStream.FIELD_COUNT_PACKED) {
@@ -151,7 +155,7 @@
     /**
      * Validate one call to checkFieldId that is expected to throw.
      */
-    public void assertCheckFieldIdThrows(long fieldId, long expectedFlags) 
+    public void assertCheckFieldIdThrows(long fieldId, long expectedFlags)
             throws Exception {
         try {
             ProtoOutputStream.checkFieldId(fieldId, expectedFlags);
diff --git a/tests/tests/provider/Android.mk b/tests/tests/provider/Android.mk
index fcd6d01..df178c7 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -28,7 +28,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.mock android.test.base.stubs android.test.runner.stubs telephony-common
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v4 \
diff --git a/tests/tests/provider/AndroidTest.xml b/tests/tests/provider/AndroidTest.xml
index 57198a8..a8fe97d 100644
--- a/tests/tests/provider/AndroidTest.xml
+++ b/tests/tests/provider/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Provider test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
index 31afce9..df94bda 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
@@ -72,6 +72,8 @@
             Data.DATA14,
             Data.DATA15,
             Data.CARRIER_PRESENCE,
+            Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+            Data.PREFERRED_PHONE_ACCOUNT_ID,
             Data.DATA_VERSION,
             Data.IS_PRIMARY,
             Data.IS_SUPER_PRIMARY,
@@ -286,6 +288,8 @@
                         Data.DATA14,
                         Data.DATA15,
                         Data.CARRIER_PRESENCE,
+                        Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+                        Data.PREFERRED_PHONE_ACCOUNT_ID,
                         Data.DATA_VERSION,
                         Data.IS_PRIMARY,
                         Data.IS_SUPER_PRIMARY,
@@ -379,6 +383,8 @@
                         Data.DATA14,
                         Data.DATA15,
                         Data.CARRIER_PRESENCE,
+                        Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+                        Data.PREFERRED_PHONE_ACCOUNT_ID,
                         Data.DATA_VERSION,
                         Data.IS_PRIMARY,
                         Data.IS_SUPER_PRIMARY,
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java b/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
index bdc973b..c0b4253 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
@@ -88,32 +88,46 @@
 
     public void testVoicemailsTable() throws Exception {
         final String[] VOICEMAILS_PROJECTION = new String[] {
-                Voicemails._ID, Voicemails.NUMBER, Voicemails.DATE, Voicemails.DURATION,
-                Voicemails.IS_READ, Voicemails.SOURCE_PACKAGE, Voicemails.SOURCE_DATA,
-                Voicemails.HAS_CONTENT, Voicemails.MIME_TYPE, Voicemails.TRANSCRIPTION,
+                Voicemails._ID,
+                Voicemails.NUMBER,
+                Voicemails.DATE,
+                Voicemails.DURATION,
+                Voicemails.NEW,
+                Voicemails.IS_READ,
+                Voicemails.SOURCE_PACKAGE,
+                Voicemails.SOURCE_DATA,
+                Voicemails.HAS_CONTENT,
+                Voicemails.MIME_TYPE,
+                Voicemails.TRANSCRIPTION,
                 Voicemails.PHONE_ACCOUNT_COMPONENT_NAME,
-                Voicemails.PHONE_ACCOUNT_ID, Voicemails.DIRTY, Voicemails.DELETED,
-                Voicemails.LAST_MODIFIED, Voicemails.BACKED_UP, Voicemails.RESTORED,
-                Voicemails.ARCHIVED, Voicemails.IS_OMTP_VOICEMAIL};
+                Voicemails.PHONE_ACCOUNT_ID,
+                Voicemails.DIRTY,
+                Voicemails.DELETED,
+                Voicemails.LAST_MODIFIED,
+                Voicemails.BACKED_UP,
+                Voicemails.RESTORED,
+                Voicemails.ARCHIVED,
+                Voicemails.IS_OMTP_VOICEMAIL};
         final int ID_INDEX = 0;
         final int NUMBER_INDEX = 1;
         final int DATE_INDEX = 2;
         final int DURATION_INDEX = 3;
-        final int IS_READ_INDEX = 4;
-        final int SOURCE_PACKAGE_INDEX = 5;
-        final int SOURCE_DATA_INDEX = 6;
-        final int HAS_CONTENT_INDEX = 7;
-        final int MIME_TYPE_INDEX = 8;
-        final int TRANSCRIPTION_INDEX= 9;
-        final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 10;
-        final int PHONE_ACCOUNT_ID_INDEX = 11;
-        final int DIRTY_INDEX = 12;
-        final int DELETED_INDEX = 13;
-        final int LAST_MODIFIED_INDEX = 14;
-        final int BACKED_UP_INDEX = 15;
-        final int RESTORED_INDEX = 16;
-        final int ARCHIVED_INDEX = 17;
-        final int IS_OMTP_VOICEMAIL_INDEX = 18;
+        final int NEW_INDEX = 4;
+        final int IS_READ_INDEX = 5;
+        final int SOURCE_PACKAGE_INDEX = 6;
+        final int SOURCE_DATA_INDEX = 7;
+        final int HAS_CONTENT_INDEX = 8;
+        final int MIME_TYPE_INDEX = 9;
+        final int TRANSCRIPTION_INDEX = 10;
+        final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 11;
+        final int PHONE_ACCOUNT_ID_INDEX = 12;
+        final int DIRTY_INDEX = 13;
+        final int DELETED_INDEX = 14;
+        final int LAST_MODIFIED_INDEX = 15;
+        final int BACKED_UP_INDEX = 16;
+        final int RESTORED_INDEX = 17;
+        final int ARCHIVED_INDEX = 18;
+        final int IS_OMTP_VOICEMAIL_INDEX = 19;
 
         String insertCallsNumber = "0123456789";
         long insertCallsDuration = 120;
@@ -131,6 +145,7 @@
         value.put(Voicemails.NUMBER, insertCallsNumber);
         value.put(Voicemails.DATE, insertDate);
         value.put(Voicemails.DURATION, insertCallsDuration);
+        value.put(Voicemails.NEW, 0);
         // Source package is expected to be inserted by the provider, if not set.
         value.put(Voicemails.SOURCE_DATA, insertSourceData);
         value.put(Voicemails.MIME_TYPE, insertMimeType);
@@ -158,17 +173,18 @@
         assertEquals(mSourcePackageName, cursor.getString(SOURCE_PACKAGE_INDEX));
         assertEquals(insertSourceData, cursor.getString(SOURCE_DATA_INDEX));
         assertEquals(insertMimeType, cursor.getString(MIME_TYPE_INDEX));
+        assertEquals(0, cursor.getInt(NEW_INDEX));
         assertEquals(0, cursor.getInt(IS_READ_INDEX));
         assertEquals(1, cursor.getInt(HAS_CONTENT_INDEX));
-        assertEquals("foo",cursor.getString(TRANSCRIPTION_INDEX));
-        assertEquals("com.foo",cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_INDEX));
-        assertEquals("bar",cursor.getString(PHONE_ACCOUNT_ID_INDEX));
-        assertEquals(0,cursor.getInt(DIRTY_INDEX));
-        assertEquals(0,cursor.getInt(DELETED_INDEX));
-        assertEquals(0,cursor.getInt(BACKED_UP_INDEX));
-        assertEquals(0,cursor.getInt(RESTORED_INDEX));
-        assertEquals(0,cursor.getInt(ARCHIVED_INDEX));
-        assertEquals(0,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
+        assertEquals("foo", cursor.getString(TRANSCRIPTION_INDEX));
+        assertEquals("com.foo", cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_INDEX));
+        assertEquals("bar", cursor.getString(PHONE_ACCOUNT_ID_INDEX));
+        assertEquals(0, cursor.getInt(DIRTY_INDEX));
+        assertEquals(0, cursor.getInt(DELETED_INDEX));
+        assertEquals(0, cursor.getInt(BACKED_UP_INDEX));
+        assertEquals(0, cursor.getInt(RESTORED_INDEX));
+        assertEquals(0, cursor.getInt(ARCHIVED_INDEX));
+        assertEquals(0, cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
         int id = cursor.getInt(ID_INDEX);
         assertEquals(id, Integer.parseInt(uri.getLastPathSegment()));
         cursor.close();
@@ -179,6 +195,7 @@
         value.put(Voicemails.DATE, updateDate);
         value.put(Voicemails.DURATION, updateCallsDuration);
         value.put(Voicemails.SOURCE_DATA, updateSourceData);
+        value.put(Voicemails.NEW, 1);
         value.put(Voicemails.DIRTY, 1);
         value.put(Voicemails.DELETED, 1);
         value.put(Voicemails.BACKED_UP, 1);
@@ -196,12 +213,13 @@
         assertEquals(updateDate, cursor.getLong(DATE_INDEX));
         assertEquals(updateCallsDuration, cursor.getLong(DURATION_INDEX));
         assertEquals(updateSourceData, cursor.getString(SOURCE_DATA_INDEX));
-        assertEquals(1,cursor.getInt(DIRTY_INDEX));
-        assertEquals(1,cursor.getInt(DELETED_INDEX));
-        assertEquals(1,cursor.getInt(BACKED_UP_INDEX));
-        assertEquals(1,cursor.getInt(RESTORED_INDEX));
-        assertEquals(1,cursor.getInt(ARCHIVED_INDEX));
-        assertEquals(1,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
+        assertEquals(1, cursor.getInt(NEW_INDEX));
+        assertEquals(1, cursor.getInt(DIRTY_INDEX));
+        assertEquals(1, cursor.getInt(DELETED_INDEX));
+        assertEquals(1, cursor.getInt(BACKED_UP_INDEX));
+        assertEquals(1, cursor.getInt(RESTORED_INDEX));
+        assertEquals(1, cursor.getInt(ARCHIVED_INDEX));
+        assertEquals(1, cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
         cursor.close();
 
         // Test: delete
@@ -213,7 +231,7 @@
     }
 
     public void testForeignUpdate_dirty() throws Exception {
-        if(!hasTelephony(getInstrumentation().getContext())){
+        if (!hasTelephony(getInstrumentation().getContext())) {
             Log.d(TAG, "skipping test that requires telephony feature");
             return;
         }
@@ -234,8 +252,34 @@
         }
     }
 
+    public void testForeignUpdate_retainDirty_notDirty() throws Exception {
+        if (!hasTelephony(getInstrumentation().getContext())) {
+            Log.d(TAG, "skipping test that requires telephony feature");
+            return;
+        }
+        // only the default dialer has WRITE_VOICEMAIL permission, which can modify voicemails of
+        // a foreign source package.
+        setTestAsDefaultDialer();
+        ContentValues values = new ContentValues();
+        values.put(Voicemails.SOURCE_PACKAGE, FOREIGN_SOURCE);
+
+        Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(FOREIGN_SOURCE), values);
+
+        ContentValues newValues = new ContentValues();
+        newValues.put(Voicemails.TRANSCRIPTION, "foo");
+        newValues.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+        mVoicemailProvider.update(uri, newValues, null, null);
+
+        try (Cursor cursor = mVoicemailProvider
+                .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+            cursor.moveToFirst();
+            assertEquals(0, cursor.getInt(0));
+        }
+    }
+
     public void testForeignUpdate_explicitNotDirty() throws Exception {
-        if(!hasTelephony(getInstrumentation().getContext())){
+        if (!hasTelephony(getInstrumentation().getContext())) {
             Log.d(TAG, "skipping test that requires telephony feature");
             return;
         }
@@ -246,7 +290,7 @@
         Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(FOREIGN_SOURCE), values);
 
         ContentValues updateValues = new ContentValues();
-        updateValues.put(Voicemails.DIRTY,0);
+        updateValues.put(Voicemails.DIRTY, 0);
         mVoicemailProvider.update(uri, updateValues, null, null);
 
         try (Cursor cursor = mVoicemailProvider
@@ -257,7 +301,7 @@
     }
 
     public void testForeignUpdate_null_dirty() throws Exception {
-        if(!hasTelephony(getInstrumentation().getContext())){
+        if (!hasTelephony(getInstrumentation().getContext())) {
             Log.d(TAG, "skipping test that requires telephony feature");
             return;
         }
@@ -279,7 +323,7 @@
     }
 
     public void testForeignUpdate_NotNormalized_normalized() throws Exception {
-        if(!hasTelephony(getInstrumentation().getContext())){
+        if (!hasTelephony(getInstrumentation().getContext())) {
             Log.d(TAG, "skipping test that requires telephony feature");
             return;
         }
@@ -303,7 +347,7 @@
     public void testLocalUpdate_notDirty() throws Exception {
 
         ContentValues values = new ContentValues();
-        values.put(Voicemails.DIRTY,1);
+        values.put(Voicemails.DIRTY, 1);
 
         Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(mSourcePackageName), values);
 
@@ -316,6 +360,25 @@
         }
     }
 
+    public void testLocalUpdate_retainDirty_dirty() throws Exception {
+
+        ContentValues values = new ContentValues();
+        values.put(Voicemails.DIRTY, 1);
+
+        Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(mSourcePackageName), values);
+
+        ContentValues newValues = new ContentValues();
+        newValues.put(Voicemails.TRANSCRIPTION, "foo");
+        newValues.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+        mVoicemailProvider.update(uri, newValues, null, null);
+
+        try (Cursor cursor = mVoicemailProvider
+                .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+            cursor.moveToFirst();
+            assertEquals(cursor.getInt(0), 1);
+        }
+    }
 
     // Data column should be automatically generated during insert.
     public void testInsert_doesNotUpdateDataColumn() throws Exception {
@@ -351,7 +414,7 @@
     private void assertDataNotEquals(String newFilePath) throws RemoteException {
         // Make sure data value is not actually updated.
         final Cursor cursor = mVoicemailProvider.query(mVoicemailContentUri,
-                new String[]{Voicemails._DATA}, null, null, null);
+                new String[] {Voicemails._DATA}, null, null, null);
         cursor.moveToNext();
         final String data = cursor.getString(0);
         assertFalse(data.equals(newFilePath));
@@ -494,10 +557,10 @@
                 packageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
     }
 
-    private void setTestAsDefaultDialer() throws Exception{
+    private void setTestAsDefaultDialer() throws Exception {
         assertTrue(mPreviousDefaultDialer == null);
         mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
-        setDefaultDialer(getInstrumentation(),PACKAGE);
+        setDefaultDialer(getInstrumentation(), PACKAGE);
     }
 
     private static String setDefaultDialer(Instrumentation instrumentation, String packageName)
diff --git a/tests/tests/renderscript/Android.mk b/tests/tests/renderscript/Android.mk
index 1a8c549..5da2036 100644
--- a/tests/tests/renderscript/Android.mk
+++ b/tests/tests/renderscript/Android.mk
@@ -30,8 +30,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    xmp_toolkit \
-    legacy-android-test
+    xmp_toolkit
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 LOCAL_JNI_SHARED_LIBRARIES := libcoremathtestcpp_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/renderscript/AndroidTest.xml b/tests/tests/renderscript/AndroidTest.xml
index 979b756..2788236 100644
--- a/tests/tests/renderscript/AndroidTest.xml
+++ b/tests/tests/renderscript/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Renderscript Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
index 9b355b0..ab326ff 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
@@ -37,14 +37,14 @@
     /**
      * Test the orignal refocus code
      */
-    public void testOriginalRefocus() {
+    public void testOriginalRefocus() throws IOException {
         refocus(RenderScriptTask.script.f32, 95);
     }
 
     /**
      * Test the new refocus code
      */
-    public void testNewRefocus() {
+    public void testNewRefocus() throws IOException {
         // The new implementation may run on a GPU using relaxed floating point
         // mathematics. Hence more relaxed precision requirement.
         refocus(RenderScriptTask.script.d1new, 45);
@@ -54,7 +54,7 @@
      * Test a refocus operator against the refocus_reference image
      * @param impl version of refocus to run
      */
-    private void refocus(RenderScriptTask.script impl, double minimumPSNR) {
+    private void refocus(RenderScriptTask.script impl, double minimumPSNR) throws IOException {
         Context ctx = getContext();
 
         RenderScript rs = RenderScript.create(ctx);
@@ -63,27 +63,26 @@
             current_rgbz = new RGBZ(getResourceRef(R.drawable.test_image),
                                     getResourceRef(R.drawable.test_depthmap),
                                     ctx.getContentResolver(), ctx);
-        } catch (IOException e) {
-            e.printStackTrace();
-            assertNull(e);
+            DepthOfFieldOptions current_depth_options = new DepthOfFieldOptions(current_rgbz);
+            RsTaskParams rsTaskParam = new RsTaskParams(rs, current_depth_options);
+
+            RenderScriptTask renderScriptTask = new RenderScriptTask(rs, impl);
+            Bitmap outputImage = renderScriptTask.applyRefocusFilter(rsTaskParam.mOptions);
+
+            Bitmap expectedImage = BitmapFactory.decodeResource(ctx.getResources(),
+                    R.drawable.expected_output);
+
+            double psnr = ImageCompare.psnr(outputImage, expectedImage);
+            android.util.Log.i("RefocusTest", "psnr = " + String.format("%.02f", psnr));
+            if (psnr < minimumPSNR) {
+                MediaStoreSaver.savePNG(outputImage, "refocus", "refocus_output" , ctx);
+                assertTrue("Required minimum psnr = " + String.format("%.02f; ", minimumPSNR) +
+                           "Actual psnr = " + String.format("%.02f", psnr),
+                           false);
+            }
+        } finally {
+            rs.destroy();
         }
-        DepthOfFieldOptions current_depth_options = new DepthOfFieldOptions(current_rgbz);
-        RsTaskParams rsTaskParam = new RsTaskParams(rs, current_depth_options);
-
-        RenderScriptTask renderScriptTask = new RenderScriptTask(rs, impl);
-        Bitmap outputImage = renderScriptTask.applyRefocusFilter(rsTaskParam.mOptions);
-
-        Bitmap expectedImage = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.expected_output);
-
-        double psnr = ImageCompare.psnr(outputImage, expectedImage);
-        android.util.Log.i("RefocusTest", "psnr = " + String.format("%.02f", psnr));
-        if (psnr < minimumPSNR) {
-            MediaStoreSaver.savePNG(outputImage, "refocus", "refocus_output" , ctx);
-            assertTrue("Required minimum psnr = " + String.format("%.02f; ", minimumPSNR) +
-                       "Actual psnr = " + String.format("%.02f", psnr),
-                       false);
-        }
-        rs.destroy();
     }
 
 
diff --git a/tests/tests/renderscriptlegacy/AndroidTest.xml b/tests/tests/renderscriptlegacy/AndroidTest.xml
index 6550d5d..127ace1 100644
--- a/tests/tests/renderscriptlegacy/AndroidTest.xml
+++ b/tests/tests/renderscriptlegacy/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Renderscript legacy Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/rsblas/Android.mk b/tests/tests/rsblas/Android.mk
index 0385573..637f08e 100644
--- a/tests/tests/rsblas/Android.mk
+++ b/tests/tests/rsblas/Android.mk
@@ -27,7 +27,8 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 LOCAL_JNI_SHARED_LIBRARIES := libbnnmdata_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/rsblas/AndroidTest.xml b/tests/tests/rsblas/AndroidTest.xml
index 5f8164b..7cbfe32 100644
--- a/tests/tests/rsblas/AndroidTest.xml
+++ b/tests/tests/rsblas/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS RS BLAS test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/rscpp/Android.mk b/tests/tests/rscpp/Android.mk
index 11957bf..f5c20ca 100644
--- a/tests/tests/rscpp/Android.mk
+++ b/tests/tests/rscpp/Android.mk
@@ -28,7 +28,8 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 LOCAL_JNI_SHARED_LIBRARIES := librscpptest_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/rscpp/AndroidTest.xml b/tests/tests/rscpp/AndroidTest.xml
index 9a9c6ba..61d05a8 100644
--- a/tests/tests/rscpp/AndroidTest.xml
+++ b/tests/tests/rscpp/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for renderscript cpp Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/sax/Android.mk b/tests/tests/sax/Android.mk
index 0c2a2b3..d854016 100644
--- a/tests/tests/sax/Android.mk
+++ b/tests/tests/sax/Android.mk
@@ -21,7 +21,9 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/sax/AndroidTest.xml b/tests/tests/sax/AndroidTest.xml
index 20609bc..f81aa67 100644
--- a/tests/tests/sax/AndroidTest.xml
+++ b/tests/tests/sax/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS SAX test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index b2247a8..3368526 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -28,10 +28,13 @@
     ctstestrunner \
     compatibility-device-util \
     guava \
-    platform-test-annotations \
-    legacy-android-test
+    platform-test-annotations
 
-LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
 
 LOCAL_JNI_SHARED_LIBRARIES := libctssecurity_jni libcts_jni libnativehelper_compat_libc++ \
                       libnativehelper \
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 2c47d89..03b2f9a 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS security test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index 6408e08..cddcd76 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -28,7 +28,6 @@
 		android_security_cts_LinuxRngTest.cpp \
 		android_security_cts_NativeCodeTest.cpp \
 		android_security_cts_SELinuxTest.cpp \
-		android_security_cts_SeccompTest.cpp \
 		android_security_cts_MMapExecutableTest.cpp \
 		android_security_cts_EncryptionTest.cpp \
 
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 835d1a6..2014591 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -49,11 +49,7 @@
         return JNI_ERR;
     }
 
-    if (register_android_security_cts_SeccompTest(env)) {
-        return JNI_ERR;
-    }
-
-     if (register_android_security_cts_KernelSettingsTest(env)) {
+    if (register_android_security_cts_KernelSettingsTest(env)) {
         return JNI_ERR;
     }
 
diff --git a/tests/tests/security/jni/android_security_cts_SeccompTest.cpp b/tests/tests/security/jni/android_security_cts_SeccompTest.cpp
deleted file mode 100644
index ee36cdd..0000000
--- a/tests/tests/security/jni/android_security_cts_SeccompTest.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <jni.h>
-
-#define LOG_TAG "SeccompTest"
-
-#include <cutils/log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-/*
- * Function: testSyscallBlocked
- * Purpose: test that the syscall listed is blocked by seccomp
- * Parameters:
- *        nr: syscall number
- * Returns:
- *        1 if blocked, else 0
- * Exceptions: None
- */
-static jboolean testSyscallBlocked(JNIEnv *, jobject, int nr) {
-    int pid = fork();
-    if (pid == 0) {
-        ALOGI("Calling syscall %d", nr);
-        int ret = syscall(nr);
-        return false;
-    } else {
-        int status;
-        int ret = waitpid(pid, &status, 0);
-        if (ret != pid) {
-            ALOGE("Unexpected return result from waitpid");
-            return false;
-        }
-
-        if (WIFEXITED(status)) {
-            ALOGE("syscall was not blocked");
-            return false;
-        }
-
-        if (WIFSIGNALED(status)) {
-            int signal = WTERMSIG(status);
-            if (signal == 31) {
-                ALOGI("syscall caused process termination");
-                return true;
-            }
-
-            ALOGE("Unexpected signal");
-            return false;
-        }
-
-        ALOGE("Unexpected status from syscall_exists");
-        return false;
-    }
-}
-
-static JNINativeMethod gMethods[] = {
-    { "testSyscallBlocked", "(I)Z",
-            (void*) testSyscallBlocked },
-};
-
-int register_android_security_cts_SeccompTest(JNIEnv* env)
-{
-    jclass clazz = env->FindClass("android/security/cts/SeccompTest");
-
-    return env->RegisterNatives(clazz, gMethods,
-            sizeof(gMethods) / sizeof(JNINativeMethod));
-}
diff --git a/tests/tests/security/res/raw/b38116746_new.ico b/tests/tests/security/res/raw/b38116746_new.ico
new file mode 100644
index 0000000..35ee5b5
--- /dev/null
+++ b/tests/tests/security/res/raw/b38116746_new.ico
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_67381469.webp b/tests/tests/security/res/raw/bug_67381469.webp
new file mode 100644
index 0000000..82ff6e4
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_67381469.webp
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
index c8bfbb1..203db12 100644
--- a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
+++ b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
@@ -17,30 +17,56 @@
 package android.security.cts;
 
 import android.graphics.BitmapFactory;
+import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.SecurityTest;
 import android.test.AndroidTestCase;
 
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.InputStream;
 
+import java.lang.Exception;
+
 import android.security.cts.R;
 
 @SecurityTest
 public class BitmapFactorySecurityTests extends AndroidTestCase {
-    private InputStream getResource(int resId) {
-        InputStream resource = mContext.getResources().openRawResource(R.raw.bug_38116746);
-        assertNotNull(resource);
-        return resource;
+    private FileDescriptor getResource(int resId) {
+        try {
+            InputStream is = mContext.getResources().openRawResource(resId);
+            assertNotNull(is);
+            File file = File.createTempFile("BitmapFactorySecurityFile" + resId, "img");
+            file.deleteOnExit();
+            FileOutputStream output = new FileOutputStream(file);
+            byte[] buffer = new byte[1024];
+            int readLength;
+            while ((readLength = is.read(buffer)) != -1) {
+                output.write(buffer, 0, readLength);
+            }
+            is.close();
+            output.close();
+            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+                    ParcelFileDescriptor.MODE_READ_ONLY);
+            return pfd.getFileDescriptor();
+        } catch (Exception e) {
+            fail("Could not get resource " + resId + "! " + e);
+            return null;
+        }
     }
 
     /**
-     * Verifies that decoding a corrupt ICO does not run out of memory.
+     * Verifies that decoding a corrupt ICO does crash.
      */
     public void test_android_bug_38116746() {
-        InputStream exploitImage = getResource(R.raw.bug_38116746);
+        FileDescriptor exploitImage = getResource(R.raw.bug_38116746);
         try {
-            BitmapFactory.decodeStream(exploitImage);
+            BitmapFactory.decodeFileDescriptor(exploitImage);
         } catch (OutOfMemoryError e) {
             fail("OOM attempting to decode ICO");
         }
+
+        // This previously crashed in fread. No need to check the output.
+        BitmapFactory.decodeFileDescriptor(getResource(R.raw.b38116746_new));
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index b880f5f..76f86d5 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -36,7 +36,6 @@
       "75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
       "DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13",
       "74:20:74:41:72:9C:DD:92:EC:79:31:D8:23:10:8D:C2:81:92:E2:BB",
-      "40:54:DA:6F:1C:3F:40:74:AC:ED:0F:EC:CD:DB:79:D1:53:FB:90:1D",
       "F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
       "58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
       "55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
@@ -51,6 +50,7 @@
       "DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
       "74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
       "31:43:64:9B:EC:CE:27:EC:ED:3A:3F:0B:8F:0D:E4:E8:91:DD:EE:CA",
+      "B7:AB:33:08:D1:EA:44:77:BA:14:80:12:5A:6F:BD:A9:36:49:0C:BB",
       "5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
       "2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
       "79:5F:88:60:C5:AB:7C:3D:92:E6:CB:F4:8D:E1:45:CD:11:EF:60:0B",
@@ -64,7 +64,6 @@
       "59:AF:82:79:91:86:C7:B4:75:07:CB:CF:03:57:46:EB:04:DD:B7:16",
       "50:30:06:09:1D:97:D4:F5:AE:39:F7:CB:E7:92:7D:7D:65:2D:34:31",
       "FE:45:65:9B:79:03:5B:98:A1:61:B5:51:2E:AC:DA:58:09:48:22:4D",
-      "1B:4B:39:61:26:27:6B:64:91:A2:68:6D:D7:02:43:21:2D:1F:1D:96",
       "8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
       "2F:78:3D:25:52:18:A7:4A:65:39:71:B5:2C:A2:9C:45:15:6F:E9:19",
       "BA:29:41:60:77:98:3F:F4:F3:EF:F2:31:05:3B:2E:EA:6D:4D:45:FD",
@@ -73,6 +72,7 @@
       "36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54",
       "1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
       "6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
+      "74:3A:F0:52:9B:D0:32:A0:F4:4A:83:CD:D4:BA:A9:7B:7C:2E:C4:9A",
       "D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
       "66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
       "DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
@@ -83,14 +83,14 @@
       "43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
       "F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
       "05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
-      "62:52:DC:40:F7:11:43:A2:2F:DE:9E:F7:34:8E:06:42:51:B1:81:18",
       "70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
       "D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
       "B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
-      "2E:14:DA:EC:28:F0:FA:1E:8E:38:9A:4E:AB:EB:26:C0:0A:D3:83:C3",
+      "4C:DD:51:A3:D1:F5:20:32:14:B0:C6:C5:32:23:03:91:C7:46:42:6D",
       "DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
       "0D:44:DD:8C:3C:8C:1A:1A:58:75:64:81:E9:0F:2E:2A:FF:B3:D2:6E",
       "CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
+      "FF:BD:CD:E7:82:C8:43:5E:3C:6F:26:86:5C:CA:A8:3A:45:5B:C3:0A",
       "13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
       "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
       "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
@@ -98,6 +98,7 @@
       "29:36:21:02:8B:20:ED:02:F5:66:C5:32:D1:D6:ED:90:9F:45:00:2F",
       "37:9A:19:7B:41:85:45:35:0C:A6:03:69:F3:3C:2E:AF:47:4F:20:79",
       "FA:B7:EE:36:97:26:62:FB:2D:B0:2A:F6:BF:03:FD:E8:7C:4B:2F:9B",
+      "C3:19:7C:39:24:E6:54:AF:1B:C4:AB:20:95:7A:E2:C3:0E:13:02:6A",
       "9F:74:4E:9F:2B:4D:BA:EC:0F:31:2C:50:B6:56:3B:8E:2D:93:C3:11",
       "A1:4B:48:D9:43:EE:0A:0E:40:90:4F:3C:E0:A4:C0:91:93:51:5D:3F",
       "C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
@@ -119,6 +120,7 @@
       "AA:DB:BC:22:23:8F:C4:01:A1:27:BB:38:DD:F4:1D:DB:08:9E:F0:12",
       "E2:52:FA:95:3F:ED:DB:24:60:BD:6E:28:F3:9C:CC:CF:5E:B3:3F:DE",
       "3B:C4:9F:48:F8:F3:73:A0:9C:1E:BD:F8:5B:B1:C3:65:C7:D8:11:B3",
+      "0F:36:38:5B:81:1A:25:C3:9B:31:4E:83:CA:E9:34:66:70:CC:74:B4",
       "28:90:3A:63:5B:52:80:FA:E6:77:4C:0B:6D:A7:D6:BA:A6:4A:F2:E8",
       "9C:BB:48:53:F6:A4:F6:D3:52:A4:E8:32:52:55:60:13:F5:AD:AF:65",
       "B1:BC:96:8B:D4:F4:9D:62:2A:A8:9A:81:F2:15:01:52:A4:1D:82:9C",
@@ -129,6 +131,7 @@
       "47:BE:AB:C9:22:EA:E8:0E:78:78:34:62:A7:9F:45:C2:54:FD:E6:8B",
       "3A:44:73:5A:E5:81:90:1F:24:86:61:46:1E:3B:9C:C4:5F:F5:3A:1B",
       "B3:1E:B1:B7:40:E3:6C:84:02:DA:DC:37:D4:4D:F5:D4:67:49:52:F9",
+      "58:D1:DF:95:95:67:6B:63:C0:F0:5B:1C:17:4D:8B:84:0B:C8:78:BD",
       "F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
       "3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
       "03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
@@ -139,12 +142,12 @@
       "B8:23:6B:00:2F:1D:16:86:53:01:55:6C:11:A4:37:CA:EB:FF:C3:BB",
       "87:82:C6:C3:04:35:3B:CF:D2:96:92:D2:59:3E:7D:44:D9:34:FF:11",
       "59:0D:2D:7D:88:4F:40:2E:61:7E:A5:62:32:17:65:CF:17:D8:94:E9",
+      "B8:BE:6D:CB:56:F1:55:B9:63:D4:12:CA:4E:06:34:C7:94:B2:1C:C0",
       "AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
       "DF:71:7E:AA:4A:D9:4E:C9:55:84:99:60:2D:48:DE:5F:BC:F0:3A:25",
       "F6:10:84:07:D6:F8:BB:67:98:0C:C2:E2:44:C2:EB:AE:1C:EF:63:BE",
       "AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4",
       "5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
-      "F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
       "9D:70:BB:01:A5:A4:A0:18:11:2E:F7:1C:01:B9:32:C5:34:E7:88:A8",
       "96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
       "4F:65:8E:1F:E9:06:D8:28:02:E9:54:47:41:C9:54:25:5D:69:CC:1A",
@@ -155,7 +158,6 @@
       "F9:B5:B6:32:45:5F:9C:BE:EC:57:5F:80:DC:E9:6E:2C:C7:B2:78:B7",
       "E6:21:F3:35:43:79:05:9A:4B:68:30:9D:8A:2F:74:22:15:87:EC:79",
       "89:DF:74:FE:5C:F4:0F:4A:80:F9:E3:37:7D:54:DA:91:E1:01:31:8E",
-      "E0:B4:32:2E:B2:F6:A5:68:B6:54:53:84:48:18:4A:50:36:87:43:84",
       "7E:04:DE:89:6A:3E:66:6D:00:E6:87:D3:3F:FA:D9:3B:E8:3D:34:9E",
       "6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
       "4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
@@ -163,4 +165,10 @@
       "8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16",
       "B1:2E:13:63:45:86:A4:6F:1A:B2:60:68:37:58:2D:C4:AC:FD:94:97",
   };
+
+  static final String[] WFA_CERTIFICATE_DATA = {
+      "BB:49:24:83:18:47:95:2B:DB:1A:12:B0:38:EC:51:54:AD:CB:DE:43",
+      "38:D6:63:9F:51:D7:24:21:AD:75:3C:C6:07:54:99:B7:1F:EC:F7:FC",
+      "51:50:1F:BF:CE:69:18:9D:60:9C:FA:F1:40:C5:76:75:5D:CC:1F:DF",
+  };
 }
diff --git a/tests/tests/security/src/android/security/cts/CertificateTest.java b/tests/tests/security/src/android/security/cts/CertificateTest.java
index 2b8f351..2588f4d 100644
--- a/tests/tests/security/src/android/security/cts/CertificateTest.java
+++ b/tests/tests/security/src/android/security/cts/CertificateTest.java
@@ -16,14 +16,20 @@
 
 package android.security.cts;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import android.content.pm.PackageManager;
 import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
@@ -31,10 +37,10 @@
 import java.util.List;
 import java.util.Set;
 
-import junit.framework.TestCase;
-
 @SecurityTest
-public class CertificateTest extends TestCase {
+public class CertificateTest extends AndroidTestCase {
+    // The directory for CA root certificates trusted by WFA (WiFi Alliance)
+    static final String DIR_OF_CACERTS_FOR_WFA = "/etc/security/cacerts_wfa";
 
     public void testNoRemovedCertificates() throws Exception {
         Set<String> expectedCertificates = new HashSet<String>(
@@ -79,6 +85,89 @@
         assertEquals("Blocked CA certificates", Collections.EMPTY_SET, deviceCertificates);
     }
 
+    /**
+     * This test exists because adding new ca certificate or removing the ca certificates trusted by
+     * WFA (WiFi Alliance) is not allowed.
+     *
+     * For questions, comments, and code reviews please contact security@android.com.
+     */
+    public void testNoRemovedWfaCertificates() throws Exception {
+        if (!supportPasspoint()) {
+            return;
+        }
+        Set<String> expectedCertificates = new HashSet<>(
+                Arrays.asList(CertificateData.WFA_CERTIFICATE_DATA));
+        Set<String> deviceWfaCertificates = getDeviceWfaCertificates();
+        expectedCertificates.removeAll(deviceWfaCertificates);
+        assertEquals("Missing WFA CA certificates", Collections.EMPTY_SET, expectedCertificates);
+    }
+
+    public void testNoAddedWfaCertificates() throws Exception {
+        if (!supportPasspoint()) {
+            return;
+        }
+        Set<String> expectedCertificates = new HashSet<String>(
+                Arrays.asList(CertificateData.WFA_CERTIFICATE_DATA));
+        Set<String> deviceWfaCertificates = getDeviceWfaCertificates();
+        deviceWfaCertificates.removeAll(expectedCertificates);
+        assertEquals("Unknown WFA CA certificates", Collections.EMPTY_SET, deviceWfaCertificates);
+    }
+
+    private boolean supportPasspoint() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT);
+    }
+
+    private KeyStore createWfaKeyStore(String dirPath) throws CertificateException, IOException,
+            KeyStoreException, NoSuchAlgorithmException {
+        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+        keyStore.load(null, null);
+        String wfaCertsDir = System.getenv("ANDROID_ROOT") + dirPath;
+        int index = 0;
+        for (X509Certificate cert : loadCertsFromDisk(wfaCertsDir)) {
+            keyStore.setCertificateEntry(String.format("%d", index++), cert);
+        }
+        return keyStore;
+    }
+
+    private Set<X509Certificate> loadCertsFromDisk(String directory) throws CertificateException,
+            IOException {
+        Set<X509Certificate> certs = new HashSet<>();
+        File certDir = new File(directory);
+        File[] certFiles = certDir.listFiles();
+        if (certFiles == null || certFiles.length <= 0) {
+            return certs;
+        }
+        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        for (File certFile : certFiles) {
+            FileInputStream fis = new FileInputStream(certFile);
+            Certificate cert = certFactory.generateCertificate(fis);
+            if (cert instanceof X509Certificate) {
+                certs.add((X509Certificate) cert);
+            }
+            fis.close();
+        }
+        return certs;
+    }
+
+    private Set<String> getDeviceWfaCertificates() throws KeyStoreException,
+            NoSuchAlgorithmException, CertificateException, IOException {
+        KeyStore wfaKeyStore = createWfaKeyStore(DIR_OF_CACERTS_FOR_WFA);
+        List<String> aliases = Collections.list(wfaKeyStore.aliases());
+        assertFalse(aliases.isEmpty());
+
+        Set<String> certificates = new HashSet<>();
+        for (String alias : aliases) {
+            assertTrue(wfaKeyStore.isCertificateEntry(alias));
+            X509Certificate certificate = (X509Certificate) wfaKeyStore.getCertificate(alias);
+            assertEquals(certificate.getSubjectUniqueID(), certificate.getIssuerUniqueID());
+            assertNotNull(certificate.getSubjectDN());
+            assertNotNull(certificate.getIssuerDN());
+            String fingerprint = getFingerprint(certificate);
+            certificates.add(fingerprint);
+        }
+        return certificates;
+    }
+
     private Set<String> getDeviceCertificates() throws KeyStoreException,
             NoSuchAlgorithmException, CertificateException, IOException {
         KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
diff --git a/tests/tests/security/src/android/security/cts/DecodeTest.java b/tests/tests/security/src/android/security/cts/DecodeTest.java
index e64e37a..0e92310 100644
--- a/tests/tests/security/src/android/security/cts/DecodeTest.java
+++ b/tests/tests/security/src/android/security/cts/DecodeTest.java
@@ -38,4 +38,17 @@
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
         assertNull(bitmap);
     }
+
+    /**
+     * Verifies that the device fails to decode a truncated animated webp.
+     *
+     * Prior to fixing bug 67381469, decoding this file would crash. Instead, it should fail to
+     * decode.
+     */
+    @SecurityTest
+    public void test_android_bug_67381469() {
+        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_67381469);
+        Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
+        assertNull(bitmap);
+    }
 }
diff --git a/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java b/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java
deleted file mode 100644
index a6bad84..0000000
--- a/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java
+++ /dev/null
@@ -1,582 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import android.security.cts.OpenSSLHeartbleedTest.AlertMessage;
-import android.security.cts.OpenSSLHeartbleedTest.HandshakeMessage;
-import android.security.cts.OpenSSLHeartbleedTest.HardcodedCertX509KeyManager;
-import android.security.cts.OpenSSLHeartbleedTest.TlsProtocols;
-import android.security.cts.OpenSSLHeartbleedTest.TlsRecord;
-import android.security.cts.OpenSSLHeartbleedTest.TlsRecordReader;
-import android.security.cts.OpenSSLHeartbleedTest.TrustAllX509TrustManager;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.KeyFactory;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ServerSocketFactory;
-import javax.net.SocketFactory;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-
-/**
- * Tests for the OpenSSL early ChangeCipherSpec (CCS) vulnerability (CVE-2014-0224).
- */
-@SecurityTest
-public class OpenSSLEarlyCCSTest extends InstrumentationTestCase {
-
-    // IMPLEMENTATION NOTE: This test spawns an SSLSocket client, SSLServerSocket server, and a
-    // Man-in-The-Middle (MiTM). The client connects to the MiTM which then connects to the server
-    // and starts forwarding all TLS records between the client and the server. In tests that check
-    // for the early CCS vulnerability, the MiTM also injects an early ChangeCipherSpec record into
-    // the traffic to which a correctly implemented peer is supposed to reply by immediately
-    // aborting the TLS handshake with an unexpected_message fatal alert.
-
-    // IMPLEMENTATION NOTE: This test spawns several background threads that perform network I/O
-    // on localhost. To ensure that these background threads are cleaned up at the end of the test,
-    // tearDown() kills the sockets they may be using. To aid this behavior, all Socket and
-    // ServerSocket instances are available as fields of this class. These fields should be accessed
-    // via setters and getters to avoid memory visibility issues due to concurrency.
-
-    private static final String TAG = OpenSSLEarlyCCSTest.class.getSimpleName();
-
-    private SSLServerSocket mServerListeningSocket;
-    private SSLSocket mServerSocket;
-    private SSLSocket mClientSocket;
-    private ServerSocket mMitmListeningSocket;
-    private Socket mMitmServerSocket;
-    private Socket mMitmClientSocket;
-    private ExecutorService mExecutorService;
-
-    private boolean mCCSWasInjected;
-    private TlsRecord mFirstRecordReceivedAfterCCSWasInjected;
-    private boolean mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted;
-
-    @Override
-    protected void tearDown() throws Exception {
-        Log.i(TAG, "Tearing down");
-        try {
-            if (mExecutorService != null) {
-                mExecutorService.shutdownNow();
-            }
-            OpenSSLHeartbleedTest.closeQuietly(getServerListeningSocket());
-            OpenSSLHeartbleedTest.closeQuietly(getServerSocket());
-            OpenSSLHeartbleedTest.closeQuietly(getClientSocket());
-            OpenSSLHeartbleedTest.closeQuietly(getMitmListeningSocket());
-            OpenSSLHeartbleedTest.closeQuietly(getMitmServerSocket());
-            OpenSSLHeartbleedTest.closeQuietly(getMitmClientSocket());
-        } finally {
-            super.tearDown();
-            Log.i(TAG, "Tear down completed");
-        }
-    }
-
-    /**
-     * Tests that TLS handshake succeeds when the MiTM simply forwards all data without tampering
-     * with it. This is to catch issues unrelated to early CCS.
-     */
-    public void testWithoutEarlyCCS() throws Exception {
-        handshake(false, false);
-    }
-
-    /**
-     * Tests whether client sockets are vulnerable to early CCS.
-     */
-    public void testEarlyCCSInjectedIntoClient() throws Exception {
-        checkEarlyCCS(true);
-    }
-
-    /**
-     * Tests whether server sockets are vulnerable to early CCS.
-     */
-    public void testEarlyCCSInjectedIntoServer() throws Exception {
-        checkEarlyCCS(false);
-    }
-
-    /**
-     * Tests for the early CCS vulnerability.
-     *
-     * @param client {@code true} to test the client, {@code false} to test the server.
-     */
-    private void checkEarlyCCS(boolean client) throws Exception {
-        // IMPLEMENTATION NOTE: The MiTM is forwarding all TLS records between the client and the
-        // server unmodified. Additionally, the MiTM transmits an early ChangeCipherSpec to server
-        // (if "client" argument is false) right before client's ClientKeyExchange or to client (if
-        // "client" argument is true) right before server's Certificate. The peer is expected to
-        // to abort the handshake immediately with unexpected_message alert.
-        try {
-            handshake(true, client);
-            // TLS handshake succeeded
-            assertTrue("Early CCS injected", wasCCSInjected());
-            fail("Handshake succeeded despite early CCS having been injected");
-        } catch (ExecutionException e) {
-            // TLS handshake failed
-            assertTrue("Early CCS injected", wasCCSInjected());
-            TlsRecord firstRecordReceivedAfterCCSWasInjected =
-                    getFirstRecordReceivedAfterCCSWasInjected();
-            assertNotNull(
-                    "Nothing received after early CCS was injected",
-                    firstRecordReceivedAfterCCSWasInjected);
-            if (firstRecordReceivedAfterCCSWasInjected.protocol == TlsProtocols.ALERT) {
-                AlertMessage alert = AlertMessage.tryParse(firstRecordReceivedAfterCCSWasInjected);
-                if ((alert != null)
-                        && (alert.level == AlertMessage.LEVEL_FATAL)
-                        && (alert.description == AlertMessage.DESCRIPTION_UNEXPECTED_MESSAGE)) {
-                    // Expected/correct response to an early CCS
-                    return;
-                }
-            }
-            fail("SSLSocket is vulnerable to early CCS in " + ((client) ? "client" : "server")
-                    + " mode: unexpected record received after CCS was injected: "
-                    + getRecordInfo(
-                            getFirstRecordReceivedAfterCCSWasInjected(),
-                            getFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted()));
-        }
-    }
-
-    /**
-     * Starts the client, server, and the MiTM. Makes the client and server perform a TLS handshake
-     * and exchange application-level data. The MiTM injects a ChangeCipherSpec record if requested
-     * by {@code earlyCCSInjected}. The direction of the injected message is specified by
-     * {@code injectedIntoClient}.
-     */
-    private void handshake(
-            final boolean earlyCCSInjected,
-            final boolean injectedIntoClient) throws Exception {
-        mExecutorService = Executors.newFixedThreadPool(4);
-        setServerListeningSocket(serverBind());
-        final SocketAddress serverAddress = getServerListeningSocket().getLocalSocketAddress();
-        Log.i(TAG, "Server bound to " + serverAddress);
-
-        setMitmListeningSocket(mitmBind());
-        final SocketAddress mitmAddress = getMitmListeningSocket().getLocalSocketAddress();
-        Log.i(TAG, "MiTM bound to " + mitmAddress);
-
-        // Start the MiTM daemon in the background
-        mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                mitmAcceptAndForward(serverAddress, earlyCCSInjected, injectedIntoClient);
-                return null;
-            }
-        });
-        // Start the server in the background
-        Future<Void> serverFuture = mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                serverAcceptAndHandshake();
-                return null;
-            }
-        });
-        // Start the client in the background
-        Future<Void> clientFuture = mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                clientConnectAndHandshake(mitmAddress);
-                return null;
-            }
-        });
-
-        // Wait for both client and server to terminate, to ensure that we observe all the traffic
-        // exchanged between them. Throw an exception if one of them failed.
-        Log.i(TAG, "Waiting for client");
-        // Wait for the client, but don't yet throw an exception if it failed.
-        Exception clientException = null;
-        try {
-            clientFuture.get(10, TimeUnit.SECONDS);
-        } catch (Exception e) {
-            clientException = e;
-        }
-        Log.i(TAG, "Waiting for server");
-        // Wait for the server and throw an exception if it failed.
-        serverFuture.get(5, TimeUnit.SECONDS);
-        // Throw an exception if the client failed.
-        if (clientException != null) {
-            throw clientException;
-        }
-        Log.i(TAG, "Handshake completed and application data exchanged");
-    }
-
-    private void clientConnectAndHandshake(SocketAddress serverAddress) throws Exception {
-        SSLContext sslContext = SSLContext.getInstance("TLS");
-        sslContext.init(
-                null,
-                new TrustManager[] {new TrustAllX509TrustManager()},
-                null);
-        SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
-        setClientSocket(socket);
-        try {
-            Log.i(TAG, "Client connecting to " + serverAddress);
-            socket.connect(serverAddress);
-            Log.i(TAG, "Client connected to server from " + socket.getLocalSocketAddress());
-            // Ensure a TLS handshake is performed and an exception is thrown if it fails.
-            socket.getOutputStream().write("client".getBytes());
-            socket.getOutputStream().flush();
-            Log.i(TAG, "Client sent request. Reading response");
-            int b = socket.getInputStream().read();
-            Log.i(TAG, "Client read response: " + b);
-        } catch (Exception e) {
-            // Make sure the test log includes exceptions from all parties involved.
-            Log.w(TAG, "Client failed", e);
-            throw e;
-          } finally {
-            socket.close();
-        }
-    }
-
-    private SSLServerSocket serverBind() throws Exception {
-        // Load the server's private key and cert chain
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(
-                OpenSSLHeartbleedTest.readResource(
-                        getInstrumentation().getContext(), R.raw.openssl_heartbleed_test_key)));
-        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-        X509Certificate[] certChain =  new X509Certificate[] {
-                (X509Certificate) certFactory.generateCertificate(
-                        new ByteArrayInputStream(OpenSSLHeartbleedTest.readResource(
-                                getInstrumentation().getContext(),
-                                R.raw.openssl_heartbleed_test_cert)))
-        };
-
-        // Initialize TLS context to use the private key and cert chain for server sockets
-        SSLContext sslContext = SSLContext.getInstance("TLS");
-        sslContext.init(
-                new KeyManager[] {new HardcodedCertX509KeyManager(privateKey, certChain)},
-                null,
-                null);
-
-        Log.i(TAG, "Server binding to local port");
-        return (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(0);
-    }
-
-    private void serverAcceptAndHandshake() throws Exception {
-        SSLSocket socket = null;
-        SSLServerSocket serverSocket = getServerListeningSocket();
-        try {
-            Log.i(TAG, "Server listening for incoming connection");
-            socket = (SSLSocket) serverSocket.accept();
-            setServerSocket(socket);
-            Log.i(TAG, "Server accepted connection from " + socket.getRemoteSocketAddress());
-            // Ensure a TLS handshake is performed and an exception is thrown if it fails.
-            socket.getOutputStream().write("server".getBytes());
-            socket.getOutputStream().flush();
-            Log.i(TAG, "Server sent reply. Reading response");
-            int b = socket.getInputStream().read();
-            Log.i(TAG, "Server read response: " + b);
-        } catch (Exception e) {
-            // Make sure the test log includes exceptions from all parties involved.
-            Log.w(TAG, "Server failed", e);
-            throw e;
-        } finally {
-            if (socket != null) {
-                socket.close();
-            }
-        }
-    }
-
-    private ServerSocket mitmBind() throws Exception {
-        Log.i(TAG, "MiTM binding to local port");
-        return ServerSocketFactory.getDefault().createServerSocket(0);
-    }
-
-    /**
-     * Accepts the connection on the MiTM listening socket, forwards the TLS records between the
-     * client and the server, and, if requested, injects an early {@code ChangeCipherSpec} record.
-     *
-     * @param injectEarlyCCS whether to inject an early {@code ChangeCipherSpec} record.
-     * @param injectIntoClient when {@code injectEarlyCCS} is {@code true}, whether to inject the
-     *        {@code ChangeCipherSpec} record into client or into server.
-     */
-    private void mitmAcceptAndForward(
-            SocketAddress serverAddress,
-            final boolean injectEarlyCCS,
-            final boolean injectIntoClient) throws Exception {
-        Socket clientSocket = null;
-        Socket serverSocket = null;
-        ServerSocket listeningSocket = getMitmListeningSocket();
-        try {
-            Log.i(TAG, "MiTM waiting for incoming connection");
-            clientSocket = listeningSocket.accept();
-            setMitmClientSocket(clientSocket);
-            Log.i(TAG, "MiTM accepted connection from " + clientSocket.getRemoteSocketAddress());
-            serverSocket = SocketFactory.getDefault().createSocket();
-            setMitmServerSocket(serverSocket);
-            Log.i(TAG, "MiTM connecting to server " + serverAddress);
-            serverSocket.connect(serverAddress, 10000);
-            Log.i(TAG, "MiTM connected to server from " + serverSocket.getLocalSocketAddress());
-            final InputStream serverInputStream = serverSocket.getInputStream();
-            final OutputStream clientOutputStream = clientSocket.getOutputStream();
-            Future<Void> serverToClientTask = mExecutorService.submit(new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    // Inject early ChangeCipherSpec before Certificate, if requested
-                    forwardTlsRecords(
-                            "MiTM S->C",
-                            serverInputStream,
-                            clientOutputStream,
-                            (injectEarlyCCS && injectIntoClient)
-                                      ? HandshakeMessage.TYPE_CERTIFICATE : -1);
-                    return null;
-                }
-            });
-            // Inject early ChangeCipherSpec before ClientKeyExchange, if requested
-            forwardTlsRecords(
-                    "MiTM C->S",
-                    clientSocket.getInputStream(),
-                    serverSocket.getOutputStream(),
-                    (injectEarlyCCS && !injectIntoClient)
-                            ? HandshakeMessage.TYPE_CLIENT_KEY_EXCHANGE : -1);
-            serverToClientTask.get(10, TimeUnit.SECONDS);
-        } catch (Exception e) {
-            // Make sure the test log includes exceptions from all parties involved.
-            Log.w(TAG, "MiTM failed", e);
-            throw e;
-        } finally {
-            OpenSSLHeartbleedTest.closeQuietly(clientSocket);
-            OpenSSLHeartbleedTest.closeQuietly(serverSocket);
-        }
-    }
-
-    /**
-     * Forwards TLS records from the provided {@code InputStream} to the provided
-     * {@code OutputStream}. If requested, injects an early {@code ChangeCipherSpec}.
-     */
-    private void forwardTlsRecords(
-            String logPrefix,
-            InputStream in,
-            OutputStream out,
-            int handshakeMessageTypeBeforeWhichToInjectEarlyCCS) throws Exception {
-        Log.i(TAG, logPrefix + ": record forwarding started");
-        boolean interestingRecordsLogged =
-                handshakeMessageTypeBeforeWhichToInjectEarlyCCS == -1;
-        try {
-            TlsRecordReader reader = new TlsRecordReader(in);
-            byte[] recordBytes;
-            // Fragments contained in records may be encrypted after a certain point in the
-            // handshake. Once they are encrypted, this MiTM cannot inspect their plaintext which.
-            boolean fragmentEncryptionMayBeEnabled = false;
-            while ((recordBytes = reader.readRecord()) != null) {
-                TlsRecord record = TlsRecord.parse(recordBytes);
-                forwardTlsRecord(logPrefix,
-                        recordBytes,
-                        record,
-                        fragmentEncryptionMayBeEnabled,
-                        out,
-                        interestingRecordsLogged,
-                        handshakeMessageTypeBeforeWhichToInjectEarlyCCS);
-                if (record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC) {
-                    fragmentEncryptionMayBeEnabled = true;
-                }
-            }
-        } finally {
-            Log.d(TAG, logPrefix + ": record forwarding finished");
-        }
-    }
-
-    private void forwardTlsRecord(
-            String logPrefix,
-            byte[] recordBytes,
-            TlsRecord record,
-            boolean fragmentEncryptionMayBeEnabled,
-            OutputStream out,
-            boolean interestingRecordsLogged,
-            int handshakeMessageTypeBeforeWhichToInjectCCS) throws IOException {
-        // Save information about the record if it's of interest to this test
-        if (interestingRecordsLogged) {
-            if (wasCCSInjected()) {
-                setFirstRecordReceivedAfterCCSWasInjected(record, fragmentEncryptionMayBeEnabled);
-            }
-        } else if ((record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC)
-                && (handshakeMessageTypeBeforeWhichToInjectCCS != -1)) {
-            // Do not forward original ChangeCipherSpec record(s) if we're injecting such a record
-            // ourselves. This is to make sure that the peer sees only one ChangeCipherSpec.
-            Log.i(TAG, logPrefix + ": Dropping TLS record. "
-                    + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
-            return;
-        }
-
-        // Inject a ChangeCipherSpec, if necessary, before the specified handshake message type
-        if (handshakeMessageTypeBeforeWhichToInjectCCS != -1) {
-            if ((!fragmentEncryptionMayBeEnabled) && (OpenSSLHeartbleedTest.isHandshakeMessageType(
-                    record, handshakeMessageTypeBeforeWhichToInjectCCS))) {
-                Log.i(TAG, logPrefix + ": Injecting ChangeCipherSpec");
-                setCCSWasInjected();
-                out.write(createChangeCipherSpecRecord(record.versionMajor, record.versionMinor));
-                out.flush();
-            }
-        }
-
-        Log.i(TAG, logPrefix + ": Forwarding TLS record. "
-                + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
-        out.write(recordBytes);
-        out.flush();
-    }
-
-    private static String getRecordInfo(TlsRecord record, boolean mayBeEncrypted) {
-        StringBuilder result = new StringBuilder();
-        result.append(getProtocolName(record.protocol))
-                .append(", ")
-                .append(getFragmentInfo(record, mayBeEncrypted));
-        return result.toString();
-    }
-
-    private static String getProtocolName(int protocol) {
-        switch (protocol) {
-            case TlsProtocols.ALERT:
-                return "alert";
-            case TlsProtocols.APPLICATION_DATA:
-                return "application data";
-            case TlsProtocols.CHANGE_CIPHER_SPEC:
-                return "change cipher spec";
-            case TlsProtocols.HANDSHAKE:
-                return "handshake";
-            default:
-                return String.valueOf(protocol);
-        }
-    }
-
-    private static String getFragmentInfo(TlsRecord record, boolean mayBeEncrypted) {
-        StringBuilder result = new StringBuilder();
-        if (mayBeEncrypted) {
-            result.append("encrypted?");
-        } else {
-            switch (record.protocol) {
-                case TlsProtocols.ALERT:
-                    result.append("level: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
-                    + ", description: "
-                    + ((record.fragment.length > 1)
-                            ? String.valueOf(record.fragment[1] & 0xff) : "n/a"));
-                    break;
-                case TlsProtocols.APPLICATION_DATA:
-                    break;
-                case TlsProtocols.CHANGE_CIPHER_SPEC:
-                    result.append("payload: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
-                    break;
-                case TlsProtocols.HANDSHAKE:
-                    result.append("type: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
-                    break;
-            }
-        }
-        result.append(", ").append("fragment length: " + record.fragment.length);
-        return result.toString();
-    }
-
-    private synchronized void setServerListeningSocket(SSLServerSocket socket) {
-        mServerListeningSocket = socket;
-    }
-
-    private synchronized SSLServerSocket getServerListeningSocket() {
-        return mServerListeningSocket;
-    }
-
-    private synchronized void setServerSocket(SSLSocket socket) {
-        mServerSocket = socket;
-    }
-
-    private synchronized SSLSocket getServerSocket() {
-        return mServerSocket;
-    }
-
-    private synchronized void setClientSocket(SSLSocket socket) {
-        mClientSocket = socket;
-    }
-
-    private synchronized SSLSocket getClientSocket() {
-        return mClientSocket;
-    }
-
-    private synchronized void setMitmListeningSocket(ServerSocket socket) {
-        mMitmListeningSocket = socket;
-    }
-
-    private synchronized ServerSocket getMitmListeningSocket() {
-        return mMitmListeningSocket;
-    }
-
-    private synchronized void setMitmServerSocket(Socket socket) {
-        mMitmServerSocket = socket;
-    }
-
-    private synchronized Socket getMitmServerSocket() {
-        return mMitmServerSocket;
-    }
-
-    private synchronized void setMitmClientSocket(Socket socket) {
-        mMitmClientSocket = socket;
-    }
-
-    private synchronized Socket getMitmClientSocket() {
-        return mMitmClientSocket;
-    }
-
-    private synchronized void setCCSWasInjected() {
-        mCCSWasInjected = true;
-    }
-
-    private synchronized boolean wasCCSInjected() {
-        return mCCSWasInjected;
-    }
-
-    private synchronized void setFirstRecordReceivedAfterCCSWasInjected(
-            TlsRecord record, boolean mayBeEncrypted) {
-        if (mFirstRecordReceivedAfterCCSWasInjected == null) {
-            mFirstRecordReceivedAfterCCSWasInjected = record;
-            mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted = mayBeEncrypted;
-        }
-    }
-
-    private synchronized TlsRecord getFirstRecordReceivedAfterCCSWasInjected() {
-        return mFirstRecordReceivedAfterCCSWasInjected;
-    }
-
-    private synchronized boolean getFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted() {
-        return mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted;
-    }
-
-    private static byte[] createChangeCipherSpecRecord(int versionMajor, int versionMinor) {
-        TlsRecord record = new TlsRecord();
-        record.protocol = TlsProtocols.CHANGE_CIPHER_SPEC;
-        record.versionMajor = versionMajor;
-        record.versionMinor = versionMinor;
-        record.fragment = new byte[] {1};
-        return TlsRecord.unparse(record);
-    }
-}
diff --git a/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java b/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java
deleted file mode 100644
index 95a82a8..0000000
--- a/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java
+++ /dev/null
@@ -1,983 +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.security.cts;
-
-import android.content.Context;
-import android.platform.test.annotations.SecurityTest;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.KeyFactory;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ServerSocketFactory;
-import javax.net.SocketFactory;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * Tests for the OpenSSL Heartbleed vulnerability.
- */
-@SecurityTest
-public class OpenSSLHeartbleedTest extends InstrumentationTestCase {
-
-    // IMPLEMENTATION NOTE: This test spawns an SSLSocket client, SSLServerSocket server, and a
-    // Man-in-The-Middle (MiTM). The client connects to the MiTM which then connects to the server
-    // and starts forwarding all TLS records between the client and the server. In tests that check
-    // for the Heartbleed vulnerability, the MiTM also injects a HeartbeatRequest message into the
-    // traffic.
-
-    // IMPLEMENTATION NOTE: This test spawns several background threads that perform network I/O
-    // on localhost. To ensure that these background threads are cleaned up at the end of the test
-    // tearDown() kills the sockets they may be using. To aid this behavior, all Socket and
-    // ServerSocket instances are available as fields of this class. These fields should be accessed
-    // via setters and getters to avoid memory visibility issues due to concurrency.
-
-    private static final String TAG = OpenSSLHeartbleedTest.class.getSimpleName();
-
-    private SSLServerSocket mServerListeningSocket;
-    private SSLSocket mServerSocket;
-    private SSLSocket mClientSocket;
-    private ServerSocket mMitmListeningSocket;
-    private Socket mMitmServerSocket;
-    private Socket mMitmClientSocket;
-    private ExecutorService mExecutorService;
-
-    private boolean mHeartbeatRequestWasInjected;
-    private boolean mHeartbeatResponseWasDetetected;
-    private int mFirstDetectedFatalAlertDescription = -1;
-
-    @Override
-    protected void tearDown() throws Exception {
-        Log.i(TAG, "Tearing down");
-        if (mExecutorService != null) {
-            mExecutorService.shutdownNow();
-        }
-        closeQuietly(getServerListeningSocket());
-        closeQuietly(getServerSocket());
-        closeQuietly(getClientSocket());
-        closeQuietly(getMitmListeningSocket());
-        closeQuietly(getMitmServerSocket());
-        closeQuietly(getMitmClientSocket());
-        super.tearDown();
-        Log.i(TAG, "Tear down completed");
-    }
-
-    /**
-     * Tests that TLS handshake succeeds when the MiTM simply forwards all data without tampering
-     * with it. This is to catch issues unrelated to TLS heartbeats.
-     */
-    public void testWithoutHeartbeats() throws Exception {
-        handshake(false, false);
-    }
-
-    /**
-     * Tests whether client sockets are vulnerable to Heartbleed.
-     */
-    public void testClientHeartbleed() throws Exception {
-        checkHeartbleed(true);
-    }
-
-    /**
-     * Tests whether server sockets are vulnerable to Heartbleed.
-     */
-    public void testServerHeartbleed() throws Exception {
-        checkHeartbleed(false);
-    }
-
-    /**
-     * Tests for Heartbleed.
-     *
-     * @param client {@code true} to test the client, {@code false} to test the server.
-     */
-    private void checkHeartbleed(boolean client) throws Exception {
-        // IMPLEMENTATION NOTE: The MiTM is forwarding all TLS records between the client and the
-        // server unmodified. Additionally, the MiTM transmits a malformed HeartbeatRequest to
-        // server (if "client" argument is false) right after client's ClientKeyExchange or to
-        // client (if "client" argument is true) right after server's ServerHello. The peer is
-        // expected to either ignore the HeartbeatRequest (if heartbeats are supported) or to abort
-        // the handshake with unexpected_message alert (if heartbeats are not supported).
-        try {
-            handshake(true, client);
-        } catch (ExecutionException e) {
-            assertFalse(
-                    "SSLSocket is vulnerable to Heartbleed in " + ((client) ? "client" : "server")
-                            + " mode",
-                    wasHeartbeatResponseDetected());
-            if (e.getCause() instanceof SSLException) {
-                // TLS handshake or data exchange failed. Check whether the error was caused by
-                // fatal alert unexpected_message
-                int alertDescription = getFirstDetectedFatalAlertDescription();
-                if (alertDescription == -1) {
-                    fail("Handshake failed without a fatal alert");
-                }
-                assertEquals(
-                        "First fatal alert description received from server",
-                        AlertMessage.DESCRIPTION_UNEXPECTED_MESSAGE,
-                        alertDescription);
-                return;
-            } else {
-                throw e;
-            }
-        }
-
-        // TLS handshake succeeded
-        assertFalse(
-                "SSLSocket is vulnerable to Heartbleed in " + ((client) ? "client" : "server")
-                        + " mode",
-                wasHeartbeatResponseDetected());
-        assertTrue("HeartbeatRequest not injected", wasHeartbeatRequestInjected());
-    }
-
-    /**
-     * Starts the client, server, and the MiTM. Makes the client and server perform a TLS handshake
-     * and exchange application-level data. The MiTM injects a HeartbeatRequest message if requested
-     * by {@code heartbeatRequestInjected}. The direction of the injected message is specified by
-     * {@code injectedIntoClient}.
-     */
-    private void handshake(
-            final boolean heartbeatRequestInjected,
-            final boolean injectedIntoClient) throws Exception {
-        mExecutorService = Executors.newFixedThreadPool(4);
-        setServerListeningSocket(serverBind());
-        final SocketAddress serverAddress = getServerListeningSocket().getLocalSocketAddress();
-        Log.i(TAG, "Server bound to " + serverAddress);
-
-        setMitmListeningSocket(mitmBind());
-        final SocketAddress mitmAddress = getMitmListeningSocket().getLocalSocketAddress();
-        Log.i(TAG, "MiTM bound to " + mitmAddress);
-
-        // Start the MiTM daemon in the background
-        mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                mitmAcceptAndForward(
-                        serverAddress,
-                        heartbeatRequestInjected,
-                        injectedIntoClient);
-                return null;
-            }
-        });
-        // Start the server in the background
-        Future<Void> serverFuture = mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                serverAcceptAndHandshake();
-                return null;
-            }
-        });
-        // Start the client in the background
-        Future<Void> clientFuture = mExecutorService.submit(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                clientConnectAndHandshake(mitmAddress);
-                return null;
-            }
-        });
-
-        // Wait for both client and server to terminate, to ensure that we observe all the traffic
-        // exchanged between them. Throw an exception if one of them failed.
-        Log.i(TAG, "Waiting for client");
-        // Wait for the client, but don't yet throw an exception if it failed.
-        Exception clientException = null;
-        try {
-            clientFuture.get(10, TimeUnit.SECONDS);
-        } catch (Exception e) {
-            clientException = e;
-        }
-        Log.i(TAG, "Waiting for server");
-        // Wait for the server and throw an exception if it failed.
-        serverFuture.get(5, TimeUnit.SECONDS);
-        // Throw an exception if the client failed.
-        if (clientException != null) {
-            throw clientException;
-        }
-        Log.i(TAG, "Handshake completed and application data exchanged");
-    }
-
-    private void clientConnectAndHandshake(SocketAddress serverAddress) throws Exception {
-        SSLContext sslContext = SSLContext.getInstance("TLS");
-        sslContext.init(
-                null,
-                new TrustManager[] {new TrustAllX509TrustManager()},
-                null);
-        SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
-        setClientSocket(socket);
-        try {
-            Log.i(TAG, "Client connecting to " + serverAddress);
-            socket.connect(serverAddress);
-            Log.i(TAG, "Client connected to server from " + socket.getLocalSocketAddress());
-            // Ensure a TLS handshake is performed and an exception is thrown if it fails.
-            socket.getOutputStream().write("client".getBytes());
-            socket.getOutputStream().flush();
-            Log.i(TAG, "Client sent request. Reading response");
-            int b = socket.getInputStream().read();
-            Log.i(TAG, "Client read response: " + b);
-        } catch (Exception e) {
-            Log.w(TAG, "Client failed", e);
-            throw e;
-          } finally {
-            socket.close();
-        }
-    }
-
-    public SSLServerSocket serverBind() throws Exception {
-        // Load the server's private key and cert chain
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(
-                readResource(
-                        getInstrumentation().getContext(), R.raw.openssl_heartbleed_test_key)));
-        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-        X509Certificate[] certChain =  new X509Certificate[] {
-                (X509Certificate) certFactory.generateCertificate(
-                        new ByteArrayInputStream(readResource(
-                                getInstrumentation().getContext(),
-                                R.raw.openssl_heartbleed_test_cert)))
-        };
-
-        // Initialize TLS context to use the private key and cert chain for server sockets
-        SSLContext sslContext = SSLContext.getInstance("TLS");
-        sslContext.init(
-                new KeyManager[] {new HardcodedCertX509KeyManager(privateKey, certChain)},
-                null,
-                null);
-
-        Log.i(TAG, "Server binding to local port");
-        return (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(0);
-    }
-
-    private void serverAcceptAndHandshake() throws Exception {
-        SSLSocket socket = null;
-        SSLServerSocket serverSocket = getServerListeningSocket();
-        try {
-            Log.i(TAG, "Server listening for incoming connection");
-            socket = (SSLSocket) serverSocket.accept();
-            setServerSocket(socket);
-            Log.i(TAG, "Server accepted connection from " + socket.getRemoteSocketAddress());
-            // Ensure a TLS handshake is performed and an exception is thrown if it fails.
-            socket.getOutputStream().write("server".getBytes());
-            socket.getOutputStream().flush();
-            Log.i(TAG, "Server sent reply. Reading response");
-            int b = socket.getInputStream().read();
-            Log.i(TAG, "Server read response: " + b);
-        } catch (Exception e) {
-          Log.w(TAG, "Server failed", e);
-          throw e;
-        } finally {
-            if (socket != null) {
-                socket.close();
-            }
-        }
-    }
-
-    private ServerSocket mitmBind() throws Exception {
-        Log.i(TAG, "MiTM binding to local port");
-        return ServerSocketFactory.getDefault().createServerSocket(0);
-    }
-
-    /**
-     * Accepts the connection on the MiTM listening socket, forwards the TLS records between the
-     * client and the server, and, if requested, injects a {@code HeartbeatRequest}.
-     *
-     * @param injectHeartbeat whether to inject a {@code HeartbeatRequest} message.
-     * @param injectIntoClient when {@code injectHeartbeat} is {@code true}, whether to inject the
-     *        {@code HeartbeatRequest} message into client or into server.
-     */
-    private void mitmAcceptAndForward(
-            SocketAddress serverAddress,
-            final boolean injectHeartbeat,
-            final boolean injectIntoClient) throws Exception {
-        Socket clientSocket = null;
-        Socket serverSocket = null;
-        ServerSocket listeningSocket = getMitmListeningSocket();
-        try {
-            Log.i(TAG, "MiTM waiting for incoming connection");
-            clientSocket = listeningSocket.accept();
-            setMitmClientSocket(clientSocket);
-            Log.i(TAG, "MiTM accepted connection from " + clientSocket.getRemoteSocketAddress());
-            serverSocket = SocketFactory.getDefault().createSocket();
-            setMitmServerSocket(serverSocket);
-            Log.i(TAG, "MiTM connecting to server " + serverAddress);
-            serverSocket.connect(serverAddress, 10000);
-            Log.i(TAG, "MiTM connected to server from " + serverSocket.getLocalSocketAddress());
-            final InputStream serverInputStream = serverSocket.getInputStream();
-            final OutputStream clientOutputStream = clientSocket.getOutputStream();
-            Future<Void> serverToClientTask = mExecutorService.submit(new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    // Inject HeatbeatRequest after ServerHello, if requested
-                    forwardTlsRecords(
-                            "MiTM S->C",
-                            serverInputStream,
-                            clientOutputStream,
-                            (injectHeartbeat && injectIntoClient)
-                                    ? HandshakeMessage.TYPE_SERVER_HELLO : -1);
-                    return null;
-                }
-            });
-            // Inject HeatbeatRequest after ClientKeyExchange, if requested
-            forwardTlsRecords(
-                    "MiTM C->S",
-                    clientSocket.getInputStream(),
-                    serverSocket.getOutputStream(),
-                    (injectHeartbeat && !injectIntoClient)
-                            ? HandshakeMessage.TYPE_CLIENT_KEY_EXCHANGE : -1);
-            serverToClientTask.get(10, TimeUnit.SECONDS);
-        } catch (Exception e) {
-            Log.w(TAG, "MiTM failed", e);
-            throw e;
-          } finally {
-            closeQuietly(clientSocket);
-            closeQuietly(serverSocket);
-        }
-    }
-
-    /**
-     * Forwards TLS records from the provided {@code InputStream} to the provided
-     * {@code OutputStream}. If requested, injects a {@code HeartbeatMessage}.
-     */
-    private void forwardTlsRecords(
-            String logPrefix,
-            InputStream in,
-            OutputStream out,
-            int handshakeMessageTypeAfterWhichToInjectHeartbeatRequest) throws Exception {
-        Log.i(TAG, logPrefix + ": record forwarding started");
-        boolean interestingRecordsLogged =
-                handshakeMessageTypeAfterWhichToInjectHeartbeatRequest == -1;
-        try {
-            TlsRecordReader reader = new TlsRecordReader(in);
-            byte[] recordBytes;
-            // Fragments contained in records may be encrypted after a certain point in the
-            // handshake. Once they are encrypted, this MiTM cannot inspect their plaintext which.
-            boolean fragmentEncryptionMayBeEnabled = false;
-            while ((recordBytes = reader.readRecord()) != null) {
-                TlsRecord record = TlsRecord.parse(recordBytes);
-                forwardTlsRecord(logPrefix,
-                        recordBytes,
-                        record,
-                        fragmentEncryptionMayBeEnabled,
-                        out,
-                        interestingRecordsLogged,
-                        handshakeMessageTypeAfterWhichToInjectHeartbeatRequest);
-                if (record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC) {
-                    fragmentEncryptionMayBeEnabled = true;
-                }
-            }
-        } catch (Exception e) {
-            Log.w(TAG, logPrefix + ": failed", e);
-            throw e;
-        } finally {
-            Log.d(TAG, logPrefix + ": record forwarding finished");
-        }
-    }
-
-    private void forwardTlsRecord(
-            String logPrefix,
-            byte[] recordBytes,
-            TlsRecord record,
-            boolean fragmentEncryptionMayBeEnabled,
-            OutputStream out,
-            boolean interestingRecordsLogged,
-            int handshakeMessageTypeAfterWhichToInjectHeartbeatRequest) throws IOException {
-        // Save information about the records if its of interest to this test
-        if (interestingRecordsLogged) {
-            switch (record.protocol) {
-                case TlsProtocols.ALERT:
-                    if (!fragmentEncryptionMayBeEnabled) {
-                        AlertMessage alert = AlertMessage.tryParse(record);
-                        if ((alert != null) && (alert.level == AlertMessage.LEVEL_FATAL)) {
-                            setFatalAlertDetected(alert.description);
-                        }
-                    }
-                    break;
-                case TlsProtocols.HEARTBEAT:
-                    // When TLS records are encrypted, we cannot determine whether a
-                    // heartbeat is a HeartbeatResponse. In our setup, the client and the
-                    // server are not expected to sent HeartbeatRequests. Thus, we err on
-                    // the side of caution and assume that any heartbeat message sent by
-                    // client or server is a HeartbeatResponse.
-                    Log.e(TAG, logPrefix
-                            + ": heartbeat response detected -- vulnerable to Heartbleed");
-                    setHeartbeatResponseWasDetected();
-                    break;
-            }
-        }
-
-        Log.i(TAG, logPrefix + ": Forwarding TLS record. "
-                + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
-        out.write(recordBytes);
-        out.flush();
-
-        // Inject HeartbeatRequest, if necessary, after the specified handshake message type
-        if (handshakeMessageTypeAfterWhichToInjectHeartbeatRequest != -1) {
-            if ((!fragmentEncryptionMayBeEnabled) && (isHandshakeMessageType(
-                    record, handshakeMessageTypeAfterWhichToInjectHeartbeatRequest))) {
-                // The Heartbeat Request message below is malformed because its declared
-                // length of payload one byte larger than the actual payload. The peer is
-                // supposed to reject such messages.
-                byte[] payload = "arbitrary".getBytes("US-ASCII");
-                byte[] heartbeatRequestRecordBytes = createHeartbeatRequestRecord(
-                        record.versionMajor,
-                        record.versionMinor,
-                        payload.length + 1,
-                        payload);
-                Log.i(TAG, logPrefix + ": Injecting malformed HeartbeatRequest: "
-                        + getRecordInfo(
-                                TlsRecord.parse(heartbeatRequestRecordBytes), false));
-                setHeartbeatRequestWasInjected();
-                out.write(heartbeatRequestRecordBytes);
-                out.flush();
-            }
-        }
-    }
-
-    private static String getRecordInfo(TlsRecord record, boolean mayBeEncrypted) {
-        StringBuilder result = new StringBuilder();
-        result.append(getProtocolName(record.protocol))
-                .append(", ")
-                .append(getFragmentInfo(record, mayBeEncrypted));
-        return result.toString();
-    }
-
-    private static String getProtocolName(int protocol) {
-        switch (protocol) {
-            case TlsProtocols.ALERT:
-                return "alert";
-            case TlsProtocols.APPLICATION_DATA:
-                return "application data";
-            case TlsProtocols.CHANGE_CIPHER_SPEC:
-                return "change cipher spec";
-            case TlsProtocols.HANDSHAKE:
-                return "handshake";
-            case TlsProtocols.HEARTBEAT:
-                return "heatbeat";
-            default:
-                return String.valueOf(protocol);
-        }
-    }
-
-    private static String getFragmentInfo(TlsRecord record, boolean mayBeEncrypted) {
-        StringBuilder result = new StringBuilder();
-        if (mayBeEncrypted) {
-            result.append("encrypted?");
-        } else {
-            switch (record.protocol) {
-                case TlsProtocols.ALERT:
-                    result.append("level: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
-                    + ", description: "
-                    + ((record.fragment.length > 1)
-                            ? String.valueOf(record.fragment[1] & 0xff) : "n/a"));
-                    break;
-                case TlsProtocols.APPLICATION_DATA:
-                    break;
-                case TlsProtocols.CHANGE_CIPHER_SPEC:
-                    result.append("payload: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
-                    break;
-                case TlsProtocols.HANDSHAKE:
-                    result.append("type: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
-                    break;
-                case TlsProtocols.HEARTBEAT:
-                    result.append("type: " + ((record.fragment.length > 0)
-                            ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
-                            + ", payload length: "
-                            + ((record.fragment.length >= 3)
-                                    ? String.valueOf(
-                                            getUnsignedShortBigEndian(record.fragment, 1))
-                                    : "n/a"));
-                    break;
-            }
-        }
-        result.append(", ").append("fragment length: " + record.fragment.length);
-        return result.toString();
-    }
-
-    private synchronized void setServerListeningSocket(SSLServerSocket socket) {
-        mServerListeningSocket = socket;
-    }
-
-    private synchronized SSLServerSocket getServerListeningSocket() {
-        return mServerListeningSocket;
-    }
-
-    private synchronized void setServerSocket(SSLSocket socket) {
-        mServerSocket = socket;
-    }
-
-    private synchronized SSLSocket getServerSocket() {
-        return mServerSocket;
-    }
-
-    private synchronized void setClientSocket(SSLSocket socket) {
-        mClientSocket = socket;
-    }
-
-    private synchronized SSLSocket getClientSocket() {
-        return mClientSocket;
-    }
-
-    private synchronized void setMitmListeningSocket(ServerSocket socket) {
-        mMitmListeningSocket = socket;
-    }
-
-    private synchronized ServerSocket getMitmListeningSocket() {
-        return mMitmListeningSocket;
-    }
-
-    private synchronized void setMitmServerSocket(Socket socket) {
-        mMitmServerSocket = socket;
-    }
-
-    private synchronized Socket getMitmServerSocket() {
-        return mMitmServerSocket;
-    }
-
-    private synchronized void setMitmClientSocket(Socket socket) {
-        mMitmClientSocket = socket;
-    }
-
-    private synchronized Socket getMitmClientSocket() {
-        return mMitmClientSocket;
-    }
-
-    private synchronized void setHeartbeatRequestWasInjected() {
-        mHeartbeatRequestWasInjected = true;
-    }
-
-    private synchronized boolean wasHeartbeatRequestInjected() {
-        return mHeartbeatRequestWasInjected;
-    }
-
-    private synchronized void setHeartbeatResponseWasDetected() {
-        mHeartbeatResponseWasDetetected = true;
-    }
-
-    private synchronized boolean wasHeartbeatResponseDetected() {
-        return mHeartbeatResponseWasDetetected;
-    }
-
-    private synchronized void setFatalAlertDetected(int description) {
-        if (mFirstDetectedFatalAlertDescription == -1) {
-            mFirstDetectedFatalAlertDescription = description;
-        }
-    }
-
-    private synchronized int getFirstDetectedFatalAlertDescription() {
-        return mFirstDetectedFatalAlertDescription;
-    }
-
-    public static abstract class TlsProtocols {
-        public static final int CHANGE_CIPHER_SPEC = 20;
-        public static final int ALERT = 21;
-        public static final int HANDSHAKE = 22;
-        public static final int APPLICATION_DATA = 23;
-        public static final int HEARTBEAT = 24;
-        private TlsProtocols() {}
-    }
-
-    public static class TlsRecord {
-        public int protocol;
-        public int versionMajor;
-        public int versionMinor;
-        public byte[] fragment;
-
-        public static TlsRecord parse(byte[] record) throws IOException {
-            TlsRecord result = new TlsRecord();
-            if (record.length < TlsRecordReader.RECORD_HEADER_LENGTH) {
-                throw new IOException("Record too short: " + record.length);
-            }
-            result.protocol = record[0] & 0xff;
-            result.versionMajor = record[1] & 0xff;
-            result.versionMinor = record[2] & 0xff;
-            int fragmentLength = getUnsignedShortBigEndian(record, 3);
-            int actualFragmentLength = record.length - TlsRecordReader.RECORD_HEADER_LENGTH;
-            if (fragmentLength != actualFragmentLength) {
-                throw new IOException("Fragment length mismatch. Expected: " + fragmentLength
-                        + ", actual: " + actualFragmentLength);
-            }
-            result.fragment = new byte[fragmentLength];
-            System.arraycopy(
-                    record, TlsRecordReader.RECORD_HEADER_LENGTH,
-                    result.fragment, 0,
-                    fragmentLength);
-            return result;
-        }
-
-        public static byte[] unparse(TlsRecord record) {
-            byte[] result = new byte[TlsRecordReader.RECORD_HEADER_LENGTH + record.fragment.length];
-            result[0] = (byte) record.protocol;
-            result[1] = (byte) record.versionMajor;
-            result[2] = (byte) record.versionMinor;
-            putUnsignedShortBigEndian(result, 3, record.fragment.length);
-            System.arraycopy(
-                    record.fragment, 0,
-                    result, TlsRecordReader.RECORD_HEADER_LENGTH,
-                    record.fragment.length);
-            return result;
-        }
-    }
-
-    public static final boolean isHandshakeMessageType(TlsRecord record, int type) {
-        HandshakeMessage handshake = HandshakeMessage.tryParse(record);
-        if (handshake == null) {
-            return false;
-        }
-        return handshake.type == type;
-    }
-
-    public static class HandshakeMessage {
-        public static final int TYPE_SERVER_HELLO = 2;
-        public static final int TYPE_CERTIFICATE = 11;
-        public static final int TYPE_CLIENT_KEY_EXCHANGE = 16;
-
-        public int type;
-
-        /**
-         * Parses the provided TLS record as a handshake message.
-         *
-         * @return alert message or {@code null} if the record does not contain a handshake message.
-         */
-        public static HandshakeMessage tryParse(TlsRecord record) {
-            if (record.protocol != TlsProtocols.HANDSHAKE) {
-                return null;
-            }
-            if (record.fragment.length < 1) {
-                return null;
-            }
-            HandshakeMessage result = new HandshakeMessage();
-            result.type = record.fragment[0] & 0xff;
-            return result;
-        }
-    }
-
-    public static class AlertMessage {
-        public static final int LEVEL_FATAL = 2;
-        public static final int DESCRIPTION_UNEXPECTED_MESSAGE = 10;
-
-        public int level;
-        public int description;
-
-        /**
-         * Parses the provided TLS record as an alert message.
-         *
-         * @return alert message or {@code null} if the record does not contain an alert message.
-         */
-        public static AlertMessage tryParse(TlsRecord record) {
-            if (record.protocol != TlsProtocols.ALERT) {
-                return null;
-            }
-            if (record.fragment.length < 2) {
-                return null;
-            }
-            AlertMessage result = new AlertMessage();
-            result.level = record.fragment[0] & 0xff;
-            result.description = record.fragment[1] & 0xff;
-            return result;
-        }
-    }
-
-    private static abstract class HeartbeatProtocol {
-        private HeartbeatProtocol() {}
-
-        private static final int MESSAGE_TYPE_REQUEST = 1;
-        @SuppressWarnings("unused")
-        private static final int MESSAGE_TYPE_RESPONSE = 2;
-
-        private static final int MESSAGE_HEADER_LENGTH = 3;
-        private static final int MESSAGE_PADDING_LENGTH = 16;
-    }
-
-    private static byte[] createHeartbeatRequestRecord(
-            int versionMajor, int versionMinor,
-            int declaredPayloadLength, byte[] payload) {
-
-        byte[] fragment = new byte[HeartbeatProtocol.MESSAGE_HEADER_LENGTH
-                + payload.length + HeartbeatProtocol.MESSAGE_PADDING_LENGTH];
-        fragment[0] = HeartbeatProtocol.MESSAGE_TYPE_REQUEST;
-        putUnsignedShortBigEndian(fragment, 1, declaredPayloadLength); // payload_length
-        TlsRecord record = new TlsRecord();
-        record.protocol = TlsProtocols.HEARTBEAT;
-        record.versionMajor = versionMajor;
-        record.versionMinor = versionMinor;
-        record.fragment = fragment;
-        return TlsRecord.unparse(record);
-    }
-
-    /**
-     * Reader of TLS records.
-     */
-    public static class TlsRecordReader {
-        private static final int MAX_RECORD_LENGTH = 16384;
-        public static final int RECORD_HEADER_LENGTH = 5;
-
-        private final InputStream in;
-        private final byte[] buffer;
-        private int firstBufferedByteOffset;
-        private int bufferedByteCount;
-
-        public TlsRecordReader(InputStream in) {
-            this.in = in;
-            buffer = new byte[MAX_RECORD_LENGTH];
-        }
-
-        /**
-         * Reads the next TLS record.
-         *
-         * @return TLS record or {@code null} if EOF was encountered before any bytes of a record
-         *         could be read.
-         */
-        public byte[] readRecord() throws IOException {
-            // Ensure that a TLS record header (or more) is in the buffer.
-            if (bufferedByteCount < RECORD_HEADER_LENGTH) {
-                boolean eofPermittedInstead = (bufferedByteCount == 0);
-                boolean eofEncounteredInstead =
-                        !readAtLeast(RECORD_HEADER_LENGTH, eofPermittedInstead);
-                if (eofEncounteredInstead) {
-                    // End of stream reached exactly before a TLS record start.
-                    return null;
-                }
-            }
-
-            // TLS record header (or more) is in the buffer.
-            // Ensure that the rest of the record is in the buffer.
-            int fragmentLength = getUnsignedShortBigEndian(buffer, firstBufferedByteOffset + 3);
-            int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
-            if (recordLength > MAX_RECORD_LENGTH) {
-                throw new IOException("TLS record too long: " + recordLength);
-            }
-            if (bufferedByteCount < recordLength) {
-                readAtLeast(recordLength - bufferedByteCount, false);
-            }
-
-            // TLS record (or more) is in the buffer.
-            byte[] record = new byte[recordLength];
-            System.arraycopy(buffer, firstBufferedByteOffset, record, 0, recordLength);
-            firstBufferedByteOffset += recordLength;
-            bufferedByteCount -= recordLength;
-            return record;
-        }
-
-        /**
-         * Reads at least the specified number of bytes from the underlying {@code InputStream} into
-         * the {@code buffer}.
-         *
-         * <p>Bytes buffered but not yet returned to the client in the {@code buffer} are relocated
-         * to the start of the buffer to make space if necessary.
-         *
-         * @param eofPermittedInstead {@code true} if it's permitted for an EOF to be encountered
-         *        without any bytes having been read.
-         *
-         * @return {@code true} if the requested number of bytes (or more) has been read,
-         *         {@code false} if {@code eofPermittedInstead} was {@code true} and EOF was
-         *         encountered when no bytes have yet been read.
-         */
-        private boolean readAtLeast(int size, boolean eofPermittedInstead) throws IOException {
-            ensureRemainingBufferCapacityAtLeast(size);
-            boolean firstAttempt = true;
-            while (size > 0) {
-                int chunkSize = in.read(
-                        buffer,
-                        firstBufferedByteOffset + bufferedByteCount,
-                        buffer.length - (firstBufferedByteOffset + bufferedByteCount));
-                if (chunkSize == -1) {
-                    if ((firstAttempt) && (eofPermittedInstead)) {
-                        return false;
-                    } else {
-                        throw new EOFException("Premature EOF");
-                    }
-                }
-                firstAttempt = false;
-                bufferedByteCount += chunkSize;
-                size -= chunkSize;
-            }
-            return true;
-        }
-
-        /**
-         * Ensures that there is enough capacity in the buffer to store the specified number of
-         * bytes at the {@code firstBufferedByteOffset + bufferedByteCount} offset.
-         */
-        private void ensureRemainingBufferCapacityAtLeast(int size) throws IOException {
-            int bufferCapacityRemaining =
-                    buffer.length - (firstBufferedByteOffset + bufferedByteCount);
-            if (bufferCapacityRemaining >= size) {
-                return;
-            }
-            // Insufficient capacity at the end of the buffer.
-            if (firstBufferedByteOffset > 0) {
-                // Some of the bytes at the start of the buffer have already been returned to the
-                // client of this reader. Check if moving the remaining buffered bytes to the start
-                // of the buffer will make enough space at the end of the buffer.
-                bufferCapacityRemaining += firstBufferedByteOffset;
-                if (bufferCapacityRemaining >= size) {
-                    System.arraycopy(buffer, firstBufferedByteOffset, buffer, 0, bufferedByteCount);
-                    firstBufferedByteOffset = 0;
-                    return;
-                }
-            }
-
-            throw new IOException("Insuffucient remaining capacity in the buffer. Requested: "
-                    + size + ", remaining: " + bufferCapacityRemaining);
-        }
-    }
-
-    private static int getUnsignedShortBigEndian(byte[] buf, int offset) {
-        return ((buf[offset] & 0xff) << 8) | (buf[offset + 1] & 0xff);
-    }
-
-    private static void putUnsignedShortBigEndian(byte[] buf, int offset, int value) {
-        buf[offset] = (byte) ((value >>> 8) & 0xff);
-        buf[offset + 1] = (byte) (value & 0xff);
-    }
-
-    // IMPLEMENTATION NOTE: We can't implement just one closeQueietly(Closeable) because on some
-    // older Android platforms Socket did not implement these interfaces. To make this patch easy to
-    // apply to these older platforms, we declare all the variants of closeQuietly that are needed
-    // without relying on the Closeable interface.
-
-    private static void closeQuietly(InputStream in) {
-        if (in != null) {
-            try {
-                in.close();
-            } catch (IOException ignored) {}
-        }
-    }
-
-    public static void closeQuietly(ServerSocket socket) {
-        if (socket != null) {
-            try {
-                socket.close();
-            } catch (IOException ignored) {}
-        }
-    }
-
-    public static void closeQuietly(Socket socket) {
-        if (socket != null) {
-            try {
-                socket.close();
-            } catch (IOException ignored) {}
-        }
-    }
-
-    public static byte[] readResource(Context context, int resId) throws IOException {
-        ByteArrayOutputStream result = new ByteArrayOutputStream();
-        InputStream in = null;
-        byte[] buf = new byte[16 * 1024];
-        try {
-            in = context.getResources().openRawResource(resId);
-            int chunkSize;
-            while ((chunkSize = in.read(buf)) != -1) {
-                result.write(buf, 0, chunkSize);
-            }
-            return result.toByteArray();
-        } finally {
-            closeQuietly(in);
-        }
-    }
-
-    /**
-     * {@link X509TrustManager} which trusts all certificate chains.
-     */
-    public static class TrustAllX509TrustManager implements X509TrustManager {
-        @Override
-        public void checkClientTrusted(X509Certificate[] chain, String authType)
-                throws CertificateException {
-        }
-
-        @Override
-        public void checkServerTrusted(X509Certificate[] chain, String authType)
-                throws CertificateException {
-        }
-
-        @Override
-        public X509Certificate[] getAcceptedIssuers() {
-            return new X509Certificate[0];
-        }
-    }
-
-    /**
-     * {@link X509KeyManager} which uses the provided private key and cert chain for all sockets.
-     */
-    public static class HardcodedCertX509KeyManager implements X509KeyManager {
-
-        private final PrivateKey mPrivateKey;
-        private final X509Certificate[] mCertChain;
-
-        HardcodedCertX509KeyManager(PrivateKey privateKey, X509Certificate[] certChain) {
-            mPrivateKey = privateKey;
-            mCertChain = certChain;
-        }
-
-        @Override
-        public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
-            return null;
-        }
-
-        @Override
-        public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
-            return "singleton";
-        }
-
-        @Override
-        public X509Certificate[] getCertificateChain(String alias) {
-            return mCertChain;
-        }
-
-        @Override
-        public String[] getClientAliases(String keyType, Principal[] issuers) {
-            return null;
-        }
-
-        @Override
-        public PrivateKey getPrivateKey(String alias) {
-            return mPrivateKey;
-        }
-
-        @Override
-        public String[] getServerAliases(String keyType, Principal[] issuers) {
-            return new String[] {"singleton"};
-        }
-    }
-}
diff --git a/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
index eb5c5ef..d1b263f 100644
--- a/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
+++ b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
diff --git a/tests/tests/security/src/android/security/cts/SeccompTest.java b/tests/tests/security/src/android/security/cts/SeccompTest.java
deleted file mode 100644
index 745aa87..0000000
--- a/tests/tests/security/src/android/security/cts/SeccompTest.java
+++ /dev/null
@@ -1,103 +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.security.cts;
-
-import android.test.AndroidTestCase;
-
-import com.android.compatibility.common.util.CpuFeatures;
-
-import junit.framework.TestCase;
-
-/**
- * Verify that the seccomp policy is enforced
- */
-public class SeccompTest extends AndroidTestCase {
-
-    static {
-        System.loadLibrary("ctssecurity_jni");
-    }
-
-    public void testCTSSyscallBlocked() {
-        if (CpuFeatures.isArm64Cpu()) {
-            testBlocked(217); // __NR_add_key
-            testBlocked(219); // __NR_keyctl
-            testAllowed(56); // __NR_openat
-
-            // b/35034743 - do not remove test without reading bug
-            testAllowed(267); // __NR_fstatfs64
-        } else if (CpuFeatures.isArmCpu()) {
-            testBlocked(309); // __NR_add_key
-            testBlocked(311); // __NR_keyctl
-            testAllowed(322); // __NR_openat
-
-            // b/35906875 - do not remove test without reading bug
-            testAllowed(316); // __NR_inotify_init
-        } else if (CpuFeatures.isX86_64Cpu()) {
-            testBlocked(248); // __NR_add_key
-            testBlocked(250); // __NR_keyctl
-            testAllowed(257); // __NR_openat
-        } else if (CpuFeatures.isX86Cpu()) {
-            testBlocked(286); // __NR_add_key
-            testBlocked(288); // __NR_keyctl
-            testAllowed(295); // __NR_openat
-        } else if (CpuFeatures.isMips64Cpu()) {
-            testBlocked(5239); // __NR_add_key
-            testBlocked(5241); // __NR_keyctl
-            testAllowed(5247); // __NR_openat
-        } else if (CpuFeatures.isMipsCpu()) {
-            testBlocked(4280); // __NR_add_key
-            testBlocked(4282); // __NR_keyctl
-            testAllowed(4288); // __NR_openat
-        } else {
-            fail("Unsupported OS");
-        }
-    }
-
-    public void testCTSSwapOnOffBlocked() {
-        if (CpuFeatures.isArm64Cpu()) {
-            testBlocked(224); // __NR_swapon
-            testBlocked(225); // __NR_swapoff
-        } else if (CpuFeatures.isArmCpu()) {
-            testBlocked(87);  // __NR_swapon
-            testBlocked(115); // __NR_swapoff
-        } else if (CpuFeatures.isX86_64Cpu()) {
-            testBlocked(167); // __NR_swapon
-            testBlocked(168); // __NR_swapoff
-        } else if (CpuFeatures.isX86Cpu()) {
-            testBlocked(87);  // __NR_swapon
-            testBlocked(115); // __NR_swapoff
-        } else if (CpuFeatures.isMips64Cpu()) {
-            testBlocked(5162); // __NR_swapon
-            testBlocked(5163); // __NR_swapoff
-        } else if (CpuFeatures.isMipsCpu()) {
-            testBlocked(4087); // __NR_swapon
-            testBlocked(4115); // __NR_swapoff
-        } else {
-            fail("Unsupported OS");
-        }
-    }
-
-    private void testBlocked(int nr) {
-        assertTrue("Syscall " + nr + " allowed", testSyscallBlocked(nr));
-    }
-
-    private void testAllowed(int nr) {
-        assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr));
-    }
-
-    private static final native boolean testSyscallBlocked(int nr);
-}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 236b4a5..c4909dd 100755
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -45,8 +45,11 @@
 import android.view.Surface;
 import android.webkit.cts.CtsTestServer;
 
+import java.io.BufferedInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
 import java.nio.ByteBuffer;
 import java.io.FileOutputStream;
 import java.io.File;
@@ -572,12 +575,38 @@
         CtsTestServer server = new CtsTestServer(context);
         String rname = resources.getResourceEntryName(rid);
         String url = server.getAssetUrl("raw/" + rname);
+        verifyServer(rid, url);
         doStagefrightTestMediaPlayer(url);
         doStagefrightTestMediaCodec(url);
         doStagefrightTestMediaMetadataRetriever(url);
         server.shutdown();
     }
 
+    // verify that CtsTestServer is functional by retrieving the asset
+    // and comparing it to the resource
+    private void verifyServer(final int rid, final String uri) throws Exception {
+        Log.i(TAG, "checking server");
+        URL url = new URL(uri);
+        InputStream in1 = new BufferedInputStream(url.openStream());
+
+        AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
+                        .openRawResourceFd(rid);
+        InputStream in2 = new BufferedInputStream(fd.createInputStream());
+
+        while (true) {
+            int b1 = in1.read();
+            int b2 = in2.read();
+            assertEquals("CtsTestServer fail", b1, b2);
+            if (b1 < 0) {
+                break;
+            }
+        }
+
+        in1.close();
+        in2.close();
+        Log.i(TAG, "checked server");
+    }
+  
     private void doStagefrightTest(final int rid, int timeout) throws Exception {
         runWithTimeout(new Runnable() {
             @Override
@@ -1042,13 +1071,13 @@
     }
 
     @SecurityTest
-    public void testBug_70897394() throws Exception {
-        doStagefrightTestRawBlob(R.raw.bug_70897394_avc, "video/avc", 320, 240);
+    public void testBug_37712181() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_37712181_hevc, "video/hevc", 320, 240);
     }
 
     @SecurityTest
-    public void testBug_37712181() throws Exception {
-        doStagefrightTestRawBlob(R.raw.bug_37712181_hevc, "video/hevc", 320, 240);
+    public void testBug_70897394() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_70897394_avc, "video/avc", 320, 240);
     }
 
     private void runWithTimeout(Runnable runner, int timeout) {
diff --git a/tests/tests/selinux/Android.mk b/tests/tests/selinux/Android.mk
index b798d87..cddf11e 100644
--- a/tests/tests/selinux/Android.mk
+++ b/tests/tests/selinux/Android.mk
@@ -12,4 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-include $(call all-subdir-makefiles)
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/common/Android.mk b/tests/tests/selinux/common/Android.mk
new file mode 100644
index 0000000..a177b5c
--- /dev/null
+++ b/tests/tests/selinux/common/Android.mk
@@ -0,0 +1,18 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/tests/tests/selinux/common/jni/Android.mk b/tests/tests/selinux/common/jni/Android.mk
new file mode 100644
index 0000000..8f9234c
--- /dev/null
+++ b/tests/tests/selinux/common/jni/Android.mk
@@ -0,0 +1,37 @@
+# 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 := libctsselinux_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+    CtsSecurityJniOnLoad.cpp \
+    android_security_SELinuxTargetSdkTest.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+    libc++ \
+    libcrypto \
+    liblog \
+    libnativehelper \
+    libpackagelistparser \
+    libpcre2 \
+    libselinux \
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/selinux/common/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/selinux/common/jni/CtsSecurityJniOnLoad.cpp
new file mode 100644
index 0000000..26dfc76
--- /dev/null
+++ b/tests/tests/selinux/common/jni/CtsSecurityJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_security_SELinuxTargetSdkTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved __attribute__((unused))) {
+    JNIEnv *env = NULL;
+
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+
+    if (register_android_security_SELinuxTargetSdkTest(env)) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp b/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp
new file mode 100644
index 0000000..0bf56aa
--- /dev/null
+++ b/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <selinux/selinux.h>
+
+#include <memory>
+
+struct SecurityContext_Delete {
+    void operator()(security_context_t p) const {
+        freecon(p);
+    }
+};
+typedef std::unique_ptr<char[], SecurityContext_Delete> Unique_SecurityContext;
+
+/*
+ * Function: getFileContext
+ * Purpose: retrieves the context associated with the given path in the file system
+ * Parameters:
+ *        path: given path in the file system
+ * Returns:
+ *        string representing the security context string of the file object
+ *        the string may be NULL if an error occured
+ * Exceptions: NullPointerException if the path object is null
+ */
+static jstring getFileContext(JNIEnv *env, jobject, jstring pathStr) {
+    ScopedUtfChars path(env, pathStr);
+    if (path.c_str() == NULL) {
+        return NULL;
+    }
+
+    security_context_t tmp = NULL;
+    int ret = getfilecon(path.c_str(), &tmp);
+    Unique_SecurityContext context(tmp);
+
+    ScopedLocalRef<jstring> securityString(env, NULL);
+    if (ret != -1) {
+        securityString.reset(env->NewStringUTF(context.get()));
+    }
+
+    return securityString.release();
+}
+
+static JNINativeMethod gMethods[] = {
+    { "getFileContext", "(Ljava/lang/String;)Ljava/lang/String;",
+            (void*) getFileContext },
+};
+
+int register_android_security_SELinuxTargetSdkTest(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/security/SELinuxTargetSdkTestBase");
+
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
new file mode 100644
index 0000000..0de87c6
--- /dev/null
+++ b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
@@ -0,0 +1,85 @@
+package android.security;
+
+import android.test.AndroidTestCase;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Scanner;
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+abstract class SELinuxTargetSdkTestBase extends AndroidTestCase
+{
+    static {
+        System.loadLibrary("ctsselinux_jni");
+    }
+
+    protected static String getFile(String filename) throws IOException {
+        BufferedReader in = null;
+        try {
+            in = new BufferedReader(new FileReader(filename));
+            return in.readLine().trim();
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    protected static String getProperty(String property)
+            throws IOException {
+        Process process = new ProcessBuilder("getprop", property).start();
+        Scanner scanner = null;
+        String line = "";
+        try {
+            scanner = new Scanner(process.getInputStream());
+            line = scanner.nextLine();
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        return line;
+    }
+
+    /**
+     * Verify that net.dns properties may not be read
+     */
+    protected static void noDns() throws IOException {
+        String[] dnsProps = {"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
+        for(int i = 0; i < dnsProps.length; i++) {
+            String dns = getProperty(dnsProps[i]);
+            assertEquals("DNS properties may not be readable by apps past " +
+                    "targetSdkVersion 26", dns, "");
+        }
+    }
+
+    /**
+     * Verify that selinux context is the expected domain based on
+     * targetSdkVersion,
+     */
+    protected void appDomainContext(String contextRegex, String errorMsg) throws IOException {
+        Pattern p = Pattern.compile(contextRegex);
+        Matcher m = p.matcher(getFile("/proc/self/attr/current"));
+        String context = getFile("/proc/self/attr/current");
+        String msg = errorMsg + context;
+        assertTrue(msg, m.matches());
+    }
+
+    /**
+     * Verify that selinux context is the expected type based on
+     * targetSdkVersion,
+     */
+    protected void appDataContext(String contextRegex, String errorMsg) throws Exception {
+        Pattern p = Pattern.compile(contextRegex);
+        File appDataDir = getContext().getFilesDir();
+        Matcher m = p.matcher(getFileContext(appDataDir.getAbsolutePath()));
+        String context = getFileContext(appDataDir.getAbsolutePath());
+        String msg = errorMsg + context;
+        assertTrue(msg, m.matches());
+    }
+
+    private static final native String getFileContext(String path);
+}
diff --git a/tests/tests/selinux/selinuxTargetSdk/Android.mk b/tests/tests/selinux/selinuxTargetSdk/Android.mk
deleted file mode 100755
index a0923ee..0000000
--- a/tests/tests/selinux/selinuxTargetSdk/Android.mk
+++ /dev/null
@@ -1,30 +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_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util \
-    ctstestrunner
-LOCAL_JAVA_LIBRARIES := legacy-android-test
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdkTestCases
-LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdk/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdk/AndroidManifest.xml
deleted file mode 100755
index 41bcaca..0000000
--- a/tests/tests/selinux/selinuxTargetSdk/AndroidManifest.xml
+++ /dev/null
@@ -1,37 +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.selinuxtargetsdk.cts">
-
-    <!-- This app tests that apps with targetSdkValue<=25 are placed in the
-         untrusted_app_25 selinux domain -->
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.selinuxtargetsdk.cts"
-                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/selinux/selinuxTargetSdk/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdk/AndroidTest.xml
deleted file mode 100644
index 5f88bb1..0000000
--- a/tests/tests/selinux/selinuxTargetSdk/AndroidTest.xml
+++ /dev/null
@@ -1,28 +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 Permission Selinux test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="security" />
-    <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="CtsSelinuxTargetSdkTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.selinuxtargetsdk.cts" />
-        <option name="runtime-hint" value="2m" />
-    </test>
-</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdk/src/android/selinuxtargetsdk/cts/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk/src/android/selinuxtargetsdk/cts/SELinuxTargetSdkTest.java
deleted file mode 100644
index 5a2e24e..0000000
--- a/tests/tests/selinux/selinuxTargetSdk/src/android/selinuxtargetsdk/cts/SELinuxTargetSdkTest.java
+++ /dev/null
@@ -1,57 +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.selinuxtargetsdk.cts;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-
-/**
- * Verify the selinux domain for apps running with targetSdkVersion<=25
- */
-public class SELinuxTargetSdkTest extends AndroidTestCase
-{
-    static String getFile(String filename) throws IOException {
-        BufferedReader in = null;
-        try {
-            in = new BufferedReader(new FileReader(filename));
-            return in.readLine().trim();
-        } finally {
-            if (in != null) {
-                in.close();
-            }
-        }
-    }
-
-    /**
-     * Verify that selinux context is the expected domain based on
-     * targetSdkVersion,
-     */
-    @SmallTest
-    public void testTargetSdkValue() throws IOException {
-        String context = getFile("/proc/self/attr/current");
-        String expected = "u:r:untrusted_app_25:s0";
-        assertEquals("Untrusted apps with targetSdkVersion<=25 " +
-                "must run in the untrusted_app_25 selinux domain.",
-                context.substring(0, expected.length()),
-                expected);
-    }
-
-}
diff --git a/tests/tests/selinux/selinuxTargetSdk2/Android.mk b/tests/tests/selinux/selinuxTargetSdk2/Android.mk
deleted file mode 100755
index f475f9f..0000000
--- a/tests/tests/selinux/selinuxTargetSdk2/Android.mk
+++ /dev/null
@@ -1,30 +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_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util \
-    ctstestrunner \
-    legacy-android-test
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk2TestCases
-LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdk2/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdk2/AndroidManifest.xml
deleted file mode 100755
index cde1249..0000000
--- a/tests/tests/selinux/selinuxTargetSdk2/AndroidManifest.xml
+++ /dev/null
@@ -1,37 +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.selinuxtargetsdk2.cts">
-
-    <!-- This app tests that apps with current targetSdkValue are placed in the
-         untrusted_app selinux domain -->
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="10000" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.selinuxtargetsdk2.cts"
-                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/selinux/selinuxTargetSdk2/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdk2/AndroidTest.xml
deleted file mode 100644
index 4b278b2..0000000
--- a/tests/tests/selinux/selinuxTargetSdk2/AndroidTest.xml
+++ /dev/null
@@ -1,28 +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 Permission Selinux test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="security" />
-    <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="CtsSelinuxTargetSdk2TestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.selinuxtargetsdk2.cts" />
-        <option name="runtime-hint" value="2m" />
-    </test>
-</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdk2/src/android/selinuxtargetsdk2/cts/SELinuxTargetSdk2Test.java b/tests/tests/selinux/selinuxTargetSdk2/src/android/selinuxtargetsdk2/cts/SELinuxTargetSdk2Test.java
deleted file mode 100644
index 6c480e5..0000000
--- a/tests/tests/selinux/selinuxTargetSdk2/src/android/selinuxtargetsdk2/cts/SELinuxTargetSdk2Test.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.selinuxtargetsdk2.cts;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.Scanner;
-
-/**
- * Verify the selinux domain for apps running with current targetSdkVersion
- */
-public class SELinuxTargetSdk2Test extends AndroidTestCase
-{
-    static String getFile(String filename) throws IOException {
-        BufferedReader in = null;
-        try {
-            in = new BufferedReader(new FileReader(filename));
-            return in.readLine().trim();
-        } finally {
-            if (in != null) {
-                in.close();
-            }
-        }
-    }
-
-    private static String getProperty(String property)
-            throws IOException {
-        Process process = new ProcessBuilder("getprop", property).start();
-        Scanner scanner = null;
-        String line = "";
-        try {
-            scanner = new Scanner(process.getInputStream());
-            line = scanner.nextLine();
-        } finally {
-            if (scanner != null) {
-                scanner.close();
-            }
-        }
-        return line;
-    }
-
-    /**
-     * Verify that net.dns properties may not be read
-     */
-    @SmallTest
-    public void testNoDns() throws IOException {
-        String[] dnsProps = {"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
-        for(int i = 0; i < dnsProps.length; i++) {
-            String dns = getProperty(dnsProps[i]);
-            assertEquals("DNS properties may not be readable by apps past " +
-                    "targetSdkVersion 26", dns, "");
-        }
-    }
-
-    /**
-     * Verify that selinux context is the expected domain based on
-     * targetSdkVersion,
-     */
-    @SmallTest
-    public void testTargetSdkValue() throws IOException {
-        String context = getFile("/proc/self/attr/current");
-        String expected = "u:r:untrusted_app:s0";
-        assertEquals("Untrusted apps with current targetSdkVersion " +
-                "must run in the untrusted_app selinux domain.",
-                context.substring(0, expected.length()),
-                expected);
-    }
-}
diff --git a/tests/tests/selinux/selinuxTargetSdk25/Android.mk b/tests/tests/selinux/selinuxTargetSdk25/Android.mk
new file mode 100755
index 0000000..15ec6e2
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk25/Android.mk
@@ -0,0 +1,45 @@
+#
+# 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_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libc++ \
+    libcrypto \
+    libcts_jni \
+    libctsselinux_jni \
+    libnativehelper \
+    libnativehelper_compat_libc++ \
+    libpackagelistparser \
+    libpcre2 \
+    libselinux \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src common)
+LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk25TestCases
+LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdk25/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdk25/AndroidManifest.xml
new file mode 100755
index 0000000..cac36b9
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk25/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.selinuxtargetsdk25.cts">
+
+    <!-- This app tests that apps with targetSdkValue<=25 are placed in the
+         untrusted_app_25 selinux domain -->
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.selinuxtargetsdk25.cts"
+                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/selinux/selinuxTargetSdk25/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdk25/AndroidTest.xml
new file mode 100644
index 0000000..d63b2c2
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk25/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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 Permission Selinux test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <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="CtsSelinuxTargetSdk25TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.selinuxtargetsdk25.cts" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdk25/common b/tests/tests/selinux/selinuxTargetSdk25/common
new file mode 120000
index 0000000..581eb17
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk25/common
@@ -0,0 +1 @@
+../common/src
\ No newline at end of file
diff --git a/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java
new file mode 100644
index 0000000..c966e5e
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.security;
+
+import android.test.AndroidTestCase;
+import java.io.IOException;
+
+/**
+ * Verify the selinux domain for apps running with targetSdkVersion<=25
+ */
+public class SELinuxTargetSdkTest extends SELinuxTargetSdkTestBase
+{
+    /**
+     * Verify that selinux context is the expected domain based on
+     * targetSdkVersion = 25
+     */
+    public void testAppDomainContext() throws IOException {
+        String context = "u:r:untrusted_app_25:s0:c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion of 25 and below " +
+            "must run in the untrusted_app_25 selinux domain and use the levelFrom=user " +
+            "selector in SELinux seapp_contexts which adds two category types " +
+            "to the app's selinux context.\n" +
+            "Example expected value: u:r:untrusted_app_25:s0:c512,c768\n" +
+            "Actual value: ";
+        appDomainContext(context, msg);
+    }
+
+    /**
+     * Verify that selinux context is the expected type based on
+     * targetSdkVersion = current
+     */
+    public void testAppDataContext() throws Exception {
+        String context = "u:object_r:app_data_file:s0:c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion of 25 and below and above" +
+            "must use the app_data_file selinux context and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app_data_file context.\n" +
+            "Example expected value: u:object_r:app_data_file:s0:c512,c768\n" +
+            "Actual value: ";
+        appDataContext(context, msg);
+    }
+}
diff --git a/tests/tests/selinux/selinuxTargetSdk27/Android.mk b/tests/tests/selinux/selinuxTargetSdk27/Android.mk
new file mode 100755
index 0000000..e0e493e
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk27/Android.mk
@@ -0,0 +1,45 @@
+#
+# 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_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libc++ \
+    libcrypto \
+    libcts_jni \
+    libctsselinux_jni \
+    libnativehelper \
+    libnativehelper_compat_libc++ \
+    libpackagelistparser \
+    libpcre2 \
+    libselinux \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src common)
+LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk27TestCases
+LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdk27/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdk27/AndroidManifest.xml
new file mode 100755
index 0000000..73b5c2b
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk27/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.selinuxtargetsdk27.cts">
+
+    <!-- This app tests that apps with 25<targetSdkValue<=27 are placed in the
+         untrusted_app_27 selinux domain -->
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.selinuxtargetsdk27.cts"
+                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/selinux/selinuxTargetSdk27/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdk27/AndroidTest.xml
new file mode 100644
index 0000000..e818dcc
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk27/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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 Permission Selinux test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <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="CtsSelinuxTargetSdk27TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.selinuxtargetsdk27.cts" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdk27/common b/tests/tests/selinux/selinuxTargetSdk27/common
new file mode 120000
index 0000000..581eb17
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk27/common
@@ -0,0 +1 @@
+../common/src
\ No newline at end of file
diff --git a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
new file mode 100644
index 0000000..cc394e2
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.security;
+
+import android.test.AndroidTestCase;
+import java.io.IOException;
+
+/**
+ * Verify the selinux domain for apps running with 25<targetSdkVersion<=27
+ */
+public class SELinuxTargetSdkTest extends SELinuxTargetSdkTestBase
+{
+    /**
+     * Verify that net.dns properties may not be read
+     */
+    public void testNoDns() throws IOException {
+        noDns();
+    }
+
+    /**
+     * Verify that selinux context is the expected domain based on
+     * targetSdkVersion = 27
+     */
+    public void testAppDomainContext() throws IOException {
+        String context = "u:r:untrusted_app_27:s0:c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion in range 26-27 " +
+            "must run in the untrusted_app_27 selinux domain and use the levelFrom=user " +
+            "selector in SELinux seapp_contexts which adds two category types " +
+            "to the app's selinux context.\n" +
+            "Example expected value: u:r:untrusted_app_27:s0:c512,c768\n" +
+            "Actual value: ";
+        appDomainContext(context, msg);
+    }
+
+    /**
+     * Verify that selinux context is the expected type based on
+     * targetSdkVersion = current
+     */
+    public void testAppDataContext() throws Exception {
+        String context = "u:object_r:app_data_file:s0:c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion in range 26-27 " +
+            "must use the app_data_file selinux context and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app_data_file context.\n" +
+            "Example expected value: u:object_r:app_data_file:s0:c512,c768\n" +
+            "Actual value: ";
+        appDataContext(context, msg);
+    }
+}
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk
new file mode 100755
index 0000000..698d1ef
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk
@@ -0,0 +1,45 @@
+#
+# 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_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libc++ \
+    libcrypto \
+    libcts_jni \
+    libctsselinux_jni \
+    libnativehelper \
+    libnativehelper_compat_libc++ \
+    libpackagelistparser \
+    libpcre2 \
+    libselinux \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src common)
+LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdkCurrentTestCases
+LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidManifest.xml
new file mode 100755
index 0000000..ca375a3
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.selinuxtargetsdkcurrent.cts">
+
+    <!-- This app tests that apps with current targetSdkValue are placed in the
+         untrusted_app selinux domain -->
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="10000" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.selinuxtargetsdkcurrent.cts"
+                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidTest.xml
new file mode 100644
index 0000000..614f442
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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 Permission Selinux test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <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="CtsSelinuxTargetSdkCurrentTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.selinuxtargetsdkcurrent.cts" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/common b/tests/tests/selinux/selinuxTargetSdkCurrent/common
new file mode 120000
index 0000000..581eb17
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/common
@@ -0,0 +1 @@
+../common/src
\ No newline at end of file
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
new file mode 100644
index 0000000..cf913fc
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.security;
+
+import android.test.AndroidTestCase;
+import java.io.IOException;
+
+/**
+ * Verify the selinux domain for apps running with current targetSdkVersion
+ */
+public class SELinuxTargetSdkTest extends SELinuxTargetSdkTestBase
+{
+    /**
+     * Verify that net.dns properties may not be read
+     */
+    public void testNoDns() throws IOException {
+        noDns();
+    }
+
+    /**
+     * Verify that selinux context is the expected domain based on
+     * targetSdkVersion = current
+     */
+    public void testAppDomainContext() throws IOException {
+        String context = "u:r:untrusted_app:s0:c[0-9]+,c[0-9]+,c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion 28 and above " +
+            "must run in the untrusted_app selinux domain and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app's selinux context.\n" +
+            "Example expected value: u:r:untrusted_app:s0:c89,c256,c512,c768\n" +
+            "Actual value: ";
+        appDomainContext(context, msg);
+    }
+
+    /**
+     * Verify that selinux context is the expected type based on
+     * targetSdkVersion = current
+     */
+    public void testAppDataContext() throws Exception {
+        String context = "u:object_r:app_data_file:s0:c[0-9]+,c[0-9]+,c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion 28 and above " +
+            "must use the app_data_file selinux context and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app_data_file context.\n" +
+            "Example expected value: u:object_r:app_data_file:s0:c89,c256,c512,c768\n" +
+            "Actual value: ";
+        appDataContext(context, msg);
+    }
+}
diff --git a/tests/tests/shortcutmanager/Android.mk b/tests/tests/shortcutmanager/Android.mk
index 29333aa..b94f4f5 100755
--- a/tests/tests/shortcutmanager/Android.mk
+++ b/tests/tests/shortcutmanager/Android.mk
@@ -29,7 +29,7 @@
     ub-uiautomator \
     ShortcutManagerTestUtils
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-java-files-under, common/src)
@@ -41,6 +41,5 @@
 LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
-#include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
index 1872a7d..ec688fe 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -20,6 +20,8 @@
     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
index c43d574..90d0df5 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
index 18a118e..a5a0060 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -20,6 +20,8 @@
     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher"
             android:enabled="true">
             <intent-filter>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
index ee92b8c..d2cc04a 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name="Launcher" android:enabled="true" android:exported="false">
         </activity>
     </application>
diff --git a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
index 9a2659b..727828e 100644
--- a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
+++ b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
@@ -108,6 +108,7 @@
                 return;
             }
             ReplyUtil.sendSuccessReply(this, replyAction);
+            Log.e(TAG, "Sent reply");
         } catch (Exception e) {
             Log.e(TAG, "Caught exception", e);
             if (replyAction != null) {
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
index bab2674..56969bb 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -234,14 +234,14 @@
     }
 
     public void testSetDynamicShortcuts() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1", "title1"),
                     makeShortcut("s2", "title2"),
                     makeShortcut("s3", "title3"))));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -262,12 +262,12 @@
         });
 
         // Publish from different package.
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1x", "title1x"))));
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -282,7 +282,7 @@
         });
 
         // Package 1 still has the same shortcuts.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -302,12 +302,12 @@
                     .isEmpty();
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s2", "title2-updated"))));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -321,11 +321,11 @@
                     .isEmpty();
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list()));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .isEmpty();
             assertWith(getManager().getPinnedShortcuts())
@@ -335,7 +335,7 @@
         });
 
         // Package2 still has the same shortcuts.
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -361,7 +361,7 @@
                 getTestContext().getResources(), R.drawable.black_16x64));
         final Icon icon6 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
                 getTestContext().getResources(), R.drawable.black_32x32));
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo source = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
                     .setLongLabel("longlabel")
@@ -392,7 +392,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -418,7 +418,7 @@
                     getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo source = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
                     .setLongLabel("longlabel")
@@ -450,7 +450,7 @@
         });
 
         // paranoid icon check
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -469,7 +469,7 @@
                     icon2);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -489,7 +489,7 @@
         });
 
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -507,7 +507,7 @@
             assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
                     icon4);
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -525,7 +525,7 @@
             assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
                     icon5);
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -547,18 +547,18 @@
 
     public void testSetDynamicShortcuts_wasPinned() throws Exception {
         // Create s1 as a floating pinned shortcut.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1"))));
         });
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s1"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeDynamicShortcuts(list("s1"));
 
             assertWith(getManager().getDynamicShortcuts())
@@ -572,14 +572,14 @@
     }
 
     public void testAddDynamicShortcuts() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcut("s1", "title1"),
                     makeShortcut("s2", "title2"),
                     makeShortcut("s3", "title3"))));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -600,12 +600,12 @@
         });
 
         // Publish from different package.
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcut("s1x", "title1x"))));
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -620,7 +620,7 @@
         });
 
         // Package 1 still has the same shortcuts.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -640,12 +640,12 @@
                     .isEmpty();
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcut("s2", "title2-updated"))));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -665,11 +665,11 @@
                     .isEmpty();
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list()));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -690,7 +690,7 @@
         });
 
         // Package2 still has the same shortcuts.
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -713,7 +713,7 @@
         final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
         final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo source = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
                     .setLongLabel("longlabel")
@@ -744,7 +744,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -770,7 +770,7 @@
                     getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo source = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
                     .setLongLabel("longlabel")
@@ -802,7 +802,7 @@
         });
 
         // paranoid icon check
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -821,7 +821,7 @@
                     icon2);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -841,7 +841,7 @@
         });
 
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("xxx")
@@ -863,18 +863,18 @@
 
     public void testAddDynamicShortcuts_wasPinned() throws Exception {
         // Create s1 as a floating pinned shortcut.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1"))));
         });
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s1"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeDynamicShortcuts(list("s1"));
 
             assertWith(getManager().getDynamicShortcuts())
@@ -888,13 +888,13 @@
     }
 
     public void testUpdateShortcut() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1", "1a"),
                     makeShortcut("s2", "2a"),
                     makeShortcut("s3", "3a"))));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1", "1b"),
                     makeShortcut("s2", "2b"),
@@ -903,21 +903,21 @@
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s2", "s3"), getUserHandle());
             getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
                     list("s1", "s2"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeDynamicShortcuts(list("s3"));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             getManager().removeDynamicShortcuts(list("s1"));
         });
 
         // Check the current status.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -943,7 +943,7 @@
             assertWith(getManager().getManifestShortcuts())
                     .isEmpty();
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -971,21 +971,21 @@
         });
 
         // finally, call update.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().updateShortcuts(list(
                     makeShortcut("s1", "upd1a"),
                     makeShortcut("s2", "upd2a"),
                     makeShortcut("xxx") // doen't exist -> ignored.
                     )));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().updateShortcuts(list(
                     makeShortcut("s1", "upd1b"),
                     makeShortcut("s2", "upd2b"))));
         });
 
         // check.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1011,7 +1011,7 @@
             assertWith(getManager().getManifestShortcuts())
                     .isEmpty();
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1047,7 +1047,7 @@
         final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
         final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo source = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
                     .setLongLabel("longlabel")
@@ -1078,7 +1078,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             // No fields updated.
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .build();
@@ -1102,7 +1102,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setShortLabel("x")
                     .build();
@@ -1126,7 +1126,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setLongLabel("y")
                     .build();
@@ -1150,7 +1150,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setActivity(getActivity("Launcher2"))
                     .build();
@@ -1174,7 +1174,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setDisabledMessage("z")
                     .build();
@@ -1198,7 +1198,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIntents(new Intent[]{new Intent("main")})
                     .build();
@@ -1222,7 +1222,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setExtras(makePersistableBundle("ek1", "X"))
                     .build();
@@ -1246,7 +1246,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setCategories(set("dog"))
                     .build();
@@ -1270,7 +1270,7 @@
                     icon1);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIcon(icon2)
                     .build();
@@ -1295,7 +1295,7 @@
         });
 
         // More paranoid tests with icons.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIcon(icon1)
                     .build();
@@ -1307,7 +1307,7 @@
         });
 
         // More paranoid tests with icons.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIcon(icon3)
                     .build();
@@ -1318,7 +1318,7 @@
                     icon3);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIcon(icon4)
                     .build();
@@ -1329,7 +1329,7 @@
                     icon4);
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo updated = makeShortcutBuilder("s1")
                     .setIcon(icon1)
                     .build();
@@ -1352,13 +1352,13 @@
     }
 
     public void testDisableAndEnableShortcut() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1", "1a"),
                     makeShortcut("s2", "2a"),
                     makeShortcut("s3", "3a"))));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1", "1b"),
                     makeShortcut("s2", "2b"),
@@ -1367,21 +1367,21 @@
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s2", "s3"), getUserHandle());
             getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
                     list("s1", "s2"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeDynamicShortcuts(list("s3"));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             getManager().removeDynamicShortcuts(list("s1"));
         });
 
         // Check the current status.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1407,7 +1407,7 @@
             assertWith(getManager().getManifestShortcuts())
                     .isEmpty();
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1435,15 +1435,15 @@
         });
 
         // finally, call disable.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().disableShortcuts(list("s1", "s3"));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             getManager().disableShortcuts(list("s1", "s2"), "custom message");
         });
 
         // check
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1469,7 +1469,7 @@
                     .isEmpty();
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1496,10 +1496,10 @@
         });
 
         // try re-enable
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().enableShortcuts(list("s3"));
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1524,11 +1524,11 @@
         });
 
         // Re-publish will implicitly re-enable.
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             getManager().addDynamicShortcuts(list(makeShortcut("s2", "re-published")));
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
@@ -1550,7 +1550,7 @@
     }
 
     public void testImmutableShortcuts() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_2", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
@@ -1558,11 +1558,11 @@
         });
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("ms21"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_1", true);
             enableManifestActivity("Launcher_manifest_2", false);
 
@@ -1570,7 +1570,7 @@
                     "Manifest shortcuts didn't show up");
         });
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .isEmpty();
             assertWith(getManager().getPinnedShortcuts())
@@ -1610,7 +1610,7 @@
     public void testManifestDefinition() throws Exception {
         final Icon iconMs21 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_2", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -1687,7 +1687,7 @@
     }
 
     public void testDynamicIntents() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
 
             final ShortcutInfo s1 = makeShortcutBuilder("s1")
                     .setShortLabel("shortlabel")
@@ -1754,7 +1754,7 @@
     }
 
     public void testManifestWithErrors() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_error_1", true);
             enableManifestActivity("Launcher_manifest_error_2", true);
             enableManifestActivity("Launcher_manifest_error_3", true);
@@ -1772,7 +1772,7 @@
     }
 
     public void testManifestDisabled() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_4a", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -1786,11 +1786,11 @@
         });
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("ms41", "ms42"), getUserHandle());
         });
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_4b", true);
             enableManifestActivity("Launcher_manifest_4a", false);
 
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
index 0614d84..cc8df51 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
@@ -42,7 +42,7 @@
     public void testGetShortcutConfigActivityList() throws Exception {
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertNotNull(getConfigActivity());
             assertNotNull(getLauncherApps().getShortcutConfigActivityIntent(getConfigActivity()));
         });
@@ -50,7 +50,7 @@
         // Get config activity works even for non-default activity.
         setDefaultLauncher(getInstrumentation(), mLauncherContext4);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertNotNull(getConfigActivity());
             // throws exception when default launcher is different.
             assertExpectException(SecurityException.class, null, () ->
@@ -61,7 +61,7 @@
     public void testCorrectIntentSenderCreated() throws Throwable {
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
         final AtomicReference<IntentSender> sender = new AtomicReference<>();
-        runWithCaller(mLauncherContext1, () ->
+        runWithCallerWithStrictMode(mLauncherContext1, () ->
             sender.set(getLauncherApps().getShortcutConfigActivityIntent(getConfigActivity())));
 
         Instrumentation.ActivityMonitor monitor =
@@ -87,7 +87,7 @@
     public void testCreateShortcutResultIntent_defaultLauncher() throws Exception {
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
         PinItemRequest request = getShortcutRequestForPackage1();
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertTrue(request.isValid());
             assertTrue(request.accept());
         });
@@ -99,7 +99,7 @@
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext4);
         // Launcher1 can still access the request
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertTrue(request.isValid());
             assertTrue(request.accept());
         });
@@ -110,7 +110,7 @@
         PinItemRequest request = getShortcutRequestForPackage1();
 
         // Launcher1 can still access the request
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertFalse(request.isValid());
             assertExpectException(SecurityException.class, null, request::accept);
         });
@@ -118,7 +118,7 @@
 
     private PinItemRequest getShortcutRequestForPackage1() {
         final AtomicReference<PinItemRequest> result = new AtomicReference<>();
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             final ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
                     .setShortLabel("label1")
                     .setIntent(new Intent(Intent.ACTION_MAIN))
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
index 1f45a7f..aa0ea527 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -33,12 +33,13 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
 import android.os.UserHandle;
 import android.support.annotation.NonNull;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
-import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -50,6 +51,14 @@
 
     private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
 
+    /**
+     * Whether to enable strict mode or not.
+     *
+     * TODO Enable it after fixing b/68051728. Somehow violations would happen on the dashboard
+     * only and can't reproduce it locally.
+     */
+    private static final boolean ENABLE_STRICT_MODE = false;
+
     private static class SpoofingContext extends ContextWrapper {
         private final String mPackageName;
 
@@ -276,6 +285,33 @@
         return mCurrentLauncherApps;
     }
 
+    protected void runWithStrictMode(Runnable r) {
+        final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        try {
+            if (ENABLE_STRICT_MODE) {
+                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                        .detectAll()
+                        .penaltyDeath()
+                        .build());
+            }
+            r.run();
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    protected void runWithNoStrictMode(Runnable r) {
+        final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        try {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .permitAll()
+                    .build());
+            r.run();
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
     protected void runWithCaller(Context callerContext, Runnable r) {
         final Context prev = mCurrentCallerPackage;
 
@@ -286,6 +322,14 @@
         setCurrentCaller(prev);
     }
 
+    protected void runWithCallerWithStrictMode(Context callerContext, Runnable r) {
+        runWithCaller(callerContext, () -> runWithStrictMode(r));
+    }
+
+    protected void runWithCallerWithNoStrictMode(Context callerContext, Runnable r) {
+        runWithCaller(callerContext, () -> runWithNoStrictMode(r));
+    }
+
     public static Bundle makeBundle(Object... keysAndValues) {
         assertTrue((keysAndValues.length % 2) == 0);
 
@@ -437,11 +481,11 @@
 
     protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
             String shortcutId, boolean withBadge) {
-        setDefaultLauncher(getInstrumentation(), launcherContext);
+        runWithNoStrictMode(() -> setDefaultLauncher(getInstrumentation(), launcherContext));
 
         final AtomicReference<Drawable> ret = new AtomicReference<>();
 
-        runWithCaller(launcherContext, () -> {
+        runWithCallerWithNoStrictMode(launcherContext, () -> {
             final ShortcutQuery q = new ShortcutQuery()
                     .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
                                     | ShortcutQuery.FLAG_MATCH_MANIFEST
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
index e527bf9..f27a60a 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -40,7 +40,7 @@
     }
 
     public void testPinShortcuts() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_1", true);
             enableManifestActivity("Launcher_manifest_2", true);
 
@@ -59,7 +59,7 @@
                     .areAllDynamic()
                     .areAllEnabled();
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             enableManifestActivity("Launcher_manifest_1", true);
             enableManifestActivity("Launcher_manifest_3", true);
 
@@ -81,7 +81,7 @@
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(
                     mPackageContext1.getPackageName(),
                     list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
@@ -90,11 +90,11 @@
                     list("s2", "s3", "ms1", "ms31"), getUserHandle());
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeDynamicShortcuts(list("s1", "s2"));
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             enableManifestActivity("Launcher_manifest_3", false);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
@@ -104,7 +104,7 @@
         });
 
         // Check the result.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .haveIds("s3", "s4", "s5")
                     .areAllEnabled();
@@ -116,7 +116,7 @@
                     .areAllEnabled();
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertWith(getManager().getDynamicShortcuts())
                     .haveIds("s3", "s4", "s5")
                     .areAllEnabled();
@@ -142,7 +142,7 @@
         Thread.sleep(2);
         final long time1 = System.currentTimeMillis();
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().updateShortcuts(list(
                     makeShortcut("s3"))));
 
@@ -156,12 +156,12 @@
         Thread.sleep(2);
         final long time2 = System.currentTimeMillis();
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().updateShortcuts(list(
                     makeShortcutWithRank("s4", 999))));
         });
 
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             setTargetActivityOverride("Launcher_manifest_1");
 
             assertTrue(getManager().updateShortcuts(list(
@@ -171,7 +171,7 @@
         Thread.sleep(2);
         final long time3 = System.currentTimeMillis();
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             assertWith(getShortcutsAsLauncher(
                     FLAG_MATCH_DYNAMIC,
                     mPackageContext1.getPackageName(),
@@ -311,7 +311,7 @@
 
         final Icon icon5 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_2", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
@@ -357,7 +357,7 @@
         final Icon icon2 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
             getTestContext().getResources(), R.drawable.black_32x32));
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_2", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
index b95599e..166c9ba 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
@@ -87,7 +87,11 @@
                 sb.append(si.getId());
             }
 
-            Log.i(TAG, "package=" + packageName + " shortcuts=" + sb.toString());
+            Log.i(TAG, "onShortcutsChanged: package="
+                    + packageName + " shortcuts=" + sb.toString());
+            for (ShortcutInfo si : shortcuts) {
+                Log.i(TAG, "  " + si);
+            }
             lastPackage = packageName;
             lastShortcuts.clear();
             lastShortcuts.addAll(shortcuts);
@@ -153,6 +157,7 @@
         reset.run();
         try {
             //-----------------------
+            Log.i(TAG, "testCallbacks: setDynamicShortcuts");
             runWithCaller(mPackageContext1, () -> {
                 assertTrue(getManager().setDynamicShortcuts(list(
                         makeShortcut("s1"),
@@ -166,6 +171,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: addDynamicShortcuts");
             runWithCaller(mPackageContext1, () -> {
                 assertTrue(getManager().addDynamicShortcuts(list(
                         makeShortcutWithRank("sx", 1)
@@ -178,6 +184,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: updateShortcuts 1");
             runWithCaller(mPackageContext1, () -> {
                 assertTrue(getManager().updateShortcuts(list(
                         makeShortcut("s2")
@@ -190,6 +197,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: updateShortcuts 2");
             runWithCaller(mPackageContext1, () -> {
                 assertTrue(getManager().updateShortcuts(list(
                         makeShortcut("sx")
@@ -202,6 +210,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: enableManifestActivity 1");
             runWithCaller(mPackageContext1, () -> {
                 enableManifestActivity("Launcher_manifest_1", true);
                 retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
@@ -214,6 +223,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: enableManifestActivity 2");
             runWithCaller(mPackageContext1, () -> {
                 enableManifestActivity("Launcher_manifest_2", true);
                 retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
@@ -227,6 +237,7 @@
 
             //-----------------------
             // Pin some shortcuts.
+            Log.i(TAG, "testCallbacks: pinShortcuts");
             runWithCaller(mLauncherContext1, () -> {
                 getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                         list("s1", "ms1", "ms21"), getUserHandle());
@@ -242,6 +253,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: removeDynamicShortcuts");
             runWithCaller(mPackageContext1, () -> {
                 getManager().removeDynamicShortcuts(list("s1", "s2"));
             });
@@ -254,6 +266,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: enableManifestActivity");
             runWithCaller(mPackageContext1, () -> {
                 enableManifestActivity("Launcher_manifest_2", false);
 
@@ -273,6 +286,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: disableShortcuts");
             runWithCaller(mPackageContext1, () -> {
                 getManager().disableShortcuts(list("s1"));
             });
@@ -291,10 +305,13 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: enableShortcuts 1");
             runWithCaller(mPackageContext1, () -> {
                 getManager().enableShortcuts(list("s1"));
             });
-            retryUntil(() -> c.isCalled(), "callback not called.");
+            retryUntil(() -> (c.isCalled() && c.isShortcutById("s1", si -> si.isEnabled())),
+                    "s1 not enabled");
+
             c.assertCalled(mPackageContext1)
                     .haveIds("s1", "sx", "ms1", "ms21")
 
@@ -307,6 +324,7 @@
             reset.run();
 
             //-----------------------
+            Log.i(TAG, "testCallbacks: enableShortcuts 2");
             runWithCaller(mPackageContext1, () -> {
                 getManager().enableShortcuts(list("s2"));
             });
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
index 3e75b4a..8b7ae4e 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
@@ -29,7 +29,7 @@
      * Basic tests: single app, single activity, no manifest shortcuts.
      */
     public void testNumDynamicShortcuts() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1"),
@@ -101,7 +101,7 @@
      * Manifest shortcuts are included in the count too.
      */
     public void testWithManifest() throws Exception {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_1", true);
             enableManifestActivity("Launcher_manifest_2", true);
 
@@ -110,7 +110,7 @@
 
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertWith(getManager().getManifestShortcuts())
                     .haveIds("ms1", "ms21", "ms22")
                     .areAllManifest()
@@ -134,7 +134,7 @@
         testNumDynamicShortcuts();
 
         // Launcher_manifest_1 has one manifest, so can only add 4 dynamic shortcuts.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             setTargetActivityOverride("Launcher_manifest_1");
 
             assertTrue(getManager().setDynamicShortcuts(list(
@@ -163,7 +163,7 @@
         });
 
         // Launcher_manifest_2 has two manifests, so can only add 3.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             setTargetActivityOverride("Launcher_manifest_2");
 
             assertTrue(getManager().addDynamicShortcuts(list(
@@ -188,7 +188,7 @@
     }
 
     public void testChangeActivity() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             setTargetActivityOverride("Launcher");
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1"),
@@ -262,7 +262,7 @@
     }
 
     public void testWithPinned() {
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s1"),
                     makeShortcut("s2"),
@@ -274,12 +274,12 @@
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s1", "s2", "s3", "s4", "s5"), getUserHandle());
         });
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut("s6"),
                     makeShortcut("s7"),
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
index 78dbccd..5ed5d52 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
@@ -16,6 +16,7 @@
 package android.content.pm.cts.shortcutmanager;
 
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
@@ -266,6 +267,18 @@
                     .revertToOriginalList()
                     .selectByIds("ms32")
                     .areAllDisabled();
+
+            // Make sure "ALL_PINNED" doesn't work without the permission.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY
+                            | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("s3", "s4", "ms22");
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY
+                            | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("s1", "s2", "s3", "ms32");
         });
     }
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
index 756cf80..c04c0af 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
@@ -40,6 +40,8 @@
 public class ShortcutManagerRequestPinTest extends ShortcutManagerCtsTestsBase {
     private static final String TAG = "ShortcutMRPT";
 
+    private static final String SHORTCUT_ID = "s12345";
+
     public void testIsRequestPinShortcutSupported() {
 
         // Launcher 1 supports it.
@@ -61,11 +63,11 @@
      * A test for {@link ShortcutManager#requestPinShortcut}, a very simple case.
      */
     public void testRequestPinShortcut() {
+        Log.i(TAG, "Testing with launcher1.");
+
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
-        final String SHORTCUT_ID = "s12345";
-
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().isRequestPinShortcutSupported());
 
             ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
@@ -79,12 +81,19 @@
                         .setExtras(extras)
                         .build();
 
+                // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+                // update the extras that contains the broadcast ID, we need to update the shortcut
+                // manually here before requestPinShortcut().
+
+                // This is only needed when a shortcut is already published with the same ID.
+                assertTrue(getManager().updateShortcuts(list(shortcut)));
+
                 Log.i(TAG, "Calling requestPinShortcut...");
                 assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
                 Log.i(TAG, "Done.");
             });
         });
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             final ShortcutQuery query = new ShortcutQuery()
                     .setPackage(mPackageContext1.getPackageName())
                     .setShortcutIds(list(SHORTCUT_ID))
@@ -117,6 +126,183 @@
                     .areAllMutable()
                     ;
         });
+
+        Log.i(TAG, "Done testing with launcher1.");
+    }
+
+    public void testRequestPinShortcut_multiLaunchers() {
+        testRequestPinShortcut();
+
+        Log.i(TAG, "Testing with launcher2.");
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+                final PersistableBundle extras = new PersistableBundle();
+                extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+                extras.putString(Constants.LABEL, "label1");
+
+                final ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
+                        .setExtras(extras)
+                        .build();
+
+                // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+                // update the extras that contains the broadcast ID, we need to update the shortcut
+                // manually here before requestPinShortcut().
+                assertTrue(getManager().updateShortcuts(list(shortcut)));
+
+                Log.i(TAG, "Calling requestPinShortcut...");
+                assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
+                Log.i(TAG, "Done.");
+            });
+        });
+        runWithCallerWithStrictMode(mLauncherContext2, () -> {
+            final ShortcutQuery query = new ShortcutQuery()
+                    .setPackage(mPackageContext1.getPackageName())
+                    .setShortcutIds(list(SHORTCUT_ID))
+                    .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+                            | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+            Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+            retryUntil(() -> {
+                final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+                        android.os.Process.myUserHandle());
+                if (shortcuts == null) {
+                    // Launcher not responded yet.
+                    return false;
+                }
+                assertWith(shortcuts)
+                        .haveIds(SHORTCUT_ID)
+                        .areAllPinned()
+                        .areAllNotDynamic()
+                        .areAllNotManifest();
+                return true;
+            }, "Shortcut still not pinned");
+        });
+        Log.i(TAG, "Done testing with launcher2.");
+    }
+
+    public void testRequestPinShortcut_multiLaunchers_withDynamic() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        // Publish as a dynamic shortcut first, then call requestPin.
+        ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
+                .setShortLabel("label1")
+                .setIntent(new Intent(Intent.ACTION_MAIN))
+                .build();
+        assertTrue(getManager().setDynamicShortcuts(list(shortcut)));
+
+        // ==============================================================
+        Log.i(TAG, "Testing with launcher1.");
+
+        assertTrue(getManager().isRequestPinShortcutSupported());
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+                final PersistableBundle extras = new PersistableBundle();
+                extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+                extras.putString(Constants.LABEL, "label1");
+
+                final ShortcutInfo shortcut2 = makeShortcutBuilder(SHORTCUT_ID)
+                        .setExtras(extras)
+                        .build();
+
+                // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+                // update the extras that contains the broadcast ID, we need to update the shortcut
+                // manually here before requestPinShortcut().
+
+                // This is only needed when a shortcut is already published with the same ID.
+                assertTrue(getManager().updateShortcuts(list(shortcut2)));
+
+                Log.i(TAG, "Calling requestPinShortcut...");
+                assertTrue(getManager().requestPinShortcut(shortcut2, /* intent sender */ null));
+                Log.i(TAG, "Done.");
+            });
+        });
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final ShortcutQuery query = new ShortcutQuery()
+                    .setPackage(mPackageContext1.getPackageName())
+                    .setShortcutIds(list(SHORTCUT_ID))
+                    .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+                            | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+            Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+            retryUntil(() -> {
+                final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+                        android.os.Process.myUserHandle());
+                if (shortcuts == null) {
+                    // Launcher not responded yet.
+                    return false;
+                }
+                assertWith(shortcuts)
+                        .haveIds(SHORTCUT_ID)
+                        .areAllPinned()
+                        .areAllDynamic()
+                        .areAllNotManifest();
+                return true;
+            }, "Shortcut still not pinned");
+        });
+        // ==============================================================
+        Log.i(TAG, "Testing with launcher2.");
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        assertTrue(getManager().isRequestPinShortcutSupported());
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+                final PersistableBundle extras = new PersistableBundle();
+                extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+                extras.putString(Constants.LABEL, "label1");
+
+                final ShortcutInfo shortcut2 = makeShortcutBuilder(SHORTCUT_ID)
+                        .setExtras(extras)
+                        .build();
+
+                // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+                // update the extras that contains the broadcast ID, we need to update the shortcut
+                // manually here before requestPinShortcut().
+
+                // This is only needed when a shortcut is already published with the same ID.
+                assertTrue(getManager().updateShortcuts(list(shortcut2)));
+
+                Log.i(TAG, "Calling requestPinShortcut...");
+                assertTrue(getManager().requestPinShortcut(shortcut2, /* intent sender */ null));
+                Log.i(TAG, "Done.");
+            });
+        });
+        runWithCallerWithStrictMode(mLauncherContext2, () -> {
+            final ShortcutQuery query = new ShortcutQuery()
+                    .setPackage(mPackageContext1.getPackageName())
+                    .setShortcutIds(list(SHORTCUT_ID))
+                    .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+                            | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+            Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+            retryUntil(() -> {
+                final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+                        android.os.Process.myUserHandle());
+                if (shortcuts == null) {
+                    // Launcher not responded yet.
+                    return false;
+                }
+                assertWith(shortcuts)
+                        .haveIds(SHORTCUT_ID)
+                        .areAllPinned()
+                        .areAllDynamic()
+                        .areAllNotManifest();
+                return true;
+            }, "Shortcut still not pinned");
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getPinnedShortcuts())
+                    .forShortcutWithId(SHORTCUT_ID, si -> {
+                        assertEquals("label1", si.getShortLabel());
+                    })
+                    .areAllPinned()
+                    .areAllDynamic()
+                    .areAllNotManifest()
+                    .areAllMutable()
+            ;
+        });
     }
 
     /**
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
index 9cf1f89..a66e8ed 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
@@ -53,7 +53,7 @@
 
         ShortcutLaunchedActivity.setExpectedOrder(expectedActions);
 
-        runWithCaller(launcher, () -> {
+        runWithCallerWithStrictMode(launcher, () -> {
             getLauncherApps().startShortcut(client.getPackageName(), id, rect, options,
                     getUserHandle());
         });
@@ -73,7 +73,7 @@
 
     private void assertShortcutCantStart(Context launcher, Context client, String id,
             Class<? extends Throwable> exceptionClass) {
-        runWithCaller(launcher, () -> {
+        runWithCallerWithStrictMode(launcher, () -> {
             assertExpectException(exceptionClass, "", () -> {
 
                 getLauncherApps().startShortcut(client.getPackageName(), id, null, null,
@@ -96,7 +96,7 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
                 .putExtra("k1", "v1");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcutBuilder("s1").setShortLabel("abc")
                             .setIntent(i).build()
@@ -128,7 +128,7 @@
                 .setComponent(mLaunchedActivity)
                 .putExtra("kx", "vx");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcutBuilder("s1").setShortLabel("abc")
                             .setIntents(new Intent[]{i1, i2, i3}).build()
@@ -182,7 +182,7 @@
         testStartMultiple();
 
         // then remove it.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeAllDynamicShortcuts();
         });
 
@@ -198,7 +198,7 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
                 .putExtra("k1", "v1");
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcutBuilder("s1").setShortLabel("abc")
                             .setIntent(i).build()
@@ -219,13 +219,13 @@
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
         // then pin it.
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s1"), getUserHandle());
         });
 
         // Then remove it.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeAllDynamicShortcuts();
         });
 
@@ -241,7 +241,7 @@
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
 
         // then pin it.
-        runWithCaller(mLauncherContext1, () -> {
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
             getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
                     list("s1"), getUserHandle());
         });
@@ -257,7 +257,7 @@
         assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1", EXPECTED_ACTIONS_SINGLE);
 
         // Then remove it.
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             getManager().removeAllDynamicShortcuts();
         });
 
@@ -304,7 +304,7 @@
         Intent i = new Intent(Intent.ACTION_MAIN)
                 .setComponent(new ComponentName("abc", "def"));
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcutBuilder("s1").setShortLabel("abc")
                             .setIntent(i).build()
@@ -329,7 +329,7 @@
                         "android.content.pm.cts.shortcutmanager.packages.package4",
                         "android.content.pm.cts.shortcutmanager.packages.Launcher"));
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
                     makeShortcutBuilder("s1").setShortLabel("abc")
                             .setIntent(i).build()
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
index 064ba58..50ce615 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
@@ -75,7 +75,7 @@
 
     public void testReportShortcutUsed() throws InterruptedException {
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             enableManifestActivity("Launcher_manifest_2", true);
 
             retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -89,13 +89,13 @@
         final String idManifest = "ms21";
         final String idNonexistance = "nonexistence";
 
-        runWithCaller(mPackageContext1, () -> {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut(id1),
                     makeShortcut(id2)
             )));
         });
-        runWithCaller(mPackageContext2, () -> {
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
                     makeShortcut(id1),
                     makeShortcut(id3)
@@ -106,7 +106,7 @@
 
         // Report usage.
         final long start1 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
-        runWithCaller(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
+        runWithCallerWithStrictMode(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
         final long end1 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
 
         // Check the log.
@@ -115,7 +115,7 @@
 
         // Report usage.
         final long start2 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
-        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
+        runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
         final long end2 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
 
         // Check the log.
@@ -124,8 +124,8 @@
 
         // Report usage.
         final long start3 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
-        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
-        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
+        runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
+        runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
         final long end3 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
 
         // Check the log.
diff --git a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
index fcbc167..0d444d4 100644
--- a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <receiver
             android:name="ShortcutManagerThrottlingTestReceiver"
             android:exported="true">
diff --git a/tests/tests/simpleperf/AndroidTest.xml b/tests/tests/simpleperf/AndroidTest.xml
index 89faa0f..eeb6f99 100644
--- a/tests/tests/simpleperf/AndroidTest.xml
+++ b/tests/tests/simpleperf/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Simpleperf test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="bionic" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk b/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
index 2a172b5..4f097a4 100644
--- a/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
+++ b/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
@@ -20,7 +20,9 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, .)
 
diff --git a/tests/tests/slice/Android.mk b/tests/tests/slice/Android.mk
new file mode 100644
index 0000000..c56f74c
--- /dev/null
+++ b/tests/tests/slice/Android.mk
@@ -0,0 +1,45 @@
+# 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.
+
+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.runner.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctsdeviceutillegacy \
+    ctstestrunner \
+    mockito-target-minus-junit4 \
+    platform-test-annotations \
+    ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSliceTestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
new file mode 100644
index 0000000..ebf3bc7
--- /dev/null
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.slice.cts">
+
+    <uses-permission android:name="android.permission.BIND_SLICE" />
+
+    <application android:label="Android TestCase"
+                android:icon="@drawable/size_48x48"
+                android:maxRecents="1"
+                android:multiArch="true"
+                android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+
+        <provider android:name=".SliceProvider"
+                  android:authorities="android.slice.cts"
+                  android:process=":slice_process" />
+
+        <provider android:name=".LocalSliceProvider"
+            android:authorities="android.slice.cts.local">
+            <intent-filter>
+                <action android:name="android.slice.cts.action.TEST_ACTION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </provider>
+
+        <activity android:name=".Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.slice.cts"
+                     android:label="CTS tests of android.slice">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/slice/AndroidTest.xml b/tests/tests/slice/AndroidTest.xml
new file mode 100644
index 0000000..ceae60a
--- /dev/null
+++ b/tests/tests/slice/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 Slice test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSliceTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm grant android.slice.cts android.permission.BIND_SLICE" />
+        <option name="teardown-command" value="pm revoke android.slice.cts android.permission.BIND_SLICE"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.slice.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/slice/res/drawable/size_48x48.jpg b/tests/tests/slice/res/drawable/size_48x48.jpg
new file mode 100644
index 0000000..5c2291e
--- /dev/null
+++ b/tests/tests/slice/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/tests/tests/slice/src/android/slice/cts/Launcher.java b/tests/tests/slice/src/android/slice/cts/Launcher.java
new file mode 100644
index 0000000..441f964
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/Launcher.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.slice.cts;
+
+import android.app.Activity;
+
+public class Launcher extends Activity {
+}
diff --git a/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java b/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java
new file mode 100644
index 0000000..0eaf90f
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.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 android.slice.cts;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.content.Intent;
+import android.net.Uri;
+
+import java.util.Collection;
+import java.util.List;
+
+public class LocalSliceProvider extends SliceProvider {
+    public static SliceProvider sProxy;
+
+    @Override
+    public boolean onCreate() {
+        return sProxy == null || sProxy.onCreate();
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri, List<SliceSpec> specs) {
+        if (sProxy != null) return sProxy.onBindSlice(sliceUri, specs);
+        return super.onBindSlice(sliceUri, specs);
+    }
+
+    @Override
+    public Uri onMapIntentToUri(Intent intent) {
+        if (sProxy != null) return sProxy.onMapIntentToUri(intent);
+        return super.onMapIntentToUri(intent);
+    }
+
+    @Override
+    public Collection<Uri> onGetSliceDescendants(Uri uri) {
+        if (sProxy != null) return sProxy.onGetSliceDescendants(uri);
+        return super.onGetSliceDescendants(uri);
+    }
+
+    @Override
+    public void onSlicePinned(Uri sliceUri) {
+        if (sProxy != null) sProxy.onSlicePinned(sliceUri);
+        super.onSlicePinned(sliceUri);
+    }
+
+    @Override
+    public void onSliceUnpinned(Uri sliceUri) {
+        if (sProxy != null) sProxy.onSliceUnpinned(sliceUri);
+        super.onSliceUnpinned(sliceUri);
+    }
+}
diff --git a/tests/tests/slice/src/android/slice/cts/SliceBindingTest.java b/tests/tests/slice/src/android/slice/cts/SliceBindingTest.java
new file mode 100644
index 0000000..05435c6
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceBindingTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.slice.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+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.app.PendingIntent.CanceledException;
+import android.app.slice.SliceManager;
+import android.app.slice.SliceSpec;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.os.Bundle;
+import android.slice.cts.SliceProvider.TestParcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SliceBindingTest {
+
+    public static boolean sFlag = false;
+
+    private static final Uri BASE_URI = Uri.parse("content://android.slice.cts/");
+    private final Context mContext = InstrumentationRegistry.getContext();
+    private final SliceManager mSliceManager = mContext.getSystemService(SliceManager.class);
+
+    @Test
+    public void testProcess() {
+        sFlag = false;
+        mSliceManager.bindSlice(BASE_URI.buildUpon().appendPath("set_flag").build(),
+                Collections.emptyList());
+        assertFalse(sFlag);
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(SliceProvider.SLICE_TYPE,
+                mContext.getContentResolver().getType(BASE_URI));
+    }
+
+    @Test
+    public void testSliceUri() {
+        Slice s = mSliceManager.bindSlice(BASE_URI,
+                Collections.emptyList());
+        assertEquals(BASE_URI, s.getUri());
+    }
+
+    @Test
+    public void testSubSlice() {
+        Uri uri = BASE_URI.buildUpon().appendPath("subslice").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_SLICE, item.getFormat());
+        assertEquals("subslice", item.getSubType());
+        // The item should start with the same Uri as the parent, but be different.
+        assertTrue(item.getSlice().getUri().toString().startsWith(uri.toString()));
+        assertNotEquals(uri, item.getSlice().getUri());
+    }
+
+    @Test
+    public void testText() {
+        Uri uri = BASE_URI.buildUpon().appendPath("text").build();
+        Slice s = mSliceManager.bindSlice(uri,
+                Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TEXT, item.getFormat());
+        // TODO: Test spannables here.
+        assertEquals("Expected text", item.getText());
+    }
+
+    @Test
+    public void testIcon() {
+        Uri uri = BASE_URI.buildUpon().appendPath("icon").build();
+        Slice s = mSliceManager.bindSlice(uri,
+                Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_IMAGE, item.getFormat());
+        assertEquals(Icon.createWithResource(mContext, R.drawable.size_48x48).toString(),
+                item.getIcon().toString());
+    }
+
+    @Test
+    public void testAction() {
+        sFlag = false;
+        CountDownLatch latch = new CountDownLatch(1);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                sFlag = true;
+                latch.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver,
+                new IntentFilter(mContext.getPackageName() + ".action"));
+        Uri uri = BASE_URI.buildUpon().appendPath("action").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_ACTION, item.getFormat());
+        try {
+            item.getAction().send();
+        } catch (CanceledException e) {
+        }
+
+        try {
+            latch.await(100, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        assertTrue(sFlag);
+        mContext.unregisterReceiver(receiver);
+    }
+
+    @Test
+    public void testInt() {
+        Uri uri = BASE_URI.buildUpon().appendPath("int").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_INT, item.getFormat());
+        assertEquals(0xff121212, item.getInt());
+    }
+
+    @Test
+    public void testTimestamp() {
+        Uri uri = BASE_URI.buildUpon().appendPath("timestamp").build();
+        Slice s = mSliceManager.bindSlice(uri,
+                Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TIMESTAMP, item.getFormat());
+        assertEquals(43, item.getTimestamp());
+    }
+
+    @Test
+    public void testHints() {
+        // Note this tests that hints are propagated through to the client but not that any specific
+        // hints have any effects.
+        Uri uri = BASE_URI.buildUpon().appendPath("hints").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(uri, s.getUri());
+
+        assertEquals(Arrays.asList(Slice.HINT_LIST), s.getHints());
+        assertEquals(Arrays.asList(Slice.HINT_TITLE), s.getItems().get(0).getHints());
+        assertEquals(Arrays.asList(Slice.HINT_NO_TINT, Slice.HINT_LARGE),
+                s.getItems().get(1).getHints());
+    }
+
+    @Test
+    public void testHasHints() {
+        Uri uri = BASE_URI.buildUpon().appendPath("hints").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+
+        assertTrue(s.getItems().get(0).hasHint(Slice.HINT_TITLE));
+        assertFalse(s.getItems().get(0).hasHint(Slice.HINT_LIST));
+    }
+
+    @Test
+    public void testBundle() {
+        Uri uri = BASE_URI.buildUpon().appendPath("bundle").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_BUNDLE, item.getFormat());
+        Bundle b = item.getBundle();
+        b.setClassLoader(getClass().getClassLoader());
+        assertEquals(new TestParcel(), b.getParcelable("a"));
+    }
+
+    @Test
+    public void testGetDescendants() {
+        Collection<Uri> allUris = mSliceManager.getSliceDescendants(BASE_URI);
+        assertEquals(SliceProvider.PATHS.length, allUris.size());
+        Iterator<Uri> it = allUris.iterator();
+        for (int i = 0; i < SliceProvider.PATHS.length; i++) {
+            assertEquals(SliceProvider.PATHS[i], it.next().getPath());
+        }
+
+        assertEquals(0, mSliceManager.getSliceDescendants(
+                BASE_URI.buildUpon().appendPath("/nothing").build()).size());
+    }
+
+    @Test
+    public void testGetSliceSpec() {
+        Uri uri = BASE_URI.buildUpon().appendPath("spec").build();
+        Slice s = mSliceManager.bindSlice(uri, Collections.emptyList());
+        assertEquals(new SliceSpec(SliceProvider.SPEC_TYPE, SliceProvider.SPEC_REV), s.getSpec());
+    }
+}
diff --git a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
new file mode 100644
index 0000000..74ed881
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.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.slice.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceSpec;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class SliceBuilderTest {
+
+    private static final Uri BASE_URI = Uri.parse("content://android.slice.cts/");
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    @Test
+    public void testInt() {
+        Slice s = new Slice.Builder(BASE_URI)
+                .addInt(0xff121212, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_INT, item.getFormat());
+        assertEquals(0xff121212, item.getInt());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testIntList() {
+        Slice s = new Slice.Builder(BASE_URI)
+                .addInt(0xff121212, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_INT, item.getFormat());
+        assertEquals(0xff121212, item.getInt());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testIcon() {
+        Icon i = Icon.createWithResource(mContext, R.drawable.size_48x48);
+        Slice s = new Slice.Builder(BASE_URI)
+                .addIcon(i, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_IMAGE, item.getFormat());
+        assertEquals(i, item.getIcon());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testIconList() {
+        Icon i = Icon.createWithResource(mContext, R.drawable.size_48x48);
+        Slice s = new Slice.Builder(BASE_URI)
+                .addIcon(i, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_IMAGE, item.getFormat());
+        assertEquals(i, item.getIcon());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testText() {
+        CharSequence i = "Some text";
+        Slice s = new Slice.Builder(BASE_URI)
+                .addText(i, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TEXT, item.getFormat());
+        assertEquals(i, item.getText());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testTextList() {
+        CharSequence i = "Some text";
+        Slice s = new Slice.Builder(BASE_URI)
+                .addText(i, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TEXT, item.getFormat());
+        assertEquals(i, item.getText());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testTimestamp() {
+        long i = 43L;
+        Slice s = new Slice.Builder(BASE_URI)
+                .addTimestamp(i, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TIMESTAMP, item.getFormat());
+        assertEquals(i, item.getTimestamp());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testTimestampList() {
+        long i = 43L;
+        Slice s = new Slice.Builder(BASE_URI)
+                .addTimestamp(i, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_TIMESTAMP, item.getFormat());
+        assertEquals(i, item.getTimestamp());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testRemoteInput() {
+        RemoteInput i = new RemoteInput.Builder("key").build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addRemoteInput(i, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_REMOTE_INPUT, item.getFormat());
+        assertEquals(i, item.getRemoteInput());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testRemoteInputList() {
+        RemoteInput i = new RemoteInput.Builder("key").build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addRemoteInput(i, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_REMOTE_INPUT, item.getFormat());
+        assertEquals(i, item.getRemoteInput());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testBundle() {
+        Bundle i = new Bundle();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addBundle(i, "subtype", Slice.HINT_TITLE)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_BUNDLE, item.getFormat());
+        assertEquals(i, item.getBundle());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testBundleList() {
+        Bundle i = new Bundle();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addBundle(i, "subtype", Arrays.asList(Slice.HINT_TITLE))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_BUNDLE, item.getFormat());
+        assertEquals(i, item.getBundle());
+        assertEquals("subtype", item.getSubType());
+        assertTrue(item.hasHint(Slice.HINT_TITLE));
+    }
+
+    @Test
+    public void testAction() {
+        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build()).build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addAction(i, subSlice)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_ACTION, item.getFormat());
+        assertEquals(i, item.getAction());
+        assertEquals(null, item.getSubType());
+        assertEquals(subSlice, item.getSlice());
+    }
+
+    @Test
+    public void testActionSubtype() {
+        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build()).build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addAction(i, subSlice, "subtype")
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_ACTION, item.getFormat());
+        assertEquals(i, item.getAction());
+        assertEquals("subtype", item.getSubType());
+        assertEquals(subSlice, item.getSlice());
+    }
+
+    @Test
+    public void testSubslice() {
+        Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build()).build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addSubSlice(subSlice)
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_SLICE, item.getFormat());
+        assertEquals(null, item.getSubType());
+        assertEquals(subSlice, item.getSlice());
+    }
+
+    @Test
+    public void testSubsliceSubtype() {
+        Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build()).build();
+        Slice s = new Slice.Builder(BASE_URI)
+                .addSubSlice(subSlice, "subtype")
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(SliceItem.FORMAT_SLICE, item.getFormat());
+        assertEquals("subtype", item.getSubType());
+        assertEquals(subSlice, item.getSlice());
+    }
+
+    @Test
+    public void testSpec() {
+        Slice s = new Slice.Builder(BASE_URI)
+                .setSpec(new SliceSpec("spec", 3))
+                .build();
+        assertEquals(BASE_URI, s.getUri());
+        assertEquals(0, s.getItems().size());
+        assertEquals(new SliceSpec("spec", 3), s.getSpec());
+    }
+}
diff --git a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
new file mode 100644
index 0000000..a2ca765
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.slice.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.app.slice.Slice;
+import android.app.slice.SliceManager;
+import android.app.slice.SliceManager.SliceCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+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 org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.verification.Timeout;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+public class SliceManagerTest {
+
+    private static final Uri BASE_URI = Uri.parse("content://android.slice.cts.local/main");
+    private final Context mContext = InstrumentationRegistry.getContext();
+    private final SliceManager mSliceManager = mContext.getSystemService(SliceManager.class);
+
+    private String mSetupLauncher;
+
+    @Before
+    public void setup() {
+        LocalSliceProvider.sProxy = mock(SliceProvider.class);
+        try {
+            mSetupLauncher = getDefaultLauncher();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        try {
+            mSliceManager.unpinSlice(BASE_URI);
+        } catch (Exception e) {
+        }
+    }
+
+    @After
+    public void teardown() throws Exception {
+        try {
+            mSliceManager.unpinSlice(BASE_URI);
+        } catch (Exception e) {
+        }
+        if (mSetupLauncher != null) {
+            setLauncher(mSetupLauncher);
+        }
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testNoAccess() {
+        mSliceManager.pinSlice(BASE_URI, Collections.emptyList());
+        fail();
+    }
+
+    @Test
+    public void testPinSlice() throws Exception {
+        setLauncher(new ComponentName(mContext.getPackageName(), Launcher.class.getName())
+                .flattenToString());
+        mSliceManager.pinSlice(BASE_URI, Collections.emptyList());
+
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSlicePinned(eq(BASE_URI));
+    }
+
+    @Test
+    public void testUnpinSlice() throws Exception {
+        setLauncher(new ComponentName(mContext.getPackageName(), Launcher.class.getName())
+                .flattenToString());
+
+        mSliceManager.pinSlice(BASE_URI, Collections.emptyList());
+
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSlicePinned(eq(BASE_URI));
+
+        mSliceManager.unpinSlice(BASE_URI);
+
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSliceUnpinned(eq(BASE_URI));
+    }
+
+    @Test
+    public void testRegisterPin() {
+        SliceCallback callback = mock(SliceCallback.class);
+
+        mSliceManager.registerSliceCallback(BASE_URI, Collections.emptyList(), callback);
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSlicePinned(eq(BASE_URI));
+
+        mSliceManager.unregisterSliceCallback(BASE_URI, callback);
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSliceUnpinned(eq(BASE_URI));
+    }
+
+    @Test
+    public void testCallback() {
+        SliceCallback callback = mock(SliceCallback.class);
+
+        mSliceManager.registerSliceCallback(BASE_URI, Collections.emptyList(),
+                command -> command.run(), callback);
+        verify(LocalSliceProvider.sProxy, timeout(2000)).onSlicePinned(eq(BASE_URI));
+
+        try {
+            Slice s = new Slice.Builder(BASE_URI).build();
+            when(LocalSliceProvider.sProxy.onBindSlice(any(), any())).thenReturn(s);
+
+            mContext.getContentResolver().notifyChange(BASE_URI, null);
+            verify(callback, new Timeout(2000, atLeastOnce())).onSliceUpdated(any());
+        } finally {
+            mSliceManager.unregisterSliceCallback(BASE_URI, callback);
+            verify(LocalSliceProvider.sProxy, timeout(2000)).onSliceUnpinned(eq(BASE_URI));
+        }
+    }
+
+    @Test
+    public void testMapIntentToUri() {
+        Intent intent = new Intent("android.slice.cts.action.TEST_ACTION");
+        intent.setPackage("android.slice.cts");
+        intent.putExtra("path", "intent");
+
+        when(LocalSliceProvider.sProxy.onMapIntentToUri(any())).then(
+                (Answer<Uri>) invocation -> BASE_URI.buildUpon().path(
+                        ((Intent) invocation.getArguments()[0]).getStringExtra("path")).build());
+
+        Uri uri = mSliceManager.mapIntentToUri(intent);
+
+        assertEquals(BASE_URI.buildUpon().path("intent").build(), uri);
+        verify(LocalSliceProvider.sProxy).onMapIntentToUri(eq(intent));
+    }
+
+    public static String getDefaultLauncher() throws Exception {
+        final String PREFIX = "Launcher: ComponentInfo{";
+        final String POSTFIX = "}";
+        for (String s : runShellCommand("cmd shortcut get-default-launcher")) {
+            if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
+                return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
+            }
+        }
+        throw new Exception("Default launcher not found");
+    }
+
+    public static void setLauncher(String component) throws Exception {
+        runShellCommand("cmd package set-home-activity --user "
+                + getInstrumentation().getContext().getUserId() + " " + component);
+    }
+
+    public static ArrayList<String> runShellCommand(String command) throws Exception {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+
+        ArrayList<String> ret = new ArrayList<>();
+        // Read the input stream fully.
+        try (BufferedReader r = new BufferedReader(
+                new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd)))) {
+            String line;
+            while ((line = r.readLine()) != null) {
+                ret.add(line);
+            }
+        }
+        return ret;
+    }
+
+    public static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+}
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
new file mode 100644
index 0000000..ecf7049
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
@@ -0,0 +1,158 @@
+/*
+ * 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.slice.cts;
+
+import static java.util.stream.Collectors.toList;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.Slice.Builder;
+import android.app.slice.SliceSpec;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class SliceProvider extends android.app.slice.SliceProvider {
+
+    static final String[] PATHS = new String[]{
+            "/set_flag",
+            "/subslice",
+            "/text",
+            "/icon",
+            "/action",
+            "/int",
+            "/timestamp",
+            "/hints",
+            "/bundle",
+            "/spec",
+    };
+
+    public static final String SPEC_TYPE = "android.cts.SliceType";
+    public static final int SPEC_REV = 4;
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Collection<Uri> onGetSliceDescendants(Uri uri) {
+        if (uri.getPath().equals("/")) {
+            Uri.Builder builder = new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority("android.slice.cts");
+            return Arrays.asList(PATHS).stream().map(s ->
+                    builder.path(s).build()).collect(toList());
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri, List<SliceSpec> specs) {
+        switch (sliceUri.getPath()) {
+            case "/set_flag":
+                SliceBindingTest.sFlag = true;
+                break;
+            case "/subslice":
+                Builder b = new Builder(sliceUri);
+                return b.addSubSlice(new Slice.Builder(b).build(), "subslice").build();
+            case "/text":
+                return new Slice.Builder(sliceUri).addText("Expected text", "text").build();
+            case "/icon":
+                return new Slice.Builder(sliceUri).addIcon(
+                        Icon.createWithResource(getContext(), R.drawable.size_48x48), "icon").build();
+            case "/action":
+                Builder builder = new Builder(sliceUri);
+                Slice subSlice = new Slice.Builder(builder).build();
+                PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
+                        new Intent(getContext().getPackageName() + ".action"), 0);
+                return builder.addAction(broadcast, subSlice, "action").build();
+            case "/int":
+                return new Slice.Builder(sliceUri).addInt(0xff121212, "int").build();
+            case "/timestamp":
+                return new Slice.Builder(sliceUri).addTimestamp(43, "timestamp").build();
+            case "/hints":
+                return new Slice.Builder(sliceUri)
+                        .addHints(Slice.HINT_LIST)
+                        .addText("Text", null, Slice.HINT_TITLE)
+                        .addIcon(Icon.createWithResource(getContext(), R.drawable.size_48x48),
+                                null, Slice.HINT_NO_TINT, Slice.HINT_LARGE)
+                        .build();
+            case "/bundle":
+                Bundle b1 = new Bundle();
+                b1.putParcelable("a", new TestParcel());
+                return new Slice.Builder(sliceUri).addBundle(b1, "bundle").build();
+            case "/spec":
+                return new Slice.Builder(sliceUri)
+                        .setSpec(new SliceSpec(SPEC_TYPE, SPEC_REV))
+                        .build();
+        }
+        return new Slice.Builder(sliceUri).build();
+    }
+
+    public static class TestParcel implements Parcelable {
+
+        private final int mValue;
+
+        public TestParcel() {
+            mValue = 42;
+        }
+
+        protected TestParcel(Parcel in) {
+            mValue = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            try {
+                TestParcel p = (TestParcel) obj;
+                return p.mValue == mValue;
+            } catch (ClassCastException e) {
+                return false;
+            }
+        }
+
+        public static final Creator<TestParcel> CREATOR = new Creator<TestParcel>() {
+            @Override
+            public TestParcel createFromParcel(Parcel in) {
+                return new TestParcel(in);
+            }
+
+            @Override
+            public TestParcel[] newArray(int size) {
+                return new TestParcel[size];
+            }
+        };
+    }
+}
diff --git a/tests/tests/speech/Android.mk b/tests/tests/speech/Android.mk
index 9e89c48..7fb9124 100755
--- a/tests/tests/speech/Android.mk
+++ b/tests/tests/speech/Android.mk
@@ -23,8 +23,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    android-support-test \
-    legacy-android-test
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/speech/AndroidTest.xml b/tests/tests/speech/AndroidTest.xml
index 9cc3132..adc4fab 100644
--- a/tests/tests/speech/AndroidTest.xml
+++ b/tests/tests/speech/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Speech 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" />
diff --git a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
index 857ffd8..6b3abf6 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
@@ -15,6 +15,7 @@
  */
 package android.speech.tts.cts;
 
+import android.os.ConditionVariable;
 import android.media.AudioFormat;
 import android.os.ConditionVariable;
 import android.speech.tts.SynthesisCallback;
@@ -37,6 +38,10 @@
     // Object that onSynthesizeText will #block on, if set to non-null
     public static volatile ConditionVariable sSynthesizeTextWait;
 
+    // Condition variable that onSynthesizeText will #open when it started
+    // synethesizing, if set to non-null.
+    public static volatile ConditionVariable sSynthesizeTextStartEvent;
+
     private ArrayList<Locale> supportedLanguages = new ArrayList<Locale>();
     private ArrayList<Locale> supportedCountries = new ArrayList<Locale>();
     private ArrayList<Locale> GBFallbacks = new ArrayList<Locale>();
@@ -80,6 +85,11 @@
             return;
         }
 
+        final ConditionVariable synthesizeTextStartEvent = sSynthesizeTextStartEvent;
+        if (synthesizeTextStartEvent != null) {
+            sSynthesizeTextStartEvent.open();
+        }
+
         final ConditionVariable synthesizeTextWait = sSynthesizeTextWait;
         if (synthesizeTextWait != null) {
             synthesizeTextWait.block(10000);  // 10s timeout
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 4d9faad..8e54d31 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -117,6 +117,27 @@
         }
     }
 
+    public void testSpeakStopBehindOtherAudioPlayback() throws Exception {
+        final ConditionVariable synthesizeTextWait = new ConditionVariable();
+        final ConditionVariable synthesizeTextStartEvent = new ConditionVariable();
+        StubTextToSpeechService.sSynthesizeTextWait = synthesizeTextWait;
+        StubTextToSpeechService.sSynthesizeTextStartEvent = synthesizeTextStartEvent;
+
+        // Make the audio playback queue busy by putting a 30s of silence.
+        getTts().stop();
+        getTts().playSilentUtterance(30000, TextToSpeech.QUEUE_ADD, "silence");
+
+        // speak(), wait it to starting in the service, and stop().
+        int result = getTts().speak(UTTERANCE, TextToSpeech.QUEUE_ADD, null, "stop");
+        assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
+        assertTrue("synthesis not started", synthesizeTextStartEvent.block(10000));
+        getTts().stop();
+
+        // Wake up the Stubs #onSynthesizeSpeech (one that will be stopped in-progress)
+        synthesizeTextWait.open();
+
+        assertTrue("speak() stop callback timeout", mTts.waitForStop("stop"));
+    }
 
     public void testMediaPlayerFails() throws Exception {
         File sampleFile = new File(Environment.getExternalStorageDirectory(), "notsound.wav");
diff --git a/tests/tests/syncmanager/Android.mk b/tests/tests/syncmanager/Android.mk
new file mode 100755
index 0000000..252010a
--- /dev/null
+++ b/tests/tests/syncmanager/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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    CtsSyncManagerCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSyncManagerTestsCases
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/syncmanager/AndroidManifest.xml b/tests/tests/syncmanager/AndroidManifest.xml
new file mode 100755
index 0000000..69ec2a8
--- /dev/null
+++ b/tests/tests/syncmanager/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.content.syncmanager.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.syncmanager.cts"
+        android:label="CTS tests for sync manager">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
new file mode 100644
index 0000000..0bf4227
--- /dev/null
+++ b/tests/tests/syncmanager/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 ShortcutManager CTS 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">
+        <!-- Disable keyguard -->
+        <option name="run-command" value="locksettings set-disabled true" />
+    </target_preparer>
+
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" /> <!-- DO NOT SUBMIT WITH FALSE -->
+        <option name="test-file-name" value="CtsSyncManagerTestsCases.apk" />
+        <option name="test-file-name" value="CtsSyncManagerApp1.apk" />
+        <option name="test-file-name" value="CtsSyncManagerApp2.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <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" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.content.syncmanager.cts" />
+        <option name="runtime-hint" value="10m00s" />
+    </test>
+</configuration>
diff --git a/tests/tests/syncmanager/apps/Android.mk b/tests/tests/syncmanager/apps/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/tests/tests/syncmanager/apps/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/tests/tests/syncmanager/apps/app1/Android.mk b/tests/tests/syncmanager/apps/app1/Android.mk
new file mode 100644
index 0000000..ecc37cb
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/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 := CtsSyncManagerApp1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    CtsSyncManagerCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ub-uiautomator
+
+LOCAL_SDK_VERSION := test_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/syncmanager/apps/app1/AndroidManifest.xml b/tests/tests/syncmanager/apps/app1/AndroidManifest.xml
new file mode 100755
index 0000000..bc5c8c4
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/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="android.content.syncmanager.cts.app1">
+
+    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
+    <application>
+        <receiver android:name="android.content.syncmanager.cts.app.CommReceiver"
+            android:enabled="true" android:exported="true">
+        </receiver>
+
+
+        <service android:name="android.content.syncmanager.cts.app.SyncManagerCtsAuthenticator"
+            android:exported="false">
+            <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="android.content.syncmanager.cts.app.SyncManagerCtsSyncService"
+            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="android.content.syncmanager.cts.app.SyncManagerCtsProvider"
+            android:authorities="android.content.syncmanager.cts.app.provider1" />
+
+    </application>
+</manifest>
+
diff --git a/tests/tests/syncmanager/apps/app1/res/values/strings.xml b/tests/tests/syncmanager/apps/app1/res/values/strings.xml
new file mode 100644
index 0000000..dd3dc84
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/res/values/strings.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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">SyncManager CTS app1</string>
+</resources>
diff --git a/tests/tests/syncmanager/apps/app1/res/xml/authenticator.xml b/tests/tests/syncmanager/apps/app1/res/xml/authenticator.xml
new file mode 100644
index 0000000..8d956ba
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/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.content.syncmanager.cts.app1"
+    android:label="@string/app_name" />
diff --git a/tests/tests/syncmanager/apps/app1/res/xml/syncadapter.xml b/tests/tests/syncmanager/apps/app1/res/xml/syncadapter.xml
new file mode 100644
index 0000000..2d68ca6
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/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.content.syncmanager.cts.app.provider1"
+    android:accountType="android.content.syncmanager.cts.app1"
+/>
diff --git a/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/CommReceiver.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/CommReceiver.java
new file mode 100644
index 0000000..d046a57
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/CommReceiver.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.content.syncmanager.cts.app;
+
+import android.content.Context;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Response.SyncInvocations;
+
+import com.android.compatibility.common.util.BroadcastRpcBase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class CommReceiver extends BroadcastRpcBase.ReceiverBase<Payload, Payload> {
+    @Override
+    protected Payload handleRequest(Context context, Payload request) throws Exception {
+        ContextHolder.sContext = context;
+
+        final Request req = request.getRequest();
+        final Payload.Response.Builder res = Payload.Response.newBuilder();
+
+        if (req.hasAddAccount()) {
+            SyncManagerCtsAuthenticator.ensureTestAccount(req.getAddAccount().getName());
+
+        } else if (req.hasRemoveAllAccounts()) {
+            SyncManagerCtsAuthenticator.removeAllAccounts();
+
+        } else if (req.hasClearSyncInvocations()) {
+            SyncManagerCtsSyncAdapter.clearSyncInvocations();
+
+        } else if (req.hasGetSyncInvocations()) {
+            res.setSyncInvocations(SyncInvocations.newBuilder()
+                    .addAllSyncInvocations(SyncManagerCtsSyncAdapter.getSyncInvocations()));
+
+        } else if (req.hasSetResult()) {
+            SyncManagerCtsSyncAdapter.setResult(req.getSetResult());
+
+        }
+
+        return Payload.newBuilder().setResponse(res).build();
+    }
+
+    @Override
+    protected byte[] responseToBytes(Payload payload) {
+        return payload.toByteArray();
+    }
+
+    @Override
+    protected Payload bytesToRequest(byte[] bytes) {
+        try {
+            return Payload.parseFrom(bytes);
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("InvalidProtocolBufferException", e);
+        }
+    }
+}
diff --git a/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/ContextHolder.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/ContextHolder.java
new file mode 100644
index 0000000..0c99e8f
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/ContextHolder.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.content.syncmanager.cts.app;
+
+import android.content.Context;
+
+public class ContextHolder {
+    static volatile Context sContext;
+
+    public static Context getContext() {
+        return sContext;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsAuthenticator.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsAuthenticator.java
new file mode 100644
index 0000000..8a367bc
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsAuthenticator.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.syncmanager.cts.app;
+
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.fail;
+
+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;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Authenticator for the sync test.
+ */
+public class SyncManagerCtsAuthenticator extends Service {
+    private static final String TAG = "SyncManagerCtsAuthenticator";
+
+    private static Authenticator sInstance;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (sInstance == null) {
+            sInstance = new Authenticator(getApplicationContext());
+
+        }
+        return sInstance.getIBinder();
+    }
+
+    static AccountManager getAccountManager() {
+        return ContextHolder.getContext().getSystemService(AccountManager.class);
+    }
+
+    static String getAccountType() {
+        return ContextHolder.getContext().getPackageName();
+    }
+
+    /**
+     * Adds a test account, if it doesn't exist yet.
+     */
+    public static void ensureTestAccount(String name) {
+
+        final Account account = new Account(name, getAccountType());
+
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+
+        if (!Arrays.asList(getAccountManager().getAccountsByType(account.type)).contains(account)) {
+            Log.i(TAG, "Adding account: " + account);
+            getAccountManager().addAccountExplicitly(account, "password", new Bundle());
+        }
+    }
+
+    /**
+     * Remove the test account.
+     */
+    public static void removeAllAccounts() throws Exception {
+        final AccountManager am = getAccountManager();
+
+        for (Account account : am.getAccountsByType(getAccountType())) {
+            Log.i(TAG, "Removing " + account + "...");
+            am.removeAccountExplicitly(account);
+        }
+        Log.i(TAG, "Removed all accounts for " + getAccountType());
+
+        waitUntil("Accounts still exist.",
+                () -> am.getAccountsByType(getAccountType()).length == 0);
+    }
+
+    public static class Authenticator extends AbstractAccountAuthenticator {
+
+        private final Context mContext;
+
+        public Authenticator(Context context) {
+            super(context);
+            mContext = context;
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] requiredFeatures, Bundle options)
+                throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+                Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public String getAuthTokenLabel(String authTokenType) {
+            return "token_label";
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+                String[] features) throws NetworkErrorException {
+            return new Bundle();
+        }
+    }
+}
diff --git a/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsProvider.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsProvider.java
new file mode 100644
index 0000000..bfc28a8
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsProvider.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.content.syncmanager.cts.app;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class SyncManagerCtsProvider extends ContentProvider {
+    @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/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncAdapter.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncAdapter.java
new file mode 100644
index 0000000..00b76c0
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncAdapter.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.content.syncmanager.cts.app;
+
+import static org.mockito.Mockito.mock;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncResult;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult.Result;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.SyncInvocation;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ParcelUtils;
+
+import com.google.protobuf.ByteString;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Sync adapter for the sync test.
+ */
+public class SyncManagerCtsSyncAdapter extends AbstractThreadedSyncAdapter {
+    private static final String TAG = "SyncManagerCtsSyncAdapter";
+
+    public SyncManagerCtsSyncAdapter(Context context) {
+        super(context, /* autoInitialize= */ false);
+    }
+
+    private static final Object sLock = new Object();
+
+    private static List<SyncInvocation> sSyncInvocations = new ArrayList<>();
+
+    private static SetResult sResult;
+
+    public interface SyncInterceptor {
+        SyncResult onPerformSync(Account account, Bundle extras, String authority,
+                SyncResult syncResult);
+    }
+
+    public static final SyncInterceptor sSyncInterceptor = mock(SyncInterceptor.class);
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+        try {
+            extras.size(); // Force unparcel extras.
+            Log.i(TAG, "onPerformSync: account=" + account + " authority=" + authority
+                    + " extras=" + extras);
+
+            synchronized (sSyncInvocations) {
+                sSyncInvocations.add(SyncInvocation.newBuilder()
+                        .setTime(SystemClock.elapsedRealtime())
+                        .setAccountName(account.name)
+                        .setAccountType(account.type)
+                        .setAuthority(authority)
+                        .setExtras(ByteString.copyFrom(ParcelUtils.toBytes(extras))).build());
+            }
+
+            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
+                getContext().getContentResolver().setIsSyncable(account, authority, 1);
+            }
+
+            sSyncInterceptor.onPerformSync(account, extras, authority, syncResult);
+
+            synchronized (sLock) {
+                if ((sResult == null) || sResult.getResult() == Result.OK) {
+                    syncResult.stats.numInserts++;
+
+                } else if (sResult.getResult() == Result.SOFT_ERROR) {
+                    syncResult.stats.numIoExceptions++;
+
+                } else if (sResult.getResult() == Result.HARD_ERROR) {
+                    syncResult.stats.numAuthExceptions++;
+                }
+            }
+
+        } catch (Throwable th) {
+            Log.e(TAG, "Exception in onPerformSync", th);
+        }
+        Log.i(TAG, "onPerformSyncFinishing: account=" + account + " authority=" + authority
+                + " extras=" + extras + " result=" + syncResult);
+    }
+
+    public static List<SyncInvocation> getSyncInvocations() {
+        synchronized (sLock) {
+            return new ArrayList<>(sSyncInvocations);
+        }
+    }
+
+    public static void clearSyncInvocations() {
+        synchronized (sLock) {
+            sSyncInvocations.clear();
+        }
+    }
+
+    public static void setResult(SetResult result) {
+        synchronized (sLock) {
+            sResult = result;
+        }
+    }
+}
diff --git a/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncService.java b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncService.java
new file mode 100644
index 0000000..fd741cb
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app1/src/android/content/syncmanager/cts/app/SyncManagerCtsSyncService.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.content.syncmanager.cts.app;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service for the sync test.
+ */
+public class SyncManagerCtsSyncService extends Service {
+
+    private static SyncManagerCtsSyncAdapter sAdapter;
+
+    @Override
+    public synchronized IBinder onBind(Intent intent) {
+        if (sAdapter == null) {
+            sAdapter = new SyncManagerCtsSyncAdapter(getApplicationContext());
+        }
+        return sAdapter.getSyncAdapterBinder();
+    }
+}
diff --git a/tests/tests/syncmanager/apps/app2/Android.mk b/tests/tests/syncmanager/apps/app2/Android.mk
new file mode 100644
index 0000000..a124671
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app2/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 := CtsSyncManagerApp2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../app1/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    CtsSyncManagerCommon \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    compatibility-device-util \
+    ub-uiautomator
+
+LOCAL_SDK_VERSION := test_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/syncmanager/apps/app2/AndroidManifest.xml b/tests/tests/syncmanager/apps/app2/AndroidManifest.xml
new file mode 100755
index 0000000..82da8af
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app2/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.syncmanager.cts.app2">
+
+    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
+    <application>
+        <receiver android:name="android.content.syncmanager.cts.app.CommReceiver"
+            android:enabled="true" android:exported="true">
+        </receiver>
+
+
+        <service android:name="android.content.syncmanager.cts.app.SyncManagerCtsAuthenticator"
+            android:exported="false">
+            <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="android.content.syncmanager.cts.app.SyncManagerCtsSyncService"
+            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="android.content.syncmanager.cts.app.SyncManagerCtsProvider"
+            android:authorities="android.content.syncmanager.cts.app.provider2" />
+
+    </application>
+</manifest>
+
diff --git a/tests/tests/syncmanager/apps/app2/res/values/strings.xml b/tests/tests/syncmanager/apps/app2/res/values/strings.xml
new file mode 100644
index 0000000..1c34156
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app2/res/values/strings.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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">SyncManager CTS app2</string>
+</resources>
diff --git a/tests/tests/syncmanager/apps/app2/res/xml/authenticator.xml b/tests/tests/syncmanager/apps/app2/res/xml/authenticator.xml
new file mode 100644
index 0000000..f8b2000
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app2/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.content.syncmanager.cts.app2"
+    android:label="@string/app_name" />
diff --git a/tests/tests/syncmanager/apps/app2/res/xml/syncadapter.xml b/tests/tests/syncmanager/apps/app2/res/xml/syncadapter.xml
new file mode 100644
index 0000000..e30379d
--- /dev/null
+++ b/tests/tests/syncmanager/apps/app2/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.content.syncmanager.cts.app.provider2"
+    android:accountType="android.content.syncmanager.cts.app2"
+/>
diff --git a/tests/tests/syncmanager/common/Android.mk b/tests/tests/syncmanager/common/Android.mk
new file mode 100644
index 0000000..0722002
--- /dev/null
+++ b/tests/tests/syncmanager/common/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-proto-files-under, proto)
+
+LOCAL_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target \
+    compatibility-device-util \
+    android.test.runner.stubs
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := CtsSyncManagerCommon
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/syncmanager/common/proto/sync_manager_cts.proto b/tests/tests/syncmanager/common/proto/sync_manager_cts.proto
new file mode 100644
index 0000000..953dc28
--- /dev/null
+++ b/tests/tests/syncmanager/common/proto/sync_manager_cts.proto
@@ -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.
+ */
+syntax = "proto2";
+
+option java_outer_classname = "SyncManagerCtsProto";
+
+package android.content.syncmanager.cts;
+
+message Payload {
+    message SyncInvocation {
+        optional int64 time = 1;
+        optional string account_name = 2;
+        optional string account_type = 3;
+        optional string authority = 4;
+        optional bytes extras = 5;
+    }
+
+    message Request {
+        message AddAccount {
+            optional string name = 1;
+        }
+        optional AddAccount add_account = 1;
+
+        message RemoveAllAccounts {
+        }
+        optional RemoveAllAccounts remove_all_accounts = 2;
+
+        message ClearSyncInvocations {
+        }
+        optional ClearSyncInvocations clear_sync_invocations = 3;
+
+        message GetSyncInvocations {
+        }
+        optional GetSyncInvocations get_sync_invocations = 4;
+
+        message SyncRequest {
+            optional SyncInvocation parameters = 1;
+        }
+        optional SyncRequest sync_request = 5;
+
+        message SetResult {
+            enum Result {
+                OK = 0;
+                SOFT_ERROR = 1;
+                HARD_ERROR = 2;
+            }
+
+            optional Result result = 1;
+        }
+        optional SetResult set_result = 6;
+    }
+
+    optional Request request = 1;
+
+    message Response {
+        message SyncInvocations {
+            repeated SyncInvocation sync_invocations = 1;
+        }
+        optional SyncInvocations sync_invocations = 1;
+    }
+    optional Response response = 2;
+}
diff --git a/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/ProtoUtils.java b/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/ProtoUtils.java
new file mode 100644
index 0000000..0dbfa3b
--- /dev/null
+++ b/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/ProtoUtils.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 android.content.syncmanager.cts.common;
+
+public class ProtoUtils {
+    private ProtoUtils() {
+    }
+}
diff --git a/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/Values.java b/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/Values.java
new file mode 100644
index 0000000..31e374c
--- /dev/null
+++ b/tests/tests/syncmanager/common/src/android/content/syncmanager/cts/common/Values.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.content.syncmanager.cts.common;
+
+import android.accounts.Account;
+import android.content.ComponentName;
+
+public class Values {
+    public static final String APP1_PACKAGE = "android.content.syncmanager.cts.app1";
+    public static final String APP2_PACKAGE = "android.content.syncmanager.cts.app2";
+
+    public static final Account ACCOUNT_1_A = new Account("accountA", APP1_PACKAGE);
+
+    public static final String APP1_AUTHORITY = "android.content.syncmanager.cts.app.provider1";
+    public static final String APP2_AUTHORITY = "android.content.syncmanager.cts.app.provider2";
+
+    public static final String COMM_RECEIVER = "android.content.syncmanager.cts.app.CommReceiver";
+
+    public static final ComponentName APP1_RECEIVER = getCommReceiver(APP1_PACKAGE);
+    public static final ComponentName APP2_RECEIVER = getCommReceiver(APP2_PACKAGE);
+
+    public static ComponentName getCommReceiver(String packageName) {
+        return new ComponentName(packageName, COMM_RECEIVER);
+    }
+}
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/BroadcastRpc.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/BroadcastRpc.java
new file mode 100644
index 0000000..2606f6a
--- /dev/null
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/BroadcastRpc.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.content.syncmanager.cts;
+
+import static android.content.syncmanager.cts.common.Values.getCommReceiver;
+
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.Builder;
+
+import com.android.compatibility.common.util.BroadcastRpcBase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.function.Consumer;
+
+public class BroadcastRpc extends BroadcastRpcBase<Payload, Payload> {
+    @Override
+    protected byte[] requestToBytes(Payload testServiceRequest) {
+        return testServiceRequest.toByteArray();
+    }
+
+    @Override
+    protected Payload bytesToResponse(byte[] bytes) {
+        try {
+            return Payload.parseFrom(bytes);
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("InvalidProtocolBufferException", e);
+        }
+    }
+
+    public Payload.Response invoke(String targetPackage, Consumer<Builder> c) throws Exception {
+        final Builder rb =  Request.newBuilder();
+        c.accept(rb);
+        final Request req = rb.build();
+        return super.invoke(getCommReceiver(targetPackage),
+                Payload.newBuilder().setRequest(req).build()).getResponse();
+    }
+}
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
new file mode 100644
index 0000000..7d9cdbe
--- /dev/null
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.syncmanager.cts;
+
+import static android.content.syncmanager.cts.common.Values.ACCOUNT_1_A;
+import static android.content.syncmanager.cts.common.Values.APP1_AUTHORITY;
+import static android.content.syncmanager.cts.common.Values.APP1_PACKAGE;
+
+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.TestUtils.waitUntil;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accounts.Account;
+import android.app.usage.UsageStatsManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.AddAccount;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.ClearSyncInvocations;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.GetSyncInvocations;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.RemoveAllAccounts;
+import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult;
+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.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.OnFailureRule;
+import com.android.compatibility.common.util.ParcelUtils;
+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.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+
+// TODO Don't run if no network is available.
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class CtsSyncManagerTest {
+    private static final String TAG = "CtsSyncManagerTest";
+
+    public static final int DEFAULT_TIMEOUT_SECONDS = 30;
+
+    public static final boolean DEBUG = false;
+
+    @Rule
+    public final OnFailureRule mDumpOnFailureRule = new OnFailureRule() {
+        @Override
+        protected void onTestFailure(Statement base, Description description, Throwable t) {
+            runCommandAndPrintOnLogcat(TAG, "dumpsys content");
+            runCommandAndPrintOnLogcat(TAG, "dumpsys jobscheduler");
+        }
+    };
+
+    protected final BroadcastRpc mRpc = new BroadcastRpc();
+
+    Context mContext;
+    ContentResolver mContentResolver;
+
+    @Before
+    public void setUp() throws Exception {
+        assertNetworkConnected(InstrumentationRegistry.getContext());
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+
+        AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_ACTIVE);
+
+        mContext = InstrumentationRegistry.getContext();
+        mContentResolver = mContext.getContentResolver();
+
+        ContentResolver.setMasterSyncAutomatically(true);
+
+        Thread.sleep(1000); // Don't make the system too busy...
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        resetSyncConfig();
+        BatteryUtils.runDumpsysBatteryReset();
+    }
+
+    private static void resetSyncConfig() {
+        putGlobalSetting("sync_manager_constants", "null");
+    }
+
+    private static void writeSyncConfig(
+            int initialSyncRetryTimeInSeconds,
+            float retryTimeIncreaseFactor,
+            int maxSyncRetryTimeInSeconds,
+            int maxRetriesWithAppStandbyExemption) {
+        putGlobalSetting("sync_manager_constants",
+                "initial_sync_retry_time_in_seconds=" + initialSyncRetryTimeInSeconds + "," +
+                "retry_time_increase_factor=" + retryTimeIncreaseFactor + "," +
+                "max_sync_retry_time_in_seconds=" + maxSyncRetryTimeInSeconds + "," +
+                "max_retries_with_app_standby_exemption=" + maxRetriesWithAppStandbyExemption);
+    }
+
+    /** Return the part of "dumpsys content" that's relevant to the current sync status. */
+    private String getSyncDumpsys() {
+        final String out = SystemUtil.runCommandAndExtractSection("dumpsys content",
+                "^Active Syncs:.*", false,
+                "^Sync Statistics", false);
+        return out;
+    }
+
+    private void removeAllAccounts() throws Exception {
+        mRpc.invoke(APP1_PACKAGE,
+                rb -> rb.setRemoveAllAccounts(RemoveAllAccounts.newBuilder()));
+
+        Thread.sleep(1000);
+
+        waitUntil("Dumpsys still mentions " + ACCOUNT_1_A,
+                () -> !getSyncDumpsys().contains(ACCOUNT_1_A.name));
+
+        Thread.sleep(1000);
+    }
+
+    private void clearSyncInvocations(String packageName) throws Exception {
+        mRpc.invoke(packageName,
+                rb -> rb.setClearSyncInvocations(ClearSyncInvocations.newBuilder()));
+    }
+
+    private void addAccountAndLetInitialSyncRun(Account account, String authority)
+            throws Exception {
+        // Add the first account, which will trigger an initial sync.
+        mRpc.invoke(APP1_PACKAGE,
+                rb -> rb.setAddAccount(AddAccount.newBuilder().setName(account.name)));
+
+        waitUntil("Syncable isn't initialized",
+                () -> ContentResolver.getIsSyncable(account, authority) == 1);
+
+        waitUntil("Periodic sync should set up",
+                () -> ContentResolver.getPeriodicSyncs(account, authority).size() == 1);
+        assertEquals("Periodic should be 24h",
+                24 * 60 * 60, ContentResolver.getPeriodicSyncs(account, authority).get(0).period);
+    }
+
+    @Test
+    public void testInitialSync() throws Exception {
+        removeAllAccounts();
+
+        mRpc.invoke(APP1_PACKAGE, rb -> rb.setClearSyncInvocations(
+                ClearSyncInvocations.newBuilder()));
+
+        // Add the first account, which will trigger an initial sync.
+        addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY);
+
+        // Check the sync request parameters.
+
+        Response res = mRpc.invoke(APP1_PACKAGE,
+                rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder()));
+        assertEquals(1, res.getSyncInvocations().getSyncInvocationsCount());
+
+        SyncInvocation si = res.getSyncInvocations().getSyncInvocations(0);
+
+        assertEquals(ACCOUNT_1_A.name, si.getAccountName());
+        assertEquals(ACCOUNT_1_A.type, si.getAccountType());
+        assertEquals(APP1_AUTHORITY, si.getAuthority());
+
+        Bundle extras = ParcelUtils.fromBytes(si.getExtras().toByteArray());
+        assertTrue(extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+    }
+
+    @Test
+    public void testSoftErrorRetriesActiveApp() throws Exception {
+        removeAllAccounts();
+
+        // Let the initial sync happen.
+        addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY);
+
+        writeSyncConfig(2, 1, 2, 3);
+
+        clearSyncInvocations(APP1_PACKAGE);
+
+        AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_ACTIVE);
+
+        // Set soft error.
+        mRpc.invoke(APP1_PACKAGE, rb ->
+                rb.setSetResult(SetResult.newBuilder().setResult(Result.SOFT_ERROR)));
+
+        Bundle b = makeBundle(
+                "testSoftErrorRetriesActiveApp", true,
+                ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+
+        ContentResolver.requestSync(ACCOUNT_1_A, APP1_AUTHORITY, b);
+
+        // First sync + 3 retries == 4, so should be called more than 4 times.
+        // But it's active, so it should retry more than that.
+        waitUntil("Should retry more than 3 times.", () -> {
+            final Response res = mRpc.invoke(APP1_PACKAGE,
+                    rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder()));
+            final int calls =  res.getSyncInvocations().getSyncInvocationsCount();
+            Log.i(TAG, "NumSyncInvocations=" + calls);
+            return calls > 7; // Arbitrarily bigger than 4.
+        });
+    }
+
+    // WIP This test doesn't work yet.
+//    @Test
+//    public void testSoftErrorRetriesFrequentApp() throws Exception {
+//        runTest(() -> {
+//            removeAllAccounts();
+//
+//            // Let the initial sync happen.
+//            addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY);
+//
+//            writeSyncConfig(2, 1, 2, 3);
+//
+//            clearSyncInvocations(APP1_PACKAGE);
+//
+//            AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_FREQUENT);
+//
+//            // Set soft error.
+//            mRpc.invoke(APP1_PACKAGE, rb ->
+//                    rb.setSetResult(SetResult.newBuilder().setResult(Result.SOFT_ERROR)));
+//
+//            Bundle b = makeBundle(
+//                    "testSoftErrorRetriesFrequentApp", true,
+//                    ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+//
+//            ContentResolver.requestSync(ACCOUNT_1_A, APP1_AUTHORITY, b);
+//
+//            waitUntil("Should retry more than 3 times.", () -> {
+//                final Response res = mRpc.invoke(APP1_PACKAGE,
+//                        rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder()));
+//                final int calls =  res.getSyncInvocations().getSyncInvocationsCount();
+//                Log.i(TAG, "NumSyncInvocations=" + calls);
+//                return calls >= 4; // First sync + 3 retries == 4, so at least 4 times.
+//            });
+//
+//            Thread.sleep(10_000);
+//
+//            // One more retry is okay because of how the job scheduler throttle jobs, but no further.
+//            final Response res = mRpc.invoke(APP1_PACKAGE,
+//                    rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder()));
+//            final int calls =  res.getSyncInvocations().getSyncInvocationsCount();
+//            assertTrue("# of syncs must be equal or less than 5, but was " + calls, calls <= 5);
+//        });
+//    }
+}
diff --git a/tests/tests/systemintents/Android.mk b/tests/tests/systemintents/Android.mk
index 573e2ee..09ac3314 100644
--- a/tests/tests/systemintents/Android.mk
+++ b/tests/tests/systemintents/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_PACKAGE_NAME := CtsSystemIntentTestCases
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/tests/tests/systemintents/AndroidTest.xml b/tests/tests/systemintents/AndroidTest.xml
index 37f9895..4f0cc36 100644
--- a/tests/tests/systemintents/AndroidTest.xml
+++ b/tests/tests/systemintents/AndroidTest.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License
   -->
 <configuration description="Config for CTS system intent test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index c572629..086f2cb 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -16,6 +16,8 @@
 
 package android.systemintents.cts;
 
+import static org.junit.Assert.assertTrue;
+
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -23,14 +25,17 @@
 import android.net.Uri;
 import android.provider.Settings;
 import android.support.test.filters.MediumTest;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
-import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @MediumTest
-public class TestSystemIntents extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class TestSystemIntents {
+
     /*
      * List of activity intents defined by the system.  Activities to handle each of these
      * intents must all exist.
@@ -56,7 +61,6 @@
         }
     }
 
-    @Rule
     private final IntentEntry[] mTestIntents = {
             /* Settings-namespace intent actions */
             new IntentEntry(0, new Intent(Settings.ACTION_SETTINGS)),
@@ -76,7 +80,7 @@
 
     @Test
     public void testSystemIntents() {
-        final PackageManager pm = getContext().getPackageManager();
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
         int productFlags = 0;
 
         if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
@@ -87,7 +91,7 @@
             productFlags |= EXCLUDE_NON_TELEPHONY;
         }
 
-        final Configuration config = getContext().getResources().getConfiguration();
+        final Configuration config = InstrumentationRegistry.getContext().getResources().getConfiguration();
         if ((config.uiMode & Configuration.UI_MODE_TYPE_WATCH) != 0) {
             productFlags |= EXCLUDE_WATCH;
         }
@@ -99,4 +103,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/systemui/Android.mk b/tests/tests/systemui/Android.mk
index 072e8c7..2c07248 100644
--- a/tests/tests/systemui/Android.mk
+++ b/tests/tests/systemui/Android.mk
@@ -24,12 +24,11 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     android-support-test \
-    legacy-android-test \
     ub-uiautomator
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
index d4c7862..10ce913 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
@@ -17,8 +17,16 @@
 package android.systemui.cts;
 
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -63,9 +71,59 @@
         }
     }
 
-    protected boolean hasVirtualNavigationBar() {
+    private boolean hasVirtualNavigationBar() {
         boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
         boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
         return !hasBackKey || !hasHomeKey;
     }
+
+    private boolean isRunningInVr() {
+        final Context context = InstrumentationRegistry.getContext();
+        final Configuration config = context.getResources().getConfiguration();
+        return (config.uiMode & Configuration.UI_MODE_TYPE_MASK)
+                == Configuration.UI_MODE_TYPE_VR_HEADSET;
+    }
+
+    private void assumeBasics() {
+        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+
+        // No bars on embedded devices.
+        assumeFalse(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_EMBEDDED));
+
+        // No bars on TVs and watches.
+        assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
+                || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+
+
+        // Non-highEndGfx devices don't do colored system bars.
+        assumeTrue(ActivityManager.isHighEndGfx());
+    }
+
+    protected void assumeHasColoredStatusBar() {
+        assumeBasics();
+
+        // No status bar when running in Vr
+        assumeFalse(isRunningInVr());
+    }
+
+    protected void assumeHasColorNavigationBar() {
+        assumeBasics();
+
+        // No virtual navigation bar, so no effect.
+        assumeTrue(hasVirtualNavigationBar());
+    }
+
+    protected void checkNavigationBarDivider(LightBarBaseActivity activity, int dividerColor) {
+        final Bitmap bitmap = takeNavigationBarScreenshot(activity);
+        int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
+        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+        for (int col = 0; col < bitmap.getWidth(); col++) {
+            if (dividerColor != pixels[col]) {
+                dumpBitmap(bitmap);
+                fail("Invalid color exptected=" + dividerColor + " actual=" + pixels[col]);
+            }
+        }
+    }
 }
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index 8b1e1b8..8e4a196 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -42,8 +42,7 @@
 /**
  * Test for light status bar.
  *
- * mmma cts/tests/tests/systemui
- * cts-tradefed run commandAndExit cts-dev --module CtsSystemUiTestCases --test android.systemui.cts.LightBarTests --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
+ * atest CtsSystemUiTestCases:LightBarTests
  */
 @RunWith(AndroidJUnit4.class)
 public class LightBarTests extends LightBarTestBase {
@@ -63,26 +62,14 @@
 
     @Test
     public void testLightStatusBarIcons() throws Throwable {
+        assumeHasColoredStatusBar();
+
         mNm = (NotificationManager) getInstrumentation().getContext()
                 .getSystemService(Context.NOTIFICATION_SERVICE);
         NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
                 NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
         mNm.createNotificationChannel(channel1);
 
-        PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
-                || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
-                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-                || isRunningInVR()) {
-            // No status bar on TVs, watches and when running in VR.
-            return;
-        }
-
-        if (!ActivityManager.isHighEndGfx()) {
-            // non-highEndGfx devices don't do colored system bars.
-            return;
-        }
-
         // post 10 notifications to ensure enough icons in the status bar
         for (int i = 0; i < 10; i++) {
             Notification.Builder noti1 = new Notification.Builder(getInstrumentation().getContext(),
@@ -107,23 +94,7 @@
 
     @Test
     public void testLightNavigationBar() throws Throwable {
-        PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
-                || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
-                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-            // No navigation bar on TVs and watches.
-            return;
-        }
-
-        if (!ActivityManager.isHighEndGfx()) {
-            // non-highEndGfx devices don't do colored system bars.
-            return;
-        }
-
-        if (!hasVirtualNavigationBar()) {
-            // No virtual navigation bar, so no effect.
-            return;
-        }
+        assumeHasColorNavigationBar();
 
         requestLightBars(Color.RED /* background */);
         Thread.sleep(WAIT_TIME);
@@ -139,6 +110,19 @@
         assertLightStats(bitmap, s);
     }
 
+    @Test
+    public void testNavigationBarDivider() throws Throwable {
+        assumeHasColorNavigationBar();
+
+        mActivityRule.runOnUiThread(() -> {
+            mActivityRule.getActivity().getWindow().setNavigationBarColor(Color.RED);
+            mActivityRule.getActivity().getWindow().setNavigationBarDividerColor(Color.WHITE);
+        });
+        Thread.sleep(WAIT_TIME);
+
+        checkNavigationBarDivider(mActivityRule.getActivity(), Color.WHITE);
+    }
+
     private void injectCanceledTap(int x, int y) {
         long downTime = SystemClock.uptimeMillis();
         injectEvent(MotionEvent.ACTION_DOWN, x, y, downTime);
@@ -182,14 +166,6 @@
         }
     }
 
-    private boolean isRunningInVR() {
-        final android.content.Context context =
-            android.support.test.InstrumentationRegistry.getContext();
-        android.content.res.Configuration config = context.getResources().getConfiguration();
-        return (config.uiMode & android.content.res.Configuration.UI_MODE_TYPE_MASK)
-        == android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
-    }
-
     private void assertMoreThan(String what, float expected, float actual, String hint) {
         if (!(actual > expected)) {
             fail(what + ": expected more than " + expected * 100 + "%, but only got " + actual * 100
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java b/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
index 3a195f0..496e3f8 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
@@ -17,6 +17,7 @@
 package android.systemui.cts;
 
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -36,8 +37,7 @@
 /**
  * Tests for light system bars that set the flag via theme.
  *
- * mmma cts/tests/tests/systemui
- * cts-tradefed run commandAndExit cts-dev --module CtsSystemUiTestCases --test android.systemui.cts.LightBarThemeTest --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
+ * atest CtsSystemUiTestCases:LightBarThemeTest
  */
 @RunWith(AndroidJUnit4.class)
 public class LightBarThemeTest extends LightBarTestBase {
@@ -50,8 +50,6 @@
 
     @Before
     public void setUp() {
-        assumeFalse(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_EMBEDDED));
         mDevice = UiDevice.getInstance(getInstrumentation());
     }
 
@@ -63,26 +61,21 @@
     }
 
     @Test
-    public void testNavigationBarDivider() throws Exception {
+    public void testGetNavigationBarDividerColor() throws Exception {
+        assumeHasColorNavigationBar();
 
-        if (!hasVirtualNavigationBar()) {
-            // No virtual navigation bar, so no effect.
-            return;
-        }
+        assertEquals(getInstrumentation().getContext().getColor(R.color.navigationBarDividerColor),
+                mActivityRule.getActivity().getWindow().getNavigationBarDividerColor());
+    }
+
+    @Test
+    public void testNavigationBarDividerColor() throws Exception {
+        assumeHasColorNavigationBar();
 
         // Wait until the activity is fully visible
         mDevice.waitForIdle();
 
-        final int dividerColor = getInstrumentation().getContext().getColor(
-                R.color.navigationBarDividerColor);
-        final Bitmap bitmap = takeNavigationBarScreenshot(mActivityRule.getActivity());
-        int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
-        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
-        for (int col = 0; col < bitmap.getWidth(); col++) {
-            if (dividerColor != pixels[col]) {
-                dumpBitmap(bitmap);
-                fail("Invalid color exptected=" + dividerColor + " actual=" + pixels[col]);
-            }
-        }
+        checkNavigationBarDivider(mActivityRule.getActivity(),
+                getInstrumentation().getContext().getColor(R.color.navigationBarDividerColor));
     }
 }
diff --git a/tests/tests/telecom/Android.mk b/tests/tests/telecom/Android.mk
index 8a243eb..8f46fd7 100644
--- a/tests/tests/telecom/Android.mk
+++ b/tests/tests/telecom/Android.mk
@@ -27,8 +27,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
 	ctstestrunner \
-	android-support-test \
-	legacy-android-test
+	android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index fb60586..46de0b7 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Telecom Tests">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="telecom" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
         <option name="token" value="sim-card" />
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index ddab9d1..7610176 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -102,6 +102,7 @@
 
         @Override
         public void onCallStateChanged(int state, String number) {
+            Log.i(TAG, "onCallStateChanged: state=" + state + ", number=%s" + number);
             mCallStates.add(Pair.create(state, number));
             mCallbackSemaphore.release();
         }
@@ -584,7 +585,11 @@
     void verifyPhoneStateListenerCallbacksForCall(int expectedCallState) throws Exception {
         assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
-        Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(0);
+        // Get the most recent callback; it is possible that there was an initial state reported due
+        // to the fact that TelephonyManager will sometimes give an initial state back to the caller
+        // when the listener is registered.
+        Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
+                mPhoneStateListener.mCallStates.size() - 1);
         assertEquals(expectedCallState, (int) callState.first);
         assertEquals(getTestNumber().getSchemeSpecificPart(), callState.second);
     }
diff --git a/tests/tests/telecom2/Android.mk b/tests/tests/telecom2/Android.mk
index 8e9c8a5d..5b22a92 100644
--- a/tests/tests/telecom2/Android.mk
+++ b/tests/tests/telecom2/Android.mk
@@ -26,8 +26,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	ctstestrunner \
-	legacy-android-test
+	ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 src_dirs := src \
     ../telecom/src/android/telecom/cts/SelfManagedConnection.java \
diff --git a/tests/tests/telecom2/AndroidTest.xml b/tests/tests/telecom2/AndroidTest.xml
index f4d8e86..d38f24b 100644
--- a/tests/tests/telecom2/AndroidTest.xml
+++ b/tests/tests/telecom2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Telecom2 Tests">
+    <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">
diff --git a/tests/tests/telecom3/Android.mk b/tests/tests/telecom3/Android.mk
index 86cb859..3138e6c 100644
--- a/tests/tests/telecom3/Android.mk
+++ b/tests/tests/telecom3/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 src_dirs := src \
     ../telecom/src/android/telecom/cts/SelfManagedConnection.java \
     ../telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java \
diff --git a/tests/tests/telecom3/AndroidTest.xml b/tests/tests/telecom3/AndroidTest.xml
index 354581a..75b1d0d 100644
--- a/tests/tests/telecom3/AndroidTest.xml
+++ b/tests/tests/telecom3/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Telecom3 Tests">
+    <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">
diff --git a/tests/tests/telephony/Android.mk b/tests/tests/telephony/Android.mk
index 30e19d0..f19dbb5 100644
--- a/tests/tests/telephony/Android.mk
+++ b/tests/tests/telephony/Android.mk
@@ -26,8 +26,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    compatibility-device-util \
-    legacy-android-test
+    compatibility-device-util
 
 LOCAL_HOST_SHARED_LIBRARIES := compatibility-device-telephony-preconditions
 
@@ -45,7 +44,8 @@
 # uncomment when b/13250611 is fixed
 #LOCAL_SDK_VERSION := current
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index a50329a..b6840a3 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -129,6 +129,10 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.telephony.euicc.cts.EuiccTestResolutionActivity"/>
+
+        <activity android:name="android.telephony.euicc.cts.EuiccResolutionActivity"/>
+
         <uses-library android:name="android.test.runner" />
 
 
diff --git a/tests/tests/telephony/AndroidTest.xml b/tests/tests/telephony/AndroidTest.xml
index 1e48ebf..a696dfc 100644
--- a/tests/tests/telephony/AndroidTest.xml
+++ b/tests/tests/telephony/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Telephony test cases">
+    <option name="test-suite-tag" value="cts" />
     <target_preparer class="android.telephony.cts.preconditions.TelephonyPreparer">
         <option name="apk" value="CtsTelephonyPreparerApp.apk" />
         <option name="package" value="android.telephony.cts.preconditions.app" />
diff --git a/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk b/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
index cbd18b2..1b9c910 100644
--- a/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
+++ b/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
@@ -14,8 +14,8 @@
 
 LOCAL_MODULE_TAGS := optional
 LOCAL_SDK_VERSION := test_current
-LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_COMPATIBILITY_SUITE := cts vts
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_PROGUARD_ENABLED := disabled
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telephony/preconditions/app/Android.mk b/tests/tests/telephony/preconditions/app/Android.mk
index a5fa396..fb64cd2 100644
--- a/tests/tests/telephony/preconditions/app/Android.mk
+++ b/tests/tests/telephony/preconditions/app/Android.mk
@@ -31,6 +31,8 @@
                                 compatibility-device-util \
                                 compatibility-device-preconditions
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/tests/tests/telephony/src/android/telephony/cts/ServiceStateTest.java b/tests/tests/telephony/src/android/telephony/cts/ServiceStateTest.java
index 59a44ed..8b92826 100644
--- a/tests/tests/telephony/src/android/telephony/cts/ServiceStateTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/ServiceStateTest.java
@@ -23,6 +23,8 @@
     private static final String OPERATOR_ALPHA_LONG = "CtsOperatorLong";
     private static final String OPERATOR_ALPHA_SHORT = "CtsOp";
     private static final String OPERATOR_NUMERIC = "02871";
+    private static final int SYSTEM_ID = 123;
+    private static final int NETWORK_ID = 456;
 
     public void testServiceState() {
         ServiceState serviceState = new ServiceState();
@@ -53,6 +55,10 @@
         assertEquals(OPERATOR_ALPHA_SHORT, serviceState.getOperatorAlphaShort());
         assertEquals(OPERATOR_NUMERIC, serviceState.getOperatorNumeric());
 
+        serviceState.setSystemAndNetworkId(SYSTEM_ID, NETWORK_ID);
+        assertEquals(SYSTEM_ID, serviceState.getSystemId());
+        assertEquals(NETWORK_ID, serviceState.getNetworkId());
+
         assertTrue(serviceState.hashCode() > 0);
         assertNotNull(serviceState.toString());
 
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index 8ffee99..74f9d13 100755
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -283,6 +283,24 @@
         }
     }
 
+
+    public void testSmsNotPersisted_failsWithoutCarrierPermissions() throws Exception {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
+                TextUtils.isEmpty(mDestAddr));
+
+        try {
+            getSmsManager().sendTextMessageWithoutPersisting(mDestAddr, null /*scAddress */,
+                    mDestAddr, mSentIntent, mDeliveredIntent);
+            fail("We should get a SecurityException due to not having carrier privileges");
+        } catch (SecurityException e) {
+            // Success
+        }
+    }
+
     private void init() {
         mSendReceiver.reset();
         mDeliveryReceiver.reset();
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index f2a6ec2..37fcb01 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -365,12 +365,12 @@
     }
 
     private void assertSerialNumber() {
-        assertNotNull("Non-telephony devices must have a Build.SERIAL number.",
-                Build.SERIAL);
+        assertNotNull("Non-telephony devices must have a Build.getSerial() number.",
+                Build.getSerial());
         assertTrue("Hardware id must be no longer than 20 characters.",
-                Build.SERIAL.length() <= 20);
+                Build.getSerial().length() <= 20);
         assertTrue("Hardware id must be alphanumeric.",
-                Pattern.matches("[0-9A-Za-z]+", Build.SERIAL));
+                Pattern.matches("[0-9A-Za-z]+", Build.getSerial()));
     }
 
     private void assertMacAddress(String macAddress) {
@@ -516,7 +516,7 @@
 
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             if (!TextUtils.isEmpty(meid)) {
-                assertMeidEsn(meid);
+                assertImei(meid);
             } else {
                 // If MEID is empty, then IMEI must not be empty. A phone should have either a
                 // IMEI or MEID. The validation of IMEI will be checked by testGetImei().
diff --git a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 01f926b..cfd31d3 100644
--- a/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -678,6 +678,9 @@
     }
 
     private boolean hasDataSms() {
+        if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+            return false;
+        }
         String mccmnc = mTelephonyManager.getSimOperator();
         return !CarrierCapability.UNSUPPORT_DATA_SMS_MESSAGES.contains(mccmnc);
     }
diff --git a/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccManagerTest.java
new file mode 100644
index 0000000..17db4b1
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.euicc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class EuiccManagerTest {
+
+    private static final int REQUEST_CODE = 0;
+    private static final int CALLBACK_TIMEOUT_MILLIS = 2000;
+    // starting activities might take extra time
+    private static final int ACTIVITY_CALLBACK_TIMEOUT_MILLIS = 5000;
+    private static final String ACTION_DOWNLOAD_SUBSCRIPTION = "cts_download_subscription";
+    private static final String ACTION_DELETE_SUBSCRIPTION = "cts_delete_subscription";
+    private static final String ACTION_SWITCH_TO_SUBSCRIPTION = "cts_switch_to_subscription";
+    private static final String ACTION_START_TEST_RESOLUTION_ACTIVITY =
+            "cts_start_test_resolution_activity";
+    private static final String ACTIVATION_CODE = "1$LOCALHOST$04386-AGYFT-A74Y8-3F815";
+
+    private static final String[] sCallbackActions =
+            new String[] {
+                ACTION_DOWNLOAD_SUBSCRIPTION,
+                ACTION_DELETE_SUBSCRIPTION,
+                ACTION_SWITCH_TO_SUBSCRIPTION,
+                ACTION_START_TEST_RESOLUTION_ACTIVITY,
+            };
+
+    private MockEuiccManager mMockEuiccManager;
+    private CallbackReceiver mCallbackReceiver;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockEuiccManager = new MockEuiccManager(getContext());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mCallbackReceiver != null) {
+            getContext().unregisterReceiver(mCallbackReceiver);
+        }
+    }
+
+    @Test
+    public void testGetEid() {
+        // test disabled state only for now
+        mMockEuiccManager.setEnabled(false /* enabled */);
+
+        // call getEid()
+        String eid = mMockEuiccManager.getEid();
+
+        // verify result is null
+        assertNull(eid);
+    }
+
+    @Test
+    public void testDownloadSubscription() {
+        // test disabled state only for now
+        mMockEuiccManager.setEnabled(false /* enabled */);
+
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(
+                        mCallbackReceiver, new IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION));
+
+        // call downloadSubscription()
+        DownloadableSubscription subscription = createDownloadableSubscription();
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_DOWNLOAD_SUBSCRIPTION);
+        mMockEuiccManager.downloadSubscription(
+                subscription, false /* switchAfterDownload */, callbackIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify correct result code is received
+        assertEquals(
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+    }
+
+    @Test
+    public void testGetEuiccInfo() {
+        // test disabled state only for now
+        mMockEuiccManager.setEnabled(false /* enabled */);
+
+        // call getEuiccInfo()
+        EuiccInfo euiccInfo = mMockEuiccManager.getEuiccInfo();
+
+        // verify result is null
+        assertNull(euiccInfo);
+    }
+
+    @Test
+    public void testDeleteSubscription() {
+        // test disabled state only for now
+        mMockEuiccManager.setEnabled(false /* enabled */);
+
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(mCallbackReceiver, new IntentFilter(ACTION_DELETE_SUBSCRIPTION));
+
+        // call deleteSubscription()
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_DELETE_SUBSCRIPTION);
+        mMockEuiccManager.deleteSubscription(3, callbackIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify correct result code is received
+        assertEquals(
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+    }
+
+    @Test
+    public void testSwitchToSubscription() {
+        // test disabled state only for now
+        mMockEuiccManager.setEnabled(false /* enabled */);
+
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(
+                        mCallbackReceiver, new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION));
+
+        // call deleteSubscription()
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_SWITCH_TO_SUBSCRIPTION);
+        mMockEuiccManager.switchToSubscription(4, callbackIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify correct result code is received
+        assertEquals(
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+    }
+
+    @Test
+    public void testStartResolutionActivity() {
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(
+                        mCallbackReceiver, new IntentFilter(ACTION_START_TEST_RESOLUTION_ACTIVITY));
+
+        /*
+         * Start EuiccTestResolutionActivity to test EuiccManager#startResolutionActivity(), since
+         * it requires a foreground activity. EuiccTestResolutionActivity will report the test
+         * result to the callback receiver.
+         */
+        Intent testResolutionActivityIntent =
+                new Intent(getContext(), EuiccTestResolutionActivity.class);
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_START_TEST_RESOLUTION_ACTIVITY);
+        testResolutionActivityIntent.putExtra(
+                EuiccTestResolutionActivity.EXTRA_ACTIVITY_CALLBACK_INTENT, callbackIntent);
+        testResolutionActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(testResolutionActivityIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(ACTIVITY_CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify test result reported by EuiccTestResolutionActivity
+        assertEquals(
+                EuiccTestResolutionActivity.RESULT_CODE_TEST_PASSED,
+                mCallbackReceiver.getResultCode());
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    private DownloadableSubscription createDownloadableSubscription() {
+        return DownloadableSubscription.forActivationCode(ACTIVATION_CODE);
+    }
+
+    private PendingIntent createCallbackIntent(String action) {
+        Intent intent = new Intent(action);
+        return PendingIntent.getBroadcast(
+                getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    /** Mock version of {@link EuiccManager} that overrides {@link EuiccManager#isEnabled()}. */
+    private class MockEuiccManager extends EuiccManager {
+
+        private boolean mEnabled = false;
+
+        public MockEuiccManager(Context context) {
+            super(context);
+        }
+
+        public void setEnabled(boolean enabled) {
+            mEnabled = enabled;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+    }
+
+    private static class CallbackReceiver extends BroadcastReceiver {
+
+        private CountDownLatch mCountDownLatch;
+
+        public CallbackReceiver(CountDownLatch latch) {
+            mCountDownLatch = latch;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            for (String callbackAction : sCallbackActions) {
+                if (callbackAction.equals(intent.getAction())) {
+                    int resultCode = getResultCode();
+                    mCountDownLatch.countDown();
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccResolutionActivity.java b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccResolutionActivity.java
new file mode 100644
index 0000000..7a4ca2a
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccResolutionActivity.java
@@ -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.
+ */
+
+package android.telephony.euicc.cts;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.telephony.euicc.EuiccManager;
+
+/**
+ * A dummy activity which simulates a resolution activity. Returns {@link Activity#RESULT_OK} to
+ * caller if 1) {@link Activity#onResume()} is called (as proof that the activity has been
+ * successfully started), and 2) the callback intent is verified.
+ */
+public class EuiccResolutionActivity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // verify callback intent
+        // TODO: verify callback intent's action matches
+        // EuiccTestResolutionActivity.ACTION_START_RESOLUTION_ACTIVITY
+        PendingIntent callbackIntent =
+                getIntent()
+                        .getParcelableExtra(
+                                EuiccManager
+                                        .EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT);
+        if (callbackIntent != null) {
+            setResult(RESULT_OK);
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        // Resolution activity has successfully started, return result & finish
+        finish();
+    }
+}
diff --git a/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
new file mode 100644
index 0000000..b15ab22
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.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.telephony.euicc.cts;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.telephony.euicc.EuiccManager;
+
+/**
+ * A dummy activity started by {@link EuiccManagerTest#testStartResolutionActivity()} for testing
+ * {@link android.telephony.euicc.EuiccManager#startResolutionActivity(Activity, int, Intent,
+ * PendingIntent)}. Sends {@link EuiccTestResolutionActivity#RESULT_CODE_TEST_PASSED} if the
+ * resolution activity is successfully started, {@link
+ * EuiccTestResolutionActivity#RESULT_CODE_TEST_FAILED} otherwise.
+ */
+public class EuiccTestResolutionActivity extends Activity {
+
+    public static final String EXTRA_ACTIVITY_CALLBACK_INTENT = "extra_activity_callback_intent";
+    public static final int RESULT_CODE_TEST_PASSED = 101;
+    public static final int RESULT_CODE_TEST_FAILED = 102;
+
+    private static final int REQUEST_CODE = 100;
+    private static final String ACTION_START_RESOLUTION_ACTIVITY = "cts_start_resolution_activity";
+
+    private PendingIntent mCallbackIntent;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCallbackIntent = getIntent().getParcelableExtra(EXTRA_ACTIVITY_CALLBACK_INTENT);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        testStartResolutionActivity();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_CODE) {
+            sendCallbackAndFinish(
+                    resultCode == RESULT_OK ? RESULT_CODE_TEST_PASSED : RESULT_CODE_TEST_FAILED);
+        }
+    }
+
+    private void testStartResolutionActivity() {
+        EuiccManager euiccManager = (EuiccManager) getSystemService(Context.EUICC_SERVICE);
+
+        // specify activity to start
+        Intent resolutionActivityIntent =
+                new Intent(getApplicationContext(), EuiccResolutionActivity.class);
+        PendingIntent resolutionIntent =
+                PendingIntent.getActivity(
+                        getApplicationContext(),
+                        0 /* requestCode */,
+                        resolutionActivityIntent,
+                        PendingIntent.FLAG_ONE_SHOT);
+
+        // add pending intent to extra
+        Intent resultIntent = new Intent();
+        resultIntent.putExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent);
+
+        // call startResolutionActivity()
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_START_RESOLUTION_ACTIVITY);
+        try {
+            euiccManager.startResolutionActivity(this, REQUEST_CODE, resultIntent, callbackIntent);
+        } catch (IntentSender.SendIntentException e) {
+            sendCallbackAndFinish(RESULT_CODE_TEST_FAILED);
+        }
+    }
+
+    private PendingIntent createCallbackIntent(String action) {
+        Intent intent = new Intent(action);
+        return PendingIntent.getBroadcast(
+                getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    private void sendCallbackAndFinish(int resultCode) {
+        if (mCallbackIntent != null) {
+            try {
+                mCallbackIntent.send(resultCode);
+            } catch (PendingIntent.CanceledException e) {
+                // Caller canceled the callback; do nothing.
+            }
+        }
+        finish();
+    }
+}
diff --git a/tests/tests/telephony2/Android.mk b/tests/tests/telephony2/Android.mk
index 7ac80b5..b86cae1 100644
--- a/tests/tests/telephony2/Android.mk
+++ b/tests/tests/telephony2/Android.mk
@@ -34,6 +34,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telephony2/AndroidTest.xml b/tests/tests/telephony2/AndroidTest.xml
index 78d8d6f..27b9070 100644
--- a/tests/tests/telephony2/AndroidTest.xml
+++ b/tests/tests/telephony2/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Telephony test cases">
+    <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">
diff --git a/tests/tests/text/Android.mk b/tests/tests/text/Android.mk
index 15a7bcc..1d4d5e3 100644
--- a/tests/tests/text/Android.mk
+++ b/tests/tests/text/Android.mk
@@ -21,7 +21,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     compatibility-device-util \
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index 40a0b50..6a998a8 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="android.text.cts">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <application android:maxRecents="1">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/text/AndroidTest.xml b/tests/tests/text/AndroidTest.xml
index 2b8c733..296fa08 100644
--- a/tests/tests/text/AndroidTest.xml
+++ b/tests/tests/text/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Text test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/text/OWNERS b/tests/tests/text/OWNERS
new file mode 100644
index 0000000..a35c604
--- /dev/null
+++ b/tests/tests/text/OWNERS
@@ -0,0 +1,5 @@
+set noparent
+
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
\ No newline at end of file
diff --git a/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
new file mode 100644
index 0000000..1bad6fe
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
new file mode 100644
index 0000000..0cf0f79
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
@@ -0,0 +1,207 @@
+<?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="0em"/>
+    <GlyphID id="2" name="1em"/>
+    <GlyphID id="3" name="3em"/>
+    <GlyphID id="4" name="5em"/>
+    <GlyphID id="5" name="7em"/>
+    <GlyphID id="6" name="10em"/>
+    <GlyphID id="7" name="50em"/>
+    <GlyphID id="8" name="100em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="100"/>
+    <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="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="50" lsb="0"/>
+    <mtx name="0em" width="0" lsb="0"/>
+    <mtx name="1em" width="100" lsb="0"/>
+    <mtx name="3em" width="300" lsb="0"/>
+    <mtx name="5em" width="500" lsb="0"/>
+    <mtx name="7em" width="700" lsb="0"/>
+    <mtx name="10em" width="1000" lsb="0"/>
+    <mtx name="50em" width="5000" lsb="0"/>
+    <mtx name="100em" width="10000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x0020" name="10em" />
+      <map code="0x002e" name="10em" />  <!-- . -->
+      <map code="0x0043" name="100em" />  <!-- C -->
+      <map code="0x0049" name="1em" />  <!-- I -->
+      <map code="0x004c" name="50em" />  <!-- L -->
+      <map code="0x0056" name="5em" />  <!-- V -->
+      <map code="0x0058" name="10em" />  <!-- X -->
+      <map code="0x005f" name="0em" /> <!-- _ -->
+      <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
+      <map code="0x10331" name="10em" />
+    </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="0em" 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" />
+    <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for StaticLayoutLineBreakingTest
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for StaticLayoutLineBreakingTest
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      SampleFont-Regular
+    </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>
+  </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/text/res/layout/keylistener_layout.xml b/tests/tests/text/res/layout/keylistener_layout.xml
index 84a7a14..e26e815 100644
--- a/tests/tests/text/res/layout/keylistener_layout.xml
+++ b/tests/tests/text/res/layout/keylistener_layout.xml
@@ -19,6 +19,7 @@
     android:id="@+id/keylistener_textview"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:textSize="10dp"
-/>
+    android:textSize="10dp">
+    <requestFocus />
+</android.text.method.cts.EditTextNoIme>
 
diff --git a/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java b/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
index 0073c42..39eadce 100644
--- a/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
+++ b/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
@@ -22,8 +22,6 @@
 
 import android.content.Context;
 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.ClipboardManager;
 
@@ -34,19 +32,16 @@
 /**
  * Test {@link ClipboardManager}.
  */
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ClipboardManagerTest {
     private ClipboardManager mClipboardManager;
 
-    @UiThreadTest
     @Before
     public void setup() {
         final Context context = InstrumentationRegistry.getTargetContext();
         mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
     }
 
-    @UiThreadTest
     @Test
     public void testAccessText() {
         // set the expected value
@@ -55,7 +50,6 @@
         assertEquals(expected, mClipboardManager.getText());
     }
 
-    @UiThreadTest
     @Test
     public void testHasText() {
         mClipboardManager.setText("");
diff --git a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
index 02139c5..42c119a 100644
--- a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
@@ -17,21 +17,25 @@
 package android.text.cts;
 
 import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
 
 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 org.mockito.Mockito.mock;
 
 import android.graphics.Paint.FontMetricsInt;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.DynamicLayout;
 import android.text.Layout;
+import android.text.SpannableStringBuilder;
 import android.text.StaticLayout;
 import android.text.TextPaint;
 import android.text.TextUtils;
+import android.text.style.TypefaceSpan;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +48,7 @@
     private static final float SPACING_MULT_NO_SCALE = 1.0f;
     private static final float SPACING_ADD_NO_SCALE = 0.0f;
     private static final int DEFAULT_OUTER_WIDTH = 150;
+    private static final int ELLIPSIZE_WIDTH = 8;
     private static final CharSequence SINGLELINE_CHAR_SEQUENCE = "......";
     private static final String[] TEXT = {"CharSequence\n", "Char\tSequence\n", "CharSequence"};
     private static final CharSequence MULTLINE_CHAR_SEQUENCE = TEXT[0] + TEXT[1] + TEXT[2];
@@ -194,6 +199,62 @@
         assertEquals(TEXT[0].length() + TEXT[1].length(), mDynamicLayout.getLineStart(LINE2));
     }
 
+    @Test
+    public void testLineSpacing() {
+        SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
+        final float spacingMultiplier = 2f;
+        final float spacingAdd = 4;
+        final int width = 1000;
+        final TextPaint textPaint = new TextPaint();
+        // create the DynamicLayout
+        final DynamicLayout dynamicLayout = new DynamicLayout(text,
+                textPaint,
+                width,
+                ALIGN_NORMAL,
+                spacingMultiplier,
+                spacingAdd,
+                false /*includepad*/);
+
+        // create a StaticLayout with same text, this will define the expectations
+        Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+                spacingMultiplier);
+        assertLineSpecs(expected, dynamicLayout);
+
+        // add a new line to the end, DynamicLayout will re-calculate
+        text = text.append("\nd");
+        expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+                spacingMultiplier);
+        assertLineSpecs(expected, dynamicLayout);
+
+        // insert a next line and a char as the new second line
+        text = text.insert(TextUtils.indexOf(text, '\n') + 1, "a1\n");
+        expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+                spacingMultiplier);
+        assertLineSpecs(expected, dynamicLayout);
+    }
+
+    @Test
+    public void testLineSpacing_textEndingWithNextLine() {
+        final SpannableStringBuilder text = new SpannableStringBuilder("a\n");
+        final float spacingMultiplier = 2f;
+        final float spacingAdd = 4f;
+        final int width = 1000;
+        final TextPaint textPaint = new TextPaint();
+        // create the DynamicLayout
+        final DynamicLayout dynamicLayout = new DynamicLayout(text,
+                textPaint,
+                width,
+                ALIGN_NORMAL,
+                spacingMultiplier,
+                spacingAdd,
+                false /*includepad*/);
+
+        // create a StaticLayout with same text, this will define the expectations
+        final Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+                spacingMultiplier);
+        assertLineSpecs(expected, dynamicLayout);
+    }
+
     private Layout createStaticLayout(CharSequence text, TextPaint textPaint, int width,
             float spacingAdd, float spacingMultiplier) {
         return StaticLayout.Builder.obtain(text, 0,
@@ -253,4 +314,110 @@
                 spacingMultiplier);
         assertLineSpecs(expected, dynamicLayout);
     }
+
+    @Test
+    public void testBuilder_obtain() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        final DynamicLayout layout = builder.build();
+        // Check values passed to obtain().
+        assertEquals(MULTLINE_CHAR_SEQUENCE, layout.getText());
+        assertEquals(mDefaultPaint, layout.getPaint());
+        assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+        // Check default values.
+        assertEquals(Layout.Alignment.ALIGN_NORMAL, layout.getAlignment());
+        assertEquals(0.0f, layout.getSpacingAdd(), 0.0f);
+        assertEquals(1.0f, layout.getSpacingMultiplier(), 0.0f);
+        assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_obtainWithNullText() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(null, mDefaultPaint, 0);
+        final DynamicLayout layout = builder.build();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_obtainWithNullPaint() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                null, 0);
+        final DynamicLayout layout = builder.build();
+    }
+
+    @Test
+    public void testBuilder_setDisplayTest() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setDisplayText(SINGLELINE_CHAR_SEQUENCE);
+        final DynamicLayout layout = builder.build();
+        assertEquals(SINGLELINE_CHAR_SEQUENCE, layout.getText());
+    }
+
+    @Test
+    public void testBuilder_setAlignment() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setAlignment(DEFAULT_ALIGN);
+        final DynamicLayout layout = builder.build();
+        assertEquals(DEFAULT_ALIGN, layout.getAlignment());
+    }
+
+    @Test
+    public void testBuilder_setLineSpacing() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setLineSpacing(1.0f, 2.0f);
+        final DynamicLayout layout = builder.build();
+        assertEquals(1.0f, layout.getSpacingAdd(), 0.0f);
+        assertEquals(2.0f, layout.getSpacingMultiplier(), 0.0f);
+    }
+
+    @Test
+    public void testBuilder_ellipsization() {
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setEllipsize(TextUtils.TruncateAt.END)
+                .setEllipsizedWidth(ELLIPSIZE_WIDTH);
+        final DynamicLayout layout = builder.build();
+        assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth());
+        assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+        for (int i = 0; i < TEXT.length; i++) {
+            if (i == TEXT.length - 1) { // last line
+                assertTrue(layout.getEllipsisCount(i) > 0);
+            } else {
+                assertEquals(0, layout.getEllipsisCount(i));
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_otherSetters() {
+        // Setter methods that cannot be directly tested.
+        // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setJustificationMode.
+        final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
+                .setIncludePad(true)
+                .setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
+        final DynamicLayout layout = builder.build();
+        assertNotNull(layout);
+    }
+
+    @Test
+    public void testReflow_afterSpanChangedShouldNotThrowException() {
+        final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!");
+
+        final TypefaceSpan span = mock(TypefaceSpan.class);
+        builder.setSpan(span, 1, 4, SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(builder,
+                new TextPaint(), Integer.MAX_VALUE).build();
+        try {
+            builder.insert(1, "Hello there\n\n");
+        } catch (Throwable e) {
+            throw new RuntimeException("Inserting text into DynamicLayout should not crash", e);
+        }
+    }
+
 }
diff --git a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
new file mode 100644
index 0000000..8967d3a
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.text.cts;
+
+import static android.text.TextDirectionHeuristics.LTR;
+import static android.text.TextDirectionHeuristics.RTL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
+import android.text.PrecomputedText;
+import android.text.PrecomputedText.Params;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.LocaleSpan;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrecomputedTextTest {
+
+    private static final CharSequence NULL_CHAR_SEQUENCE = null;
+    private static final String STRING = "Hello, World!";
+    private static final String MULTIPARA_STRING = "Hello,\nWorld!";
+
+    private static final int SPAN_START = 3;
+    private static final int SPAN_END = 7;
+    private static final LocaleSpan SPAN = new LocaleSpan(Locale.US);
+    private static final Spanned SPANNED;
+    static {
+        final SpannableStringBuilder ssb = new SpannableStringBuilder(STRING);
+        ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        SPANNED = ssb;
+    }
+
+    private static final TextPaint PAINT = new TextPaint();
+
+    @Test
+    public void testParams_create() {
+        assertNotNull(new Params.Builder(PAINT).build());
+        assertNotNull(new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build());
+        assertNotNull(new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL).build());
+        assertNotNull(new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(LTR).build());
+    }
+
+    @Test
+    public void testParams_SetGet() {
+        assertEquals(Layout.BREAK_STRATEGY_SIMPLE, new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build().getBreakStrategy());
+        assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, new Params.Builder(PAINT)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build()
+                        .getHyphenationFrequency());
+        assertEquals(RTL, new Params.Builder(PAINT).setTextDirection(RTL).build()
+                .getTextDirection());
+    }
+
+    @Test
+    public void testParams_GetDefaultValues() {
+        assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY,
+                     new Params.Builder(PAINT).build().getBreakStrategy());
+        assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL,
+                     new Params.Builder(PAINT).build().getHyphenationFrequency());
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+                     new Params.Builder(PAINT).build().getTextDirection());
+    }
+
+    @Test
+    public void testParams_equals() {
+        final Params base = new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(LTR).build();
+
+        assertFalse(base.equals(null));
+        assertTrue(base.equals(base));
+        assertFalse(base.equals(new Object()));
+
+        Params other = new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(LTR).build();
+        assertTrue(base.equals(other));
+        assertTrue(other.equals(base));
+        assertEquals(base.hashCode(), other.hashCode());
+
+        other = new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(LTR).build();
+        assertFalse(base.equals(other));
+        assertFalse(other.equals(base));
+
+        other = new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                .setTextDirection(LTR).build();
+        assertFalse(base.equals(other));
+        assertFalse(other.equals(base));
+
+
+        other = new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(RTL).build();
+        assertFalse(base.equals(other));
+        assertFalse(other.equals(base));
+
+
+        TextPaint anotherPaint = new TextPaint(PAINT);
+        anotherPaint.setTextSize(PAINT.getTextSize() * 2.0f);
+        other = new Params.Builder(anotherPaint)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setTextDirection(LTR).build();
+        assertFalse(base.equals(other));
+        assertFalse(other.equals(base));
+
+    }
+
+    @Test
+    public void testCreate_withNull() {
+        final Params param = new Params.Builder(PAINT).build();
+        try {
+            PrecomputedText.create(NULL_CHAR_SEQUENCE, param);
+            fail();
+        } catch (NullPointerException e) {
+            // pass
+        }
+        try {
+            PrecomputedText.create(STRING, null);
+            fail();
+        } catch (NullPointerException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testCharSequenceInteface() {
+        final Params param = new Params.Builder(PAINT).build();
+        final CharSequence s = PrecomputedText.create(STRING, param);
+        assertEquals(STRING.length(), s.length());
+        assertEquals('H', s.charAt(0));
+        assertEquals('e', s.charAt(1));
+        assertEquals('l', s.charAt(2));
+        assertEquals('l', s.charAt(3));
+        assertEquals('o', s.charAt(4));
+        assertEquals(',', s.charAt(5));
+        assertEquals("Hello, World!", s.toString());
+
+        final CharSequence s3 = s.subSequence(0, 3);
+        assertEquals(3, s3.length());
+        assertEquals('H', s3.charAt(0));
+        assertEquals('e', s3.charAt(1));
+        assertEquals('l', s3.charAt(2));
+
+    }
+
+    @Test
+    public void testSpannedInterface_Spanned() {
+        final Params param = new Params.Builder(PAINT).build();
+        final Spanned s = PrecomputedText.create(SPANNED, param);
+        final LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
+        assertNotNull(spans);
+        assertEquals(1, spans.length);
+        assertEquals(SPAN, spans[0]);
+
+        assertEquals(SPAN_START, s.getSpanStart(SPAN));
+        assertEquals(SPAN_END, s.getSpanEnd(SPAN));
+        assertTrue((s.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
+
+        assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), LocaleSpan.class));
+        assertEquals(SPAN_END, s.nextSpanTransition(SPAN_START, s.length(), LocaleSpan.class));
+    }
+
+    @Test
+    public void testSpannedInterface_String() {
+        final Params param = new Params.Builder(PAINT).build();
+        final Spanned s = PrecomputedText.create(STRING, param);
+        LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
+        assertNotNull(spans);
+        assertEquals(0, spans.length);
+
+        assertEquals(-1, s.getSpanStart(SPAN));
+        assertEquals(-1, s.getSpanEnd(SPAN));
+        assertEquals(0, s.getSpanFlags(SPAN));
+
+        assertEquals(s.length(), s.nextSpanTransition(0, s.length(), LocaleSpan.class));
+    }
+
+    @Test
+    public void testGetText() {
+        final Params param = new Params.Builder(PAINT).build();
+        assertEquals(STRING.toString(), PrecomputedText.create(STRING, param).getText().toString());
+        assertEquals(SPANNED.toString(),
+                PrecomputedText.create(SPANNED, param).getText().toString());
+    }
+
+    @Test
+    public void testGetParagraphCount() {
+        final Params param = new Params.Builder(PAINT).build();
+        final PrecomputedText pm = PrecomputedText.create(STRING, param);
+        assertEquals(1, pm.getParagraphCount());
+        assertEquals(0, pm.getParagraphStart(0));
+        assertEquals(STRING.length(), pm.getParagraphEnd(0));
+
+        final PrecomputedText pm1 = PrecomputedText.create(MULTIPARA_STRING, param);
+        assertEquals(2, pm1.getParagraphCount());
+        assertEquals(0, pm1.getParagraphStart(0));
+        assertEquals(7, pm1.getParagraphEnd(0));
+        assertEquals(7, pm1.getParagraphStart(1));
+        assertEquals(pm1.length(), pm1.getParagraphEnd(1));
+    }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/SelectionTest.java b/tests/tests/text/src/android/text/cts/SelectionTest.java
index f920bda..2aac83b 100644
--- a/tests/tests/text/src/android/text/cts/SelectionTest.java
+++ b/tests/tests/text/src/android/text/cts/SelectionTest.java
@@ -23,6 +23,7 @@
 
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.StaticLayout;
@@ -275,6 +276,56 @@
     }
 
     @Test
+    public void testMoveUpAfterTyping() {
+        CharSequence text = "aaa\nmm";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 1, 1);
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(5, Selection.getSelectionStart(builder));
+        assertEquals(5, Selection.getSelectionEnd(builder));
+
+        builder.insert(5, "mm");
+        layout = new StaticLayout(builder, new TextPaint(), 200, Layout.Alignment.ALIGN_NORMAL,
+                0, 0, false);
+        assertEquals(7, Selection.getSelectionStart(builder));
+        assertEquals(7, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testMoveUpKeepsOriginalMemoryPosition() {
+        CharSequence text = "aa\nm";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 1, 1);
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(4, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
     public void testMoveDown() {
         CharSequence text = "hello,world\nGoogle";
         SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -313,6 +364,340 @@
     }
 
     @Test
+    public void testMoveDownAfterTyping() {
+        CharSequence text = "mm\naaa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 4, 4);
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+
+        builder.insert(1, "mm");
+        layout = new StaticLayout(builder, new TextPaint(), 200, Layout.Alignment.ALIGN_NORMAL,
+                0, 0, false);
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(8, Selection.getSelectionStart(builder));
+        assertEquals(8, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testMoveDownKeepsOriginalMemoryPosition() {
+        CharSequence text = "m\naa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 3, 3);
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
+    public void testMemoryPositionResetByHorizontalMovement() {
+        CharSequence text = "m\naa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 3, 3);
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveRight(builder, layout));
+        assertEquals(4, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(4, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveLeft(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 3, 3);
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
+    public void testMemoryPositionResetByRemoveSelection() {
+        CharSequence text = "m\naa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 3, 3);
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.removeSelection(builder);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
+    public void testMultilineLengthMoveDown() {
+        CharSequence text = "a\n\na";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 1);
+        // Move down to empty line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(2, Selection.getSelectionStart(builder));
+        assertEquals(2, Selection.getSelectionEnd(builder));
+
+        // Move down to third line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(4, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testMultilineLengthExtendDown() {
+        CharSequence text = "Google\n\nhello, world";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 1, 3);
+        assertTrue(Selection.extendDown(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(7, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.extendDown(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(15, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.extendDown(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(text.length(), Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testExtendDownKeepsOriginalMemoryPosition() {
+        CharSequence text = "m\naa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 3, 3);
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.extendDown(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
+    public void testMultilineLengthMoveUp() {
+        CharSequence text = "a\n\na";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 4);
+        // Move up to empty line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(2, Selection.getSelectionStart(builder));
+        assertEquals(2, Selection.getSelectionEnd(builder));
+
+        // Move up to first line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testMultilineLengthExtendUp() {
+        CharSequence text = "Google\n\nhello, world";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(0, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 9, 16);
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(9, Selection.getSelectionStart(builder));
+        assertEquals(7, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(9, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(9, Selection.getSelectionStart(builder));
+        assertEquals(0, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testExtendUpKeepsOriginalMemoryPosition() {
+        CharSequence text = "aa\nm";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+                Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+        assertEquals(0,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        Selection.setSelection(builder, 1, 1);
+        assertTrue(Selection.extendDown(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(4, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+        assertTrue(Selection.extendUp(builder, layout));
+        assertEquals(1, Selection.getSelectionStart(builder));
+        assertEquals(1, Selection.getSelectionEnd(builder));
+        assertEquals(1,
+                builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+    }
+
+    @Test
+    public void testMultilineLengthMoveDownAfterSelection() {
+        CharSequence text = "aaaaa\n\naaaaa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 3);
+        // Move down to empty line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(6, Selection.getSelectionStart(builder));
+        assertEquals(6, Selection.getSelectionEnd(builder));
+
+        // Move down to third line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(3, Selection.getSelectionStart(builder));
+        assertEquals(3, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 5);
+        // Move down to empty line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(6, Selection.getSelectionStart(builder));
+        assertEquals(6, Selection.getSelectionEnd(builder));
+
+        // Move down to third line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(5, Selection.getSelectionStart(builder));
+        assertEquals(5, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
+    public void testMultilineLengthMoveUpAfterSelection() {
+        CharSequence text = "aaaaa\n\naaaaa";
+        SpannableStringBuilder builder = new SpannableStringBuilder(text);
+        StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+        assertEquals(-1, Selection.getSelectionStart(builder));
+        assertEquals(-1, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 10);
+        // Move up to empty line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(6, Selection.getSelectionStart(builder));
+        assertEquals(6, Selection.getSelectionEnd(builder));
+
+        // Move down to third line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(10, Selection.getSelectionStart(builder));
+        assertEquals(10, Selection.getSelectionEnd(builder));
+
+        Selection.setSelection(builder, 12);
+        // Move up to empty line
+        assertTrue(Selection.moveUp(builder, layout));
+        assertEquals(6, Selection.getSelectionStart(builder));
+        assertEquals(6, Selection.getSelectionEnd(builder));
+
+        // Move down to third line
+        assertTrue(Selection.moveDown(builder, layout));
+        assertEquals(12, Selection.getSelectionStart(builder));
+        assertEquals(12, Selection.getSelectionEnd(builder));
+    }
+
+    @Test
     public void testExtendSelection() {
         CharSequence text = "hello, world";
         SpannableStringBuilder builder = new SpannableStringBuilder(text);
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
new file mode 100644
index 0000000..adb8f97
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
@@ -0,0 +1,459 @@
+/*
+ * 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.text.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;;
+import android.graphics.Typeface;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.MetricAffectingSpan;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutLineBreakingTest {
+    // Span test are currently not supported because text measurement uses the MeasuredText
+    // internal mWorkPaint instead of the provided MockTestPaint.
+    private static final boolean SPAN_TESTS_SUPPORTED = false;
+    private static final boolean DEBUG = false;
+
+    private static final float SPACE_MULTI = 1.0f;
+    private static final float SPACE_ADD = 0.0f;
+    private static final int WIDTH = 100;
+    private static final Alignment ALIGN = Alignment.ALIGN_NORMAL;
+
+    private static final char SURR_FIRST = '\uD800';
+    private static final char SURR_SECOND = '\uDF31';
+
+    private static final int[] NO_BREAK = new int[] {};
+
+    private static final TextPaint sTextPaint = new TextPaint();
+
+    static {
+        // The test font has following coverage and width.
+        // U+0020: 10em
+        // U+002E (.): 10em
+        // U+0043 (C): 100em
+        // U+0049 (I): 1em
+        // U+004C (L): 50em
+        // U+0056 (V): 5em
+        // U+0058 (X): 10em
+        // U+005F (_): 0em
+        // U+FFFD (invalid surrogate will be replaced to this): 7em
+        // U+10331 (\uD800\uDF31): 10em
+        Context context = InstrumentationRegistry.getTargetContext();
+        sTextPaint.setTypeface(Typeface.createFromAsset(context.getAssets(),
+                  "fonts/StaticLayoutLineBreakingTestFont.ttf"));
+        sTextPaint.setTextSize(1.0f);  // Make 1em == 1px.
+    }
+
+    private static StaticLayout getStaticLayout(CharSequence source, int width,
+            int breakStrategy) {
+        return StaticLayout.Builder.obtain(source, 0, source.length(), sTextPaint, width)
+                .setAlignment(ALIGN)
+                .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+                .setIncludePad(false)
+                .setBreakStrategy(breakStrategy)
+                .build();
+    }
+
+    private static int[] getBreaks(CharSequence source) {
+        return getBreaks(source, WIDTH, Layout.BREAK_STRATEGY_SIMPLE);
+    }
+
+    private static int[] getBreaks(CharSequence source, int width, int breakStrategy) {
+        final StaticLayout staticLayout = getStaticLayout(source, width, breakStrategy);
+
+        final int[] breaks = new int[staticLayout.getLineCount() - 1];
+        for (int line = 0; line < breaks.length; line++) {
+            breaks[line] = staticLayout.getLineEnd(line);
+        }
+        return breaks;
+    }
+
+    private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
+        if (DEBUG) {
+            int count = staticLayout.getLineCount();
+            Log.i("SLLBTest", "\"" + source.toString() + "\": "
+                    + count + " lines");
+            for (int line = 0; line < count; line++) {
+                int lineStart = staticLayout.getLineStart(line);
+                int lineEnd = staticLayout.getLineEnd(line);
+                Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
+                        + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
+            }
+        }
+    }
+
+    private static void layout(CharSequence source, int[] breaks) {
+        layout(source, breaks, WIDTH);
+    }
+
+    private static void layout(CharSequence source, int[] breaks, int width) {
+        final int[] breakStrategies = {Layout.BREAK_STRATEGY_SIMPLE,
+                Layout.BREAK_STRATEGY_HIGH_QUALITY};
+        for (int breakStrategy : breakStrategies) {
+            final StaticLayout staticLayout = getStaticLayout(source, width, breakStrategy);
+
+            debugLayout(source, staticLayout);
+
+            final int lineCount = breaks.length + 1;
+            assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
+
+            for (int line = 0; line < lineCount; line++) {
+                final int lineStart = staticLayout.getLineStart(line);
+                final int lineEnd = staticLayout.getLineEnd(line);
+
+                if (line == 0) {
+                    assertEquals("Line start for first line", 0, lineStart);
+                } else {
+                    assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+                }
+
+                if (line == lineCount - 1) {
+                    assertEquals("Line end for last line", source.length(), lineEnd);
+                } else {
+                    assertEquals("Line end for line " + line, breaks[line], lineEnd);
+                }
+            }
+        }
+    }
+
+    private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
+        final StaticLayout staticLayout = StaticLayout.Builder
+                .obtain(source, 0, source.length(), sTextPaint, WIDTH)
+                .setAlignment(ALIGN)
+                .setTextDirection(TextDirectionHeuristics.LTR)
+                .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+                .setIncludePad(false)
+                .setMaxLines(maxLines)
+                .build();
+
+        debugLayout(source, staticLayout);
+
+        final int lineCount = staticLayout.getLineCount();
+
+        for (int line = 0; line < lineCount; line++) {
+            int lineStart = staticLayout.getLineStart(line);
+            int lineEnd = staticLayout.getLineEnd(line);
+
+            if (line == 0) {
+                assertEquals("Line start for first line", 0, lineStart);
+            } else {
+                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+            }
+
+            if (line == lineCount - 1 && line != breaks.length - 1) {
+                assertEquals("Line end for last line", source.length(), lineEnd);
+            } else {
+                assertEquals("Line end for line " + line, breaks[line], lineEnd);
+            }
+        }
+    }
+
+    private static final int MAX_SPAN_COUNT = 10;
+    private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
+    private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
+
+    private static MetricAffectingSpan getMetricAffectingSpan() {
+        return new MetricAffectingSpan() {
+            @Override
+            public void updateDrawState(TextPaint tp) { /* empty */ }
+
+            @Override
+            public void updateMeasureState(TextPaint p) { /* empty */ }
+        };
+    }
+
+    /**
+     * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
+     * @param text
+     * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
+     */
+    private static CharSequence spanify(String text) {
+        int startIndex = text.indexOf('<');
+        if (startIndex < 0) return text;
+
+        int spanCount = 0;
+        do {
+            int endIndex = text.indexOf('>');
+            if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
+
+            text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
+                    + text.substring(endIndex + 1);
+
+            sSpanStarts[spanCount] = startIndex;
+            sSpanEnds[spanCount] = endIndex - 2;
+            spanCount++;
+
+            startIndex = text.indexOf('<');
+        } while (startIndex >= 0);
+
+        SpannableStringBuilder result = new SpannableStringBuilder(text);
+        for (int i = 0; i < spanCount; i++) {
+            result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
+                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+        return result;
+    }
+
+    @Test
+    public void testNoLineBreak() {
+        // Width lower than WIDTH
+        layout("", NO_BREAK);
+        layout("I", NO_BREAK);
+        layout("V", NO_BREAK);
+        layout("X", NO_BREAK);
+        layout("L", NO_BREAK);
+        layout("I VILI", NO_BREAK);
+        layout("XXXX", NO_BREAK);
+        layout("LXXXX", NO_BREAK);
+
+        // Width equal to WIDTH
+        layout("C", NO_BREAK);
+        layout("LL", NO_BREAK);
+        layout("L XXXX", NO_BREAK);
+        layout("XXXXXXXXXX", NO_BREAK);
+        layout("XXX XXXXXX", NO_BREAK);
+        layout("XXX XXXX X", NO_BREAK);
+        layout("XXX XXXXX ", NO_BREAK);
+        layout(" XXXXXXXX ", NO_BREAK);
+        layout("  XX  XXX ", NO_BREAK);
+        //      0123456789
+
+        // Width greater than WIDTH, but no break
+        layout("  XX  XXX  ", NO_BREAK);
+        layout("XX XXX XXX ", NO_BREAK);
+        layout("XX XXX XXX     ", NO_BREAK);
+        layout("XXXXXXXXXX     ", NO_BREAK);
+        //      01234567890
+    }
+
+    @Test
+    public void testOneLineBreak() {
+        //      01234567890
+        layout("XX XXX XXXX", new int[] {7});
+        layout("XX XXXX XXX", new int[] {8});
+        layout("XX XXXXX XX", new int[] {9});
+        layout("XX XXXXXX X", new int[] {10});
+        //      01234567890
+        layout("XXXXXXXXXXX", new int[] {10});
+        layout("XXXXXXXXX X", new int[] {10});
+        layout("XXXXXXXX XX", new int[] {9});
+        layout("XXXXXXX XXX", new int[] {8});
+        layout("XXXXXX XXXX", new int[] {7});
+        //      01234567890
+        layout("LL LL", new int[] {3});
+        layout("LLLL", new int[] {2});
+        layout("C C", new int[] {2});
+        layout("CC", new int[] {1});
+    }
+
+    @Test
+    public void testSpaceAtBreak() {
+        //      0123456789012
+        layout("XXXX XXXXX X", new int[] {11});
+        layout("XXXXXXXXXX X", new int[] {11});
+        layout("XXXXXXXXXV X", new int[] {11});
+        layout("C X", new int[] {2});
+    }
+
+    @Test
+    public void testMultipleSpacesAtBreak() {
+        //      0123456789012
+        layout("LXX XXXX", new int[] {4});
+        layout("LXX  XXXX", new int[] {5});
+        layout("LXX   XXXX", new int[] {6});
+        layout("LXX    XXXX", new int[] {7});
+        layout("LXX     XXXX", new int[] {8});
+    }
+
+    @Test
+    public void testZeroWidthCharacters() {
+        //      0123456789012345678901234
+        layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
+        layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
+        layout("C_X", new int[] {2});
+        layout("C__X", new int[] {3});
+    }
+
+    /**
+     * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
+     * measure text runs anymore. This is probably a bug.
+     * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
+     * mWorkPaint in MeasuredText#addStyleRun
+     */
+    @Test
+    public void testWithSpans() {
+        if (!SPAN_TESTS_SUPPORTED) return;
+
+        layout(spanify("<012 456 89>"), NO_BREAK);
+        layout(spanify("012 <456> 89"), NO_BREAK);
+        layout(spanify("<012> <456>< 89>"), NO_BREAK);
+        layout(spanify("<012> <456> <89>"), NO_BREAK);
+
+        layout(spanify("<012> <456> <89>012"), new int[] {8});
+        layout(spanify("<012> <456> 89<012>"), new int[] {8});
+        layout(spanify("<012> <456> <89><012>"), new int[] {8});
+        layout(spanify("<012> <456> 89 <123>"), new int[] {11});
+        layout(spanify("<012> <456> 89< 123>"), new int[] {11});
+        layout(spanify("<012> <456> <89> <123>"), new int[] {11});
+        layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
+    }
+
+    /*
+     * Adding a span to the string should not change the layout, since the metrics are unchanged.
+     */
+    @Test
+    public void testWithOneSpan() {
+        if (!SPAN_TESTS_SUPPORTED) return;
+
+        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+                "012 456 89012 456 89012", "0123456789012" };
+
+        MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
+
+        for (String text : texts) {
+            // Get the line breaks without any span
+            int[] breaks = getBreaks(text);
+
+            // Add spans on all possible offsets
+            for (int spanStart = 0; spanStart < text.length(); spanStart++) {
+                for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
+                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+                    ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
+                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    layout(ssb, breaks);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testWithTwoSpans() {
+        if (!SPAN_TESTS_SUPPORTED) return;
+
+        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+                "012 456 89012 456 89012", "0123456789012" };
+
+        MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
+        MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
+
+        for (String text : texts) {
+            // Get the line breaks without any span
+            int[] breaks = getBreaks(text);
+
+            // Add spans on all possible offsets
+            for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
+                for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
+                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+                    ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
+                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+                    for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
+                        for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
+                            ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
+                                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                            layout(ssb, breaks);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public static String replace(String string, char c, char r) {
+        return string.replaceAll(String.valueOf(c), String.valueOf(r));
+    }
+
+    @Test
+    public void testWithSurrogate() {
+        layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+        layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+        // LXXXXI (91) + SURR_FIRST + SURR_SECOND (10). Do not break in the middle point of
+        // surrogatge pair.
+        layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {6});
+
+        // LXXXXI (91) + SURR_SECOND (replaced with REPLACEMENT CHARACTER. width is 7px) fits.
+        // Break just after invalid trailing surrogate.
+        layout("LXXXXI" + SURR_SECOND + SURR_FIRST, new int[] {7});
+
+        layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
+    }
+
+    @Test
+    public void testNarrowWidth() {
+        int[] widths = new int[] { 0, 4, 10 };
+        String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
+
+        for (String text: texts) {
+            // 15 is such that only one character will fit
+            int[] breaks = getBreaks(text, 15, Layout.BREAK_STRATEGY_SIMPLE);
+
+            // Width under 15 should all lead to the same line break
+            for (int width: widths) {
+                layout(text, breaks, width);
+            }
+        }
+    }
+
+    @Test
+    public void testNarrowWidthZeroWidth() {
+        int[] widths = new int[] { 1, 4 };
+        for (int width: widths) {
+            layout("X.", new int[] {1}, width);
+            layout("X__", NO_BREAK, width);
+            layout("X__X", new int[] {3}, width);
+            layout("X__X_", new int[] {3}, width);
+
+            layout("_", NO_BREAK, width);
+            layout("__", NO_BREAK, width);
+
+            // TODO: The line breaking algorithms break the line too frequently in the presence of
+            // zero-width characters. The following cases document how line-breaking should behave
+            // in some cases, where the current implementation does not seem reasonable. (Breaking
+            // between a zero-width character that start the line and a character with positive
+            // width does not make sense.) Line-breaking should be fixed so that all the following
+            // tests end up on one line, with no breaks.
+            // layout("_X", NO_BREAK, width);
+            // layout("_X_", NO_BREAK, width);
+            // layout("__X__", NO_BREAK, width);
+        }
+    }
+
+    @Test
+    public void testMaxLines() {
+        layoutMaxLines("C", NO_BREAK, 1);
+        layoutMaxLines("C C", new int[] {2}, 1);
+        layoutMaxLines("C C", new int[] {2}, 2);
+        layoutMaxLines("CC", new int[] {1}, 1);
+        layoutMaxLines("CC", new int[] {1}, 2);
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 4f022a5..8113615 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -22,17 +22,24 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Typeface;
+import android.os.LocaleList;
 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;
 import android.text.Layout.Alignment;
+import android.text.PrecomputedText;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
@@ -59,6 +66,13 @@
     private static final int LINE_COUNT = 6;
     private static final int LARGER_THAN_LINE_COUNT  = 50;
 
+    private static final String LOREM_IPSUM = "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.";
+
     /* the first line must have one tab. the others not. totally 6 lines
      */
     private static final CharSequence LAYOUT_TEXT = "CharSe\tq\nChar"
@@ -203,8 +217,8 @@
             // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setIndents.
             StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
                     LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
-            builder.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY);
-            builder.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_FULL);
+            builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
+            builder.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
             builder.setIncludePad(true);
             builder.setIndents(null, null);
             StaticLayout layout = builder.build();
@@ -213,6 +227,38 @@
     }
 
     @Test
+    public void testSetLineSpacing_whereLineEndsWithNextLine() {
+        final float spacingAdd = 10f;
+        final float spacingMult = 3f;
+
+        // two lines of text, with line spacing, first line will have the spacing, but last line
+        // wont have the spacing
+        final String tmpText = "a\nb";
+        StaticLayout.Builder builder = StaticLayout.Builder.obtain(tmpText, 0, tmpText.length(),
+                mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        builder.setLineSpacing(spacingAdd, spacingMult).setIncludePad(false);
+        final StaticLayout comparisonLayout = builder.build();
+
+        assertEquals(2, comparisonLayout.getLineCount());
+        final int heightWithLineSpacing = comparisonLayout.getLineBottom(0)
+                - comparisonLayout.getLineTop(0);
+        final int heightWithoutLineSpacing = comparisonLayout.getLineBottom(1)
+                - comparisonLayout.getLineTop(1);
+        assertTrue(heightWithLineSpacing > heightWithoutLineSpacing);
+
+        final String text = "a\n";
+        // build the layout to be tested
+        builder = StaticLayout.Builder.obtain("a\n", 0, text.length(), mDefaultPaint,
+                DEFAULT_OUTER_WIDTH);
+        builder.setLineSpacing(spacingAdd, spacingMult).setIncludePad(false);
+        final StaticLayout layout = builder.build();
+
+        assertEquals(comparisonLayout.getLineCount(), layout.getLineCount());
+        assertEquals(heightWithLineSpacing, layout.getLineBottom(0) - layout.getLineTop(0));
+        assertEquals(heightWithoutLineSpacing, layout.getLineBottom(1) - layout.getLineTop(1));
+    }
+
+    @Test
     public void testBuilder_setJustificationMode() {
         StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
                 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
@@ -222,6 +268,7 @@
         // without causing any exceptions.
         assertNotNull(layout);
     }
+
     /*
      * Get the line number corresponding to the specified vertical position.
      *  If you ask for a position above 0, you get 0. above 0 means pixel above the fire line
@@ -1190,4 +1237,232 @@
                 .setEllipsize(TruncateAt.END).build();
         layout.getPrimaryHorizontal(layout.getText().length());
     }
+
+    // TODO: Re-enable once http://b/65207701 is fixed.
+    @Test
+    @Suppress
+    public void testGetLineWidth() {
+        final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
+        final int lineWidth = (int) (wholeWidth / 10.0f);  // Make 10 lines per paragraph.
+        final String multiParaTestString =
+                LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM;
+        final Layout layout = StaticLayout.Builder.obtain(multiParaTestString, 0,
+                multiParaTestString.length(), mDefaultPaint, lineWidth)
+                .build();
+        for (int i = 0; i < layout.getLineCount(); i++) {
+            assertTrue(layout.getLineWidth(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.
+        final int indentWidth = (int) (lineWidth * 0.3f);  // Make 30% indent.
+        final String multiParaTestString =
+                LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM;
+        final Layout layout = StaticLayout.Builder.obtain(multiParaTestString, 0,
+                multiParaTestString.length(), mDefaultPaint, lineWidth)
+                .setIndents(new int[] { indentWidth }, null)
+                .build();
+        for (int i = 0; i < layout.getLineCount(); i++) {
+            assertTrue(layout.getLineWidth(i) <= lineWidth - indentWidth);
+        }
+    }
+
+    private static Bitmap drawToBitmap(Layout l) {
+        final Bitmap bmp = Bitmap.createBitmap(l.getWidth(), l.getHeight(), Bitmap.Config.RGB_565);
+        final Canvas c = new Canvas(bmp);
+
+        c.save();
+        c.translate(0, 0);
+        l.draw(c);
+        c.restore();
+        return bmp;
+    }
+
+    private static String textPaintToString(TextPaint p) {
+        return "{"
+            + "mTextSize=" + p.getTextSize() + ", "
+            + "mTextSkewX=" + p.getTextSkewX() + ", "
+            + "mTextScaleX=" + p.getTextScaleX() + ", "
+            + "mLetterSpacing=" + p.getLetterSpacing() + ", "
+            + "mFlags=" + p.getFlags() + ", "
+            + "mTextLocales=" + p.getTextLocales() + ", "
+            + "mFontVariationSettings=" + p.getFontVariationSettings() + ", "
+            + "mTypeface=" + p.getTypeface() + ", "
+            + "mFontFeatureSettings=" + p.getFontFeatureSettings()
+            + "}";
+    }
+
+    private static String directionToString(TextDirectionHeuristic dir) {
+        if (dir == TextDirectionHeuristics.LTR) {
+            return "LTR";
+        } else if (dir == TextDirectionHeuristics.RTL) {
+            return "RTL";
+        } else if (dir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+            return "FIRSTSTRONG_LTR";
+        } else if (dir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+            return "FIRSTSTRONG_RTL";
+        } else if (dir == TextDirectionHeuristics.ANYRTL_LTR) {
+            return "ANYRTL_LTR";
+        } else {
+            throw new RuntimeException("Unknown Direction");
+        }
+    }
+
+    static class LayoutParam {
+        final int mStrategy;
+        final int mFrequency;
+        final TextPaint mPaint;
+        final TextDirectionHeuristic mDir;
+
+        LayoutParam(int strategy, int frequency, TextPaint paint, TextDirectionHeuristic dir) {
+            mStrategy = strategy;
+            mFrequency = frequency;
+            mPaint = new TextPaint(paint);
+            mDir = dir;
+        }
+
+        @Override
+        public String toString() {
+            return "{"
+                + "mStrategy=" + mStrategy + ", "
+                + "mFrequency=" + mFrequency + ", "
+                + "mPaint=" + textPaintToString(mPaint) + ", "
+                + "mDir=" + directionToString(mDir)
+                + "}";
+
+        }
+
+        Layout getLayout(CharSequence text, int width) {
+            return StaticLayout.Builder.obtain(text, 0, text.length(), mPaint, width)
+                .setBreakStrategy(mStrategy).setHyphenationFrequency(mFrequency)
+                .setTextDirection(mDir).build();
+        }
+
+        PrecomputedText getPrecomputedText(CharSequence text) {
+            PrecomputedText.Params param = new PrecomputedText.Params.Builder(mPaint)
+                    .setBreakStrategy(mStrategy)
+                    .setHyphenationFrequency(mFrequency)
+                    .setTextDirection(mDir).build();
+            return PrecomputedText.create(text, param);
+        }
+    };
+
+    void assertSameStaticLayout(CharSequence text, LayoutParam measuredTextParam,
+                                LayoutParam staticLayoutParam) {
+        String msg = "StaticLayout for " + staticLayoutParam + " with PrecomputedText"
+                + " created with " + measuredTextParam + " must output the same BMP.";
+
+        final float wholeWidth = mDefaultPaint.measureText(text.toString());
+        final int lineWidth = (int) (wholeWidth / 10.0f);  // Make 10 lines per paragraph.
+
+        // Static layout parameter should be used for the final output.
+        final Layout expectedLayout = staticLayoutParam.getLayout(text, lineWidth);
+
+        final PrecomputedText mt = measuredTextParam.getPrecomputedText(text);
+        final Layout resultLayout = StaticLayout.Builder.obtain(mt, 0, mt.length(),
+                staticLayoutParam.mPaint, lineWidth)
+                .setBreakStrategy(staticLayoutParam.mStrategy)
+                .setHyphenationFrequency(staticLayoutParam.mFrequency)
+                .setTextDirection(staticLayoutParam.mDir).build();
+
+        assertEquals(msg, expectedLayout.getHeight(), resultLayout.getHeight(), 0.0f);
+
+        final Bitmap expectedBMP = drawToBitmap(expectedLayout);
+        final Bitmap resultBMP = drawToBitmap(resultLayout);
+
+        assertTrue(msg, resultBMP.sameAs(expectedBMP));
+    }
+
+    @Test
+    public void testPrecomputedText() {
+        int[] breaks = {
+            Layout.BREAK_STRATEGY_SIMPLE,
+            Layout.BREAK_STRATEGY_HIGH_QUALITY,
+            Layout.BREAK_STRATEGY_BALANCED,
+        };
+
+        int[] frequencies = {
+            Layout.HYPHENATION_FREQUENCY_NORMAL,
+            Layout.HYPHENATION_FREQUENCY_FULL,
+            Layout.HYPHENATION_FREQUENCY_NONE,
+        };
+
+        TextDirectionHeuristic[] dirs = {
+            TextDirectionHeuristics.LTR,
+            TextDirectionHeuristics.RTL,
+            TextDirectionHeuristics.FIRSTSTRONG_LTR,
+            TextDirectionHeuristics.FIRSTSTRONG_RTL,
+            TextDirectionHeuristics.ANYRTL_LTR,
+        };
+
+        float[] textSizes = {
+            8.0f, 16.0f, 32.0f
+        };
+
+        LocaleList[] locales = {
+            LocaleList.forLanguageTags("en-US"),
+            LocaleList.forLanguageTags("ja-JP"),
+            LocaleList.forLanguageTags("en-US,ja-JP"),
+        };
+
+        TextPaint paint = new TextPaint();
+
+        // If the PrecomputedText is created with the same argument of the StaticLayout, generate
+        // the same bitmap.
+        for (int b : breaks) {
+            for (int f : frequencies) {
+                for (TextDirectionHeuristic dir : dirs) {
+                    for (float textSize : textSizes) {
+                        for (LocaleList locale : locales) {
+                            paint.setTextSize(textSize);
+                            paint.setTextLocales(locale);
+
+                            assertSameStaticLayout(LOREM_IPSUM,
+                                    new LayoutParam(b, f, paint, dir),
+                                    new LayoutParam(b, f, paint, dir));
+                        }
+                    }
+                }
+            }
+        }
+
+        // If the parameters are different, the output of the static layout must be
+        // same bitmap.
+        for (int bi = 0; bi < breaks.length; bi++) {
+            for (int fi = 0; fi < frequencies.length; fi++) {
+                for (int diri = 0; diri < dirs.length; diri++) {
+                    for (int sizei = 0; sizei < textSizes.length; sizei++) {
+                        for (int localei = 0; localei < locales.length; localei++) {
+                            TextPaint p1 = new TextPaint();
+                            TextPaint p2 = new TextPaint();
+
+                            p1.setTextSize(textSizes[sizei]);
+                            p2.setTextSize(textSizes[(sizei + 1) % textSizes.length]);
+
+                            p1.setTextLocales(locales[localei]);
+                            p2.setTextLocales(locales[(localei + 1) % locales.length]);
+
+                            int b1 = breaks[bi];
+                            int b2 = breaks[(bi + 1) % breaks.length];
+
+                            int f1 = frequencies[fi];
+                            int f2 = frequencies[(fi + 1) % frequencies.length];
+
+                            TextDirectionHeuristic dir1 = dirs[diri];
+                            TextDirectionHeuristic dir2 = dirs[(diri + 1) % dirs.length];
+
+                            assertSameStaticLayout(LOREM_IPSUM,
+                                    new LayoutParam(b1, f1, p1, dir1),
+                                    new LayoutParam(b2, f2, p2, dir2));
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 2fd4e59..0b4f625 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -1614,6 +1614,8 @@
         spannableStringTokens.add(new SpannableString("span 2"));
         spannableStringTokens.add(new SpannableString("span 3"));
         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
+
+        assertEquals("", TextUtils.join("|", new ArrayList<CharSequence>()));
     }
 
     @Test(expected=NullPointerException.class)
@@ -1636,6 +1638,8 @@
                 new SpannableString("span 2"),
                 new SpannableString("span 3") };
         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
+
+        assertEquals("", TextUtils.join("|", new String[0]));
     }
 
     @Test(expected=NullPointerException.class)
diff --git a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
index 13c9aa2..50c97b2 100644
--- a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
@@ -18,12 +18,15 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.provider.Settings;
 import android.support.annotation.NonNull;
@@ -312,6 +315,27 @@
         }
     }
 
+    @Test
+    public void test_ContextLocaleIsUsed() {
+        final Locale oldLocale = Locale.getDefault();
+
+        try {
+            Date date = new Date(YEAR_FROM_1900, MONTH, DAY);
+            Locale.setDefault(Locale.FRANCE);
+            final String javaResult = java.text.DateFormat.getDateInstance(
+                    java.text.DateFormat.LONG).format(date);
+
+            final Configuration config = new Configuration();
+            config.setLocales(new LocaleList(Locale.JAPAN));
+            final Context context = mContext.createConfigurationContext(config);
+            final String androidResult = DateFormat.getLongDateFormat(context).format(date);
+
+            assertNotEquals(javaResult, androidResult);
+        } finally {
+            Locale.setDefault(oldLocale);
+        }
+    }
+
     @NonNull
     private String getTimeFormat() throws IOException {
         return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
diff --git a/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java b/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
index 9c96519..4f26aea 100644
--- a/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
+++ b/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
@@ -18,7 +18,6 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.text.cts.R;
 import android.text.method.BaseKeyListener;
 import android.text.method.DateKeyListener;
@@ -29,7 +28,6 @@
 import android.text.method.QwertyKeyListener;
 import android.text.method.TextKeyListener;
 import android.text.method.TimeKeyListener;
-import android.util.Log;
 
 /**
  * This Activity is used for testing:
@@ -55,47 +53,9 @@
  */
 
 public class KeyListenerCtsActivity extends Activity {
-    private boolean mHasWindowFocus = false;
-    private final Object mHasWindowFocusLock = new Object();
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.keylistener_layout);
     }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        if (!hasFocus) {
-            Log.w("KeyListenerCtsActivity", "KeyListenerCtsActivity lost window focus");
-        }
-        synchronized(mHasWindowFocusLock) {
-            mHasWindowFocus = hasFocus;
-            mHasWindowFocusLock.notify();
-        }
-    }
-
-    /**
-     * Blocks the calling thread until the {@link KeyListenerCtsActivity} has window focus or the
-     * specified duration (in milliseconds) has passed.
-     */
-    public boolean waitForWindowFocus(long durationMillis) {
-        long elapsedMillis = SystemClock.elapsedRealtime();
-        synchronized(mHasWindowFocusLock) {
-            mHasWindowFocus = hasWindowFocus();
-            while (!mHasWindowFocus && durationMillis > 0) {
-                long newElapsedMillis = SystemClock.elapsedRealtime();
-                durationMillis -= (newElapsedMillis - elapsedMillis);
-                elapsedMillis = newElapsedMillis;
-                if (durationMillis > 0) {
-                    try {
-                        mHasWindowFocusLock.wait(durationMillis);
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return mHasWindowFocus;
-        }
-    }
 }
diff --git a/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java b/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
index 8d47c57..36f92e2 100644
--- a/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
+++ b/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
@@ -16,40 +16,86 @@
 
 package android.text.method.cts;
 
+import static android.provider.Settings.System.TEXT_AUTO_CAPS;
+
+import android.app.AppOpsManager;
 import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.text.cts.R;
 import android.text.method.KeyListener;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.widget.EditText;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
 
+import java.io.IOException;
+
 /**
  * Base class for various KeyListener tests.
  */
 public abstract class KeyListenerTestCase {
+    private static final String TAG = "KeyListenerTestCase";
+
     protected KeyListenerCtsActivity mActivity;
     protected Instrumentation mInstrumentation;
     protected EditText mTextView;
+    private int mAutoCapSetting;
 
     @Rule
     public ActivityTestRule<KeyListenerCtsActivity> mActivityRule =
             new ActivityTestRule<>(KeyListenerCtsActivity.class);
 
     @Before
-    public void setup() {
+    public void setup() throws IOException {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        mTextView = (EditText) mActivity.findViewById(R.id.keylistener_textview);
+        mTextView = mActivity.findViewById(R.id.keylistener_textview);
 
         PollingCheck.waitFor(5000, mActivity::hasWindowFocus);
     }
 
+    protected void enableAutoCapSettings() throws IOException {
+        grantWriteSettingsPermission();
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        instrumentation.runOnMainSync(() -> {
+            final ContentResolver resolver = context.getContentResolver();
+            mAutoCapSetting = Settings.System.getInt(resolver, TEXT_AUTO_CAPS, 1);
+            try {
+                Settings.System.putInt(resolver, TEXT_AUTO_CAPS, 1);
+            } catch (SecurityException e) {
+                Log.e(TAG, "Cannot set TEXT_AUTO_CAPS to 1", e);
+                // ignore
+            }
+        });
+        instrumentation.waitForIdleSync();
+    }
+
+    protected void resetAutoCapSettings() throws IOException {
+        grantWriteSettingsPermission();
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        instrumentation.runOnMainSync(() -> {
+            final ContentResolver resolver = context.getContentResolver();
+            try {
+                Settings.System.putInt(resolver, TEXT_AUTO_CAPS, mAutoCapSetting);
+            } catch (SecurityException e) {
+                Log.e(TAG, "Cannot set TEXT_AUTO_CAPS to previous value", e);
+                // ignore
+            }
+        });
+        instrumentation.waitForIdleSync();
+    }
+
     /**
      * Synchronously sets mTextView's key listener on the UI thread.
      */
@@ -63,4 +109,10 @@
         return new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN, keycode,
                 0 /* repeat */, metaState);
     }
+
+    private void grantWriteSettingsPermission() throws IOException {
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "appops set " + mActivity.getPackageName() + " "
+                        + AppOpsManager.OPSTR_WRITE_SETTINGS + " allow");
+    }
 }
diff --git a/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
index c73b7fa..206c6a5 100644
--- a/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
@@ -39,9 +39,13 @@
 import android.view.KeyEvent;
 import android.widget.TextView.BufferType;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class MultiTapKeyListenerTest extends KeyListenerTestCase {
@@ -50,6 +54,17 @@
      */
     private static final long TIME_OUT = 3000;
 
+    @Before
+    public void setup() throws IOException {
+        super.setup();
+        enableAutoCapSettings();
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        resetAutoCapSettings();
+    }
+
     @Test
     public void testConstructor() {
         new MultiTapKeyListener(Capitalize.NONE, true);
diff --git a/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
index c2e684a..c527257 100644
--- a/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
@@ -32,12 +32,28 @@
 import android.view.KeyEvent;
 import android.widget.TextView.BufferType;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class QwertyKeyListenerTest extends KeyListenerTestCase {
+
+    @Before
+    public void setup() throws IOException {
+        super.setup();
+        enableAutoCapSettings();
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        resetAutoCapSettings();
+    }
+
     @Test
     public void testConstructor() {
         new QwertyKeyListener(Capitalize.NONE, false);
diff --git a/tests/tests/text/src/android/text/style/cts/BulletSpanTest.java b/tests/tests/text/src/android/text/style/cts/BulletSpanTest.java
index 4d1985b..b6f7eed 100644
--- a/tests/tests/text/src/android/text/style/cts/BulletSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/BulletSpanTest.java
@@ -16,6 +16,7 @@
 
 package android.text.style.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Canvas;
@@ -34,20 +35,44 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BulletSpanTest {
-    @Test
-    public void testConstructor() {
-        new BulletSpan();
-        new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
-        BulletSpan b = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH, Color.RED);
 
-        final Parcel p = Parcel.obtain();
-        try {
-            b.writeToParcel(p, 0);
-            p.setDataPosition(0);
-            new BulletSpan(p);
-        } finally {
-            p.recycle();
-        }
+    @Test
+    public void testDefaultConstructor() {
+        BulletSpan bulletSpan = new BulletSpan();
+        assertEquals(calculateLeadingMargin(bulletSpan.getGapWidth(), bulletSpan.getBulletRadius()),
+                bulletSpan.getLeadingMargin(true));
+        assertEquals(0, bulletSpan.getColor());
+        assertEquals(BulletSpan.STANDARD_GAP_WIDTH, bulletSpan.getGapWidth());
+        assertTrue(bulletSpan.getBulletRadius() > 0);
+    }
+
+    @Test
+    public void testConstructorFromGapWidth() {
+        BulletSpan bulletSpan = new BulletSpan(2);
+        assertEquals(calculateLeadingMargin(2, bulletSpan.getBulletRadius()),
+                bulletSpan.getLeadingMargin(true));
+        assertEquals(0, bulletSpan.getColor());
+        assertEquals(2, bulletSpan.getGapWidth());
+        assertTrue(bulletSpan.getBulletRadius() > 0);
+    }
+
+    @Test
+    public void testConstructorFromGapWidthColor() {
+        BulletSpan bulletSpan = new BulletSpan(2, Color.RED);
+        assertEquals(bulletSpan.getBulletRadius() * 2 + bulletSpan.getGapWidth(),
+                bulletSpan.getLeadingMargin(true));
+        assertEquals(Color.RED, bulletSpan.getColor());
+        assertEquals(2, bulletSpan.getGapWidth());
+        assertTrue(bulletSpan.getBulletRadius() > 0);
+    }
+
+    @Test
+    public void testConstructorFromGapWidthColorBulletRadius() {
+        BulletSpan bulletSpan = new BulletSpan(2, Color.RED, 10);
+        assertEquals(calculateLeadingMargin(2, 10), bulletSpan.getLeadingMargin(true));
+        assertEquals(Color.RED, bulletSpan.getColor());
+        assertEquals(2, bulletSpan.getGapWidth());
+        assertEquals(10, bulletSpan.getBulletRadius());
     }
 
     @Test
@@ -72,7 +97,7 @@
         bulletSpan.drawLeadingMargin(canvas, paint, 10, 0, 10, 0, 20, text, 0, 0, true, null);
     }
 
-    @Test(expected=ClassCastException.class)
+    @Test(expected = ClassCastException.class)
     public void testDrawLeadingMarginString() {
         BulletSpan bulletSpan = new BulletSpan(10, 20);
 
@@ -81,7 +106,7 @@
         bulletSpan.drawLeadingMargin(null, null, 0, 0, 0, 0, 0, text, 0, 0, true, null);
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testDrawLeadingMarginNull() {
         BulletSpan bulletSpan = new BulletSpan(10, 20);
 
@@ -108,27 +133,35 @@
 
         Parcel p = Parcel.obtain();
         try {
-            BulletSpan bulletSpan = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH, Color.RED);
+            BulletSpan bulletSpan = new BulletSpan(2, Color.RED, 5);
             bulletSpan.writeToParcel(p, 0);
             p.setDataPosition(0);
             BulletSpan b = new BulletSpan(p);
             leadingMargin1 = b.getLeadingMargin(true);
+            assertEquals(calculateLeadingMargin(2, 5), leadingMargin1);
+            assertEquals(Color.RED, b.getColor());
+            assertEquals(2, b.getGapWidth());
         } finally {
             p.recycle();
         }
 
         p = Parcel.obtain();
         try {
-            BulletSpan bulletSpan = new BulletSpan(10, Color.BLACK);
+            BulletSpan bulletSpan = new BulletSpan();
             bulletSpan.writeToParcel(p, 0);
             p.setDataPosition(0);
             BulletSpan b = new BulletSpan(p);
             leadingMargin2 = b.getLeadingMargin(true);
+            assertEquals(calculateLeadingMargin(b.getGapWidth(), b.getBulletRadius()),
+                    leadingMargin2);
+            assertEquals(0, bulletSpan.getColor());
+            assertEquals(BulletSpan.STANDARD_GAP_WIDTH, bulletSpan.getGapWidth());
         } finally {
             p.recycle();
         }
+    }
 
-        assertTrue(leadingMargin2 > leadingMargin1);
-        // TODO: Test color. How?
+    private int calculateLeadingMargin(int gapWidth, int bulletRadius) {
+        return 2 * bulletRadius + gapWidth;
     }
 }
diff --git a/tests/tests/text/src/android/text/style/cts/QuoteSpanTest.java b/tests/tests/text/src/android/text/style/cts/QuoteSpanTest.java
index 358be07..109cafc 100644
--- a/tests/tests/text/src/android/text/style/cts/QuoteSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/QuoteSpanTest.java
@@ -17,6 +17,7 @@
 package android.text.style.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -33,15 +34,47 @@
 @RunWith(AndroidJUnit4.class)
 public class QuoteSpanTest {
     @Test
-    public void testConstructor() {
-        new QuoteSpan();
-        QuoteSpan q = new QuoteSpan(Color.RED);
+    public void testDefaultConstructor() {
+        QuoteSpan span = new QuoteSpan();
+        assertEquals(calculateLeadingMargin(span.getStripeWidth(), span.getGapWidth()),
+                span.getLeadingMargin(true));
+        assertTrue(span.getColor() != 0);
+        assertTrue(span.getGapWidth() > 0);
+        assertTrue(span.getStripeWidth() > 0);
+    }
+
+    @Test
+    public void testConstructorFromColor() {
+        QuoteSpan span = new QuoteSpan(Color.RED);
+        assertEquals(calculateLeadingMargin(span.getStripeWidth(), span.getGapWidth()),
+                span.getLeadingMargin(true));
+        assertEquals(Color.RED, span.getColor());
+        assertTrue(span.getStripeWidth() > 0);
+        assertTrue(span.getGapWidth() > 0);
+    }
+
+    @Test
+    public void testConstructorFromColorStripeWidthGapWidth() {
+        QuoteSpan span = new QuoteSpan(Color.RED, 10, 5);
+        assertEquals(calculateLeadingMargin(10, 5), span.getLeadingMargin(true));
+        assertEquals(Color.RED, span.getColor());
+        assertEquals(10, span.getStripeWidth());
+        assertEquals(5, span.getGapWidth());
+    }
+
+    @Test
+    public void testConstructorFromParcel() {
+        QuoteSpan quoteSpan = new QuoteSpan(Color.RED, 10, 5);
 
         final Parcel p = Parcel.obtain();
         try {
-            q.writeToParcel(p, 0);
+            quoteSpan.writeToParcel(p, 0);
             p.setDataPosition(0);
-            new QuoteSpan(p);
+            QuoteSpan span = new QuoteSpan(p);
+            assertEquals(calculateLeadingMargin(10, 5), span.getLeadingMargin(true));
+            assertEquals(Color.RED, span.getColor());
+            assertEquals(10, span.getStripeWidth());
+            assertEquals(5, span.getGapWidth());
         } finally {
             p.recycle();
         }
@@ -101,6 +134,8 @@
             p.setDataPosition(0);
             QuoteSpan q = new QuoteSpan(p);
             assertEquals(Color.RED, q.getColor());
+            assertTrue(q.getGapWidth() > 0);
+            assertTrue(q.getStripeWidth() > 0);
         } finally {
             p.recycle();
         }
@@ -111,8 +146,14 @@
             p.setDataPosition(0);
             QuoteSpan q = new QuoteSpan(p);
             assertEquals(Color.MAGENTA, q.getColor());
+            assertTrue(q.getGapWidth() > 0);
+            assertTrue(q.getStripeWidth() > 0);
         } finally {
             p.recycle();
         }
     }
+
+    private int calculateLeadingMargin(int stripeWidth, int gapWidth) {
+        return stripeWidth + gapWidth;
+    }
 }
diff --git a/tests/tests/text/src/android/text/style/cts/TypefaceSpanTest.java b/tests/tests/text/src/android/text/style/cts/TypefaceSpanTest.java
index ffc024a..317cd8e 100644
--- a/tests/tests/text/src/android/text/style/cts/TypefaceSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/TypefaceSpanTest.java
@@ -36,6 +36,14 @@
     private static final String FAMILY = "monospace";
 
     @Test
+    public void testConstructorFromTypeface() {
+        Typeface typeface = Typeface.create(FAMILY, Typeface.BOLD);
+        TypefaceSpan t = new TypefaceSpan(typeface);
+
+        assertEquals(typeface, t.getTypeface());
+    }
+
+    @Test
     public void testConstructor() {
         TypefaceSpan t = new TypefaceSpan(FAMILY);
 
@@ -69,7 +77,7 @@
         assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testUpdateMeasureStateNull() {
         TypefaceSpan typefaceSpan = new TypefaceSpan(FAMILY);
 
@@ -90,7 +98,7 @@
         assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testUpdateDrawStateNull() {
         TypefaceSpan typefaceSpan = new TypefaceSpan(FAMILY);
 
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 69956da..8bf2e83 100644
--- a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
+++ b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
@@ -19,6 +19,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
@@ -32,13 +37,22 @@
 import android.text.util.Linkify;
 import android.text.util.Linkify.MatchFilter;
 import android.text.util.Linkify.TransformFilter;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.widget.TextView;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 
+import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -934,6 +948,40 @@
                 domain.length(), email);
     }
 
+    @Test
+    public void testLinkifyAsync() throws Exception {
+        final String email = "wales@example.com";
+        final Spannable text = new SpannableString(email + " is an email address");
+        final int start = 0;
+        final int end = email.length();
+        final Map<String, Float> entityScores = new HashMap<>();
+        entityScores.put(TextClassifier.TYPE_EMAIL, 1f);
+        final TextClassifier classifier = mock(TextClassifier.class);
+
+        final TextView textView = new TextView(mContext);
+        textView.setText(text);
+        textView.setTextClassifier(classifier);
+        final TextLinks.Options options = TextLinks.Options.fromLinkMask(Linkify.EMAIL_ADDRESSES);
+        final Executor executor = Executors.newSingleThreadExecutor();
+        final Consumer<Integer> callback = mock(Consumer.class);
+        final TextLinks links = new TextLinks.Builder(text.toString())
+                .addLink(start, end, entityScores)
+                .build();
+
+        when(classifier.generateLinks(argThat(new EqStringMatcher(text)), eq(options)))
+            .thenReturn(links);
+        when(classifier.getMaxGenerateLinksTextLength()).thenReturn(Integer.MAX_VALUE);
+
+        final Future future = Linkify.addLinksAsync(textView, options, executor, callback);
+        future.get();
+
+        verify(callback).accept(TextLinks.STATUS_LINKS_APPLIED);
+        assertEquals(1,
+                ((Spannable) textView.getText()).getSpans(start, end, TextLinks.TextLinkSpan.class)
+                        .length);
+        // TODO: Add more Linkify async tests.
+    }
+
     // Utility functions
     private static void verifyAddLinksWithWebUrlSucceeds(String msg, String url) {
         verifyAddLinksSucceeds(msg, url, Linkify.WEB_URLS);
@@ -989,4 +1037,18 @@
         assertTrue(msg, linksAdded);
         assertEquals(msg, expected, spans[0].getURL().toString());
     }
+
+    /** Helper to match a CharSequence based on String equivalence. */
+    class EqStringMatcher implements ArgumentMatcher<CharSequence> {
+        private final String mReference;
+
+        EqStringMatcher(CharSequence reference) {
+            mReference = reference.toString();
+        }
+
+        @Override
+        public boolean matches(CharSequence arg) {
+            return mReference.equals(arg.toString());
+        }
+    }
 }
diff --git a/tests/tests/theme/Android.mk b/tests/tests/theme/Android.mk
index 71d576d..ff8536b 100644
--- a/tests/tests/theme/Android.mk
+++ b/tests/tests/theme/Android.mk
@@ -24,7 +24,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/theme/AndroidTest.xml b/tests/tests/theme/AndroidTest.xml
index 39da798..95a7a88 100644
--- a/tests/tests/theme/AndroidTest.xml
+++ b/tests/tests/theme/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Theme test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 66ceb5d..0f00913 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/transition/Android.mk b/tests/tests/transition/Android.mk
index 6e027ad..5b721e0 100644
--- a/tests/tests/transition/Android.mk
+++ b/tests/tests/transition/Android.mk
@@ -17,7 +17,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := CtsTransitionTestCases
-LOCAL_SDK_VERSION := test_current
 
 # Don't include this package in any target.
 LOCAL_MODULE_TAGS := optional
@@ -41,4 +40,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+# Enforce public / test api only
+LOCAL_SDK_VERSION := test_current
+
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/transition/AndroidTest.xml b/tests/tests/transition/AndroidTest.xml
index 4632cbe..e75e8de 100644
--- a/tests/tests/transition/AndroidTest.xml
+++ b/tests/tests/transition/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Transition test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/transition/res/layout/scene14.xml b/tests/tests/transition/res/layout/scene14.xml
new file mode 100644
index 0000000..fd2dc4b
--- /dev/null
+++ b/tests/tests/transition/res/layout/scene14.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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:transitionName="holder"
+    android:id="@+id/holder">
+    <ImageView
+        android:id="@+id/redSquare"
+        android:src="#F00"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:visibility="gone"/>
+</RelativeLayout>
diff --git a/tests/tests/transition/src/android/transition/cts/ArcMotionTest.java b/tests/tests/transition/src/android/transition/cts/ArcMotionTest.java
index 370155d..db88bde 100644
--- a/tests/tests/transition/src/android/transition/cts/ArcMotionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ArcMotionTest.java
@@ -196,7 +196,38 @@
         expected = arcWithPoint(100, 50, 0, 0, ex, ey);
         path = arcMotion.getPath(100, 50, 0, 0);
         assertPathMatches(expected, path);
+    }
 
+    @Test
+    public void horizontalAndVerticalMotion() {
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMinimumHorizontalAngle(80);
+        arcMotion.setMaximumAngle(90);
+
+        float ex = 50;
+        float ey = (float) (50 * Math.tan(Math.toRadians(40)));
+        Path expected = arcWithPoint(0, 0, 100, 0, ex, ey);
+        Path path = arcMotion.getPath(0, 0, 100, 0);
+        assertPathMatches(expected, path);
+
+        // Now move it in the opposite direction
+        expected = arcWithPoint(100, 0, 0, 0, ex, ey);
+        path = arcMotion.getPath(100, 0, 0, 0);
+        assertPathMatches(expected, path);
+
+        // Now try vertical path
+        arcMotion.setMinimumVerticalAngle(45);
+        ex = (float) (50 * Math.tan(Math.toRadians(22.5)));
+        ey = 50f;
+        expected = arcWithPoint(0, 0, 0, 100f, ex, ey);
+        path = arcMotion.getPath(0, 0, 0, 100f);
+        assertPathMatches(expected, path);
+
+        // Now move it in the opposite direction
+        arcMotion.setMinimumVerticalAngle(45);
+        expected = arcWithPoint(0, 100, 0, 0f, ex, ey);
+        path = arcMotion.getPath(0, 100, 0, 0f);
+        assertPathMatches(expected, path);
     }
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java b/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
index d11051f..c35a0c0 100644
--- a/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
@@ -36,6 +36,8 @@
 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;
@@ -125,8 +127,7 @@
 
         startTransition(R.layout.scene6);
 
-        // now delay for at least a few frames before interrupting the transition
-        Thread.sleep(150);
+        waitForMiddleOfTransition();
         resetChangeBoundsTransition();
         startTransition(R.layout.scene6);
 
@@ -144,8 +145,7 @@
 
         startTransition(R.layout.scene6);
 
-        // now delay for at least a few frames before interrupting the transition
-        Thread.sleep(150);
+        waitForMiddleOfTransition();
 
         resetChangeBoundsTransition();
         mChangeBounds.setResizeClip(true);
@@ -165,8 +165,7 @@
 
         startTransition(R.layout.scene6);
 
-        // now delay for at least a few frames before reversing
-        Thread.sleep(150);
+        waitForMiddleOfTransition();
         // reverse the transition back to scene1
         resetChangeBoundsTransition();
         startTransition(R.layout.scene1);
@@ -184,9 +183,7 @@
         validateInScene1();
 
         startTransition(R.layout.scene6);
-
-        // now delay for at least a few frames before reversing
-        Thread.sleep(150);
+        waitForMiddleOfTransition();
 
         // reverse the transition back to scene1
         resetChangeBoundsTransition();
@@ -199,6 +196,21 @@
         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 boolean isRestartingAnimation() {
         View red = mActivity.findViewById(R.id.redSquare);
         View green = mActivity.findViewById(R.id.greenSquare);
diff --git a/tests/tests/transition/src/android/transition/cts/ChangeClipBoundsTest.java b/tests/tests/transition/src/android/transition/cts/ChangeClipBoundsTest.java
index 400bc63..3489391 100644
--- a/tests/tests/transition/src/android/transition/cts/ChangeClipBoundsTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ChangeClipBoundsTest.java
@@ -18,7 +18,6 @@
 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.graphics.Rect;
 import android.support.test.filters.MediumTest;
@@ -27,6 +26,8 @@
 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;
@@ -59,17 +60,8 @@
             redSquare.setClipBounds(newClip);
         });
         waitForStart();
-        Thread.sleep(150);
-        mActivityRule.runOnUiThread(() -> {
-            Rect midClip = redSquare.getClipBounds();
-            assertNotNull(midClip);
-            assertTrue(midClip.left > 0 && midClip.left < newClip.left);
-            assertTrue(midClip.top > 0 && midClip.top < newClip.top);
-            assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
-            assertTrue(midClip.bottom < redSquare.getBottom() &&
-                    midClip.bottom > newClip.bottom);
-        });
-        waitForEnd(400);
+        PollingCheck.waitFor(isMiddleOfClipping(redSquare, newClip));
+        waitForEnd(600);
 
         mActivityRule.runOnUiThread(() -> {
             final Rect endRect = redSquare.getClipBounds();
@@ -83,19 +75,21 @@
             redSquare.setClipBounds(null);
         });
         waitForStart();
-        Thread.sleep(150);
-        mActivityRule.runOnUiThread(() -> {
-            Rect midClip = redSquare.getClipBounds();
-            assertNotNull(midClip);
-            assertTrue(midClip.left > 0 && midClip.left < newClip.left);
-            assertTrue(midClip.top > 0 && midClip.top < newClip.top);
-            assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
-            assertTrue(midClip.bottom < redSquare.getBottom() &&
-                    midClip.bottom > newClip.bottom);
-        });
-        waitForEnd(400);
+        PollingCheck.waitFor(isMiddleOfClipping(redSquare, newClip));
+        waitForEnd(600);
 
         mActivityRule.runOnUiThread(() -> assertNull(redSquare.getClipBounds()));
     }
+
+    private static PollingCheck.PollingCheckCondition isMiddleOfClipping(final View redSquare,
+            final Rect newClip) {
+        return () -> {
+            Rect midClip = redSquare.getClipBounds();
+            return midClip.left > 0 && midClip.left < newClip.left
+                    && midClip.top > 0 && midClip.top < newClip.top
+                    && midClip.right < redSquare.getRight() && midClip.right > newClip.right
+                    && midClip.bottom < redSquare.getBottom() && midClip.bottom > newClip.bottom;
+        };
+    }
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/ChangeScrollTest.java b/tests/tests/transition/src/android/transition/cts/ChangeScrollTest.java
index b0e1f9e..01969cb 100644
--- a/tests/tests/transition/src/android/transition/cts/ChangeScrollTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ChangeScrollTest.java
@@ -28,6 +28,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ChangeScrollTest extends BaseTransitionTest {
@@ -45,15 +48,22 @@
     @Test
     public void testChangeScroll() throws Throwable {
         enterScene(R.layout.scene5);
+        final CountDownLatch scrollChanged = new CountDownLatch(1);
+        final View.OnScrollChangeListener listener = (v, newX, newY, oldX, oldY) -> {
+            if (newX != 0 && newY != 0) {
+                scrollChanged.countDown();
+            }
+        };
         mActivityRule.runOnUiThread(() -> {
             final View view = mActivity.findViewById(R.id.text);
             assertEquals(0, view.getScrollX());
             assertEquals(0, view.getScrollY());
             TransitionManager.beginDelayedTransition(mSceneRoot, mChangeScroll);
             view.scrollTo(150, 300);
+            view.setOnScrollChangeListener(listener);
         });
         waitForStart();
-        Thread.sleep(150);
+        assertTrue(scrollChanged.await(1, TimeUnit.SECONDS));
         mActivityRule.runOnUiThread(() -> {
             final View view = mActivity.findViewById(R.id.text);
             final int scrollX = view.getScrollX();
@@ -63,7 +73,7 @@
             assertTrue(scrollY > 0);
             assertTrue(scrollY < 300);
         });
-        waitForEnd(400);
+        waitForEnd(800);
         mActivityRule.runOnUiThread(() -> {
             final View view = mActivity.findViewById(R.id.text);
             assertEquals(150, view.getScrollX());
diff --git a/tests/tests/transition/src/android/transition/cts/FadeTest.java b/tests/tests/transition/src/android/transition/cts/FadeTest.java
index fe93d1b..48f6aa1 100644
--- a/tests/tests/transition/src/android/transition/cts/FadeTest.java
+++ b/tests/tests/transition/src/android/transition/cts/FadeTest.java
@@ -16,6 +16,7 @@
 package android.transition.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -30,6 +31,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.Fade;
+import android.transition.Scene;
 import android.transition.Transition;
 import android.transition.TransitionListenerAdapter;
 import android.transition.TransitionManager;
@@ -81,12 +83,12 @@
         enterScene(R.layout.scene4);
         startTransition(R.layout.scene1);
         verify(mListener, never()).onTransitionEnd(any());
-        waitForEnd(400);
+        waitForEnd(1000);
 
         resetListener();
         startTransition(R.layout.scene4);
         verify(mListener, never()).onTransitionEnd(any());
-        waitForEnd(400);
+        waitForEnd(1000);
 
         // Now only animate in
         mFade = new Fade(Fade.IN);
@@ -94,7 +96,7 @@
         resetListener();
         startTransition(R.layout.scene1);
         verify(mListener, never()).onTransitionEnd(any());
-        waitForEnd(400);
+        waitForEnd(1000);
 
         // No animation since it should only animate in
         resetListener();
@@ -112,7 +114,7 @@
         resetListener();
         startTransition(R.layout.scene4);
         verify(mListener, never()).onTransitionEnd(any());
-        waitForEnd(400);
+        waitForEnd(1000);
     }
 
     @Test
@@ -175,6 +177,32 @@
         assertTrue(onDisappearCalled.await(0, TimeUnit.SECONDS));
     }
 
+    // After a transition, a transitioned view as part of a scene should not be removed
+    @Test
+    public void endVisibilityIsCorrect() throws Throwable {
+        final Scene[] scene11 = new Scene[1];
+        mActivityRule.runOnUiThread(() -> {
+            View view = mActivity.getLayoutInflater().inflate(R.layout.scene11, mSceneRoot, false);
+            scene11[0] = new Scene(mSceneRoot, view);
+        });
+        final Scene scene14 = loadScene(R.layout.scene14);
+        enterScene(scene11[0]);
+
+        assertNotNull(mActivity.findViewById(R.id.redSquare));
+
+        // We don't really care how short the duration is, so let's make it really short
+        mFade.setDuration(1);
+
+        startTransition(scene14);
+        waitForEnd(1000); // should be much shorter, but why worry about it?
+
+        assertNotNull(mActivity.findViewById(R.id.redSquare));
+
+        resetListener();
+        startTransition(scene11[0]);
+        assertNotNull(mActivity.findViewById(R.id.redSquare));
+    }
+
     private Bitmap createViewBitmap(View view) {
         int bitmapWidth = view.getWidth();
         int bitmapHeight = view.getHeight();
diff --git a/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java b/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
index b4bf800..f70db09 100644
--- a/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
+++ b/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
@@ -33,6 +33,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.compatibility.common.util.PollingCheck;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -223,6 +225,13 @@
         }
     }
 
+    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 verifyTranslation(int slideEdge, View view) {
         switch (slideEdge) {
             case Gravity.LEFT:
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java b/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
index f48f780..27cd3f6 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
@@ -16,6 +16,7 @@
 package android.transition.cts;
 
 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 static org.mockito.Matchers.any;
@@ -73,17 +74,20 @@
     public void testDefaultBeginDelayedTransition() throws Throwable {
         enterScene(R.layout.scene1);
         final CountDownLatch startLatch = new CountDownLatch(1);
-        mSceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        startLatch.countDown();
-                        return true;
-                    }
-                });
-        mActivityRule.runOnUiThread(() -> TransitionManager.beginDelayedTransition(mSceneRoot));
-        enterScene(R.layout.scene6);
+        final Scene scene6 = loadScene(R.layout.scene6);
+        mActivityRule.runOnUiThread(() -> {
+            mSceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                    new ViewTreeObserver.OnPreDrawListener() {
+                        @Override
+                        public boolean onPreDraw() {
+                            mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+                            startLatch.countDown();
+                            return true;
+                        }
+                    });
+            TransitionManager.beginDelayedTransition(mSceneRoot);
+            scene6.enter();
+        });
         assertTrue(startLatch.await(500, TimeUnit.MILLISECONDS));
         ensureRedSquareIsMoving();
         endTransition();
@@ -95,14 +99,27 @@
         // We should see a ChangeBounds on redSquare
         final Rect position = new Rect(view.getLeft(), view.getTop(), view.getRight(),
                 view.getBottom());
+
         final CountDownLatch latch = new CountDownLatch(1);
-        view.postOnAnimationDelayed(() -> {
-            Rect next = new Rect(view.getLeft(), view.getTop(), view.getRight(),
-                    view.getBottom());
-            assertTrue(!next.equals(position));
-            latch.countDown();
-        }, 20);
+        final Rect[] nextArr = new Rect[1];
+        view.postOnAnimation(new Runnable() {
+            // Wait at most 10 frames for the position to change
+            int mFramesToChange = 10;
+
+            @Override
+            public void run() {
+                nextArr[0] = new Rect(view.getLeft(), view.getTop(), view.getRight(),
+                        view.getBottom());
+                mFramesToChange--;
+                if (nextArr[0].equals(position) && mFramesToChange > 0) {
+                    view.postOnAnimation(this);
+                } else {
+                    latch.countDown();
+                }
+            }
+        });
         assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        assertNotEquals(position, nextArr[0]);
     }
 
     @Test
@@ -129,17 +146,19 @@
     public void testDefaultGo() throws Throwable {
         enterScene(R.layout.scene1);
         final CountDownLatch startLatch = new CountDownLatch(1);
-        mSceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        startLatch.countDown();
-                        return true;
-                    }
-                });
         final Scene scene6 = loadScene(R.layout.scene6);
-        mActivityRule.runOnUiThread(() -> TransitionManager.go(scene6));
+        mActivityRule.runOnUiThread(() -> {
+            mSceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                    new ViewTreeObserver.OnPreDrawListener() {
+                        @Override
+                        public boolean onPreDraw() {
+                            mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+                            startLatch.countDown();
+                            return true;
+                        }
+                    });
+            TransitionManager.go(scene6);
+        });
         assertTrue(startLatch.await(500, TimeUnit.MILLISECONDS));
         ensureRedSquareIsMoving();
         endTransition();
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java b/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
index 60cd5b0..489fb2d 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.assertSame;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -67,6 +66,7 @@
     public void testTransitionSequentially() throws Throwable {
         TransitionSet transitionSet = new TransitionSet();
         Fade fade = new Fade();
+        fade.setDuration(500);
         final Transition.TransitionListener fadeListener =
                 mock(Transition.TransitionListener.class);
         fade.addListener(fadeListener);
@@ -85,13 +85,19 @@
 
         enterScene(R.layout.scene1);
         startTransition(R.layout.scene3);
-        mActivityRule.runOnUiThread(() -> {
-            verify(fadeListener, times(1)).onTransitionStart(any());
-            verify(changeBoundsListener, never()).onTransitionStart(any());
-        });
-        verify(fadeListener, within(400)).onTransitionEnd(any());
-        mActivityRule.runOnUiThread(
-                () -> verify(changeBoundsListener, times(1)).onTransitionStart(any()));
+        verify(fadeListener, within(500)).onTransitionStart(any());
+        verify(fadeListener, times(1)).onTransitionStart(any());
+
+        // change bounds shouldn't start until after fade finishes
+        verify(fadeListener, times(0)).onTransitionEnd(any());
+        verify(changeBoundsListener, times(0)).onTransitionStart(any());
+
+        // now wait for the fade transition to end
+        verify(fadeListener, within(1000)).onTransitionEnd(any());
+
+        // The change bounds should start soon after
+        verify(changeBoundsListener, within(500)).onTransitionStart(any());
+        verify(changeBoundsListener, times(1)).onTransitionStart(any());
     }
 
     @Test
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionTest.java b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
index 9ba201a..683c222 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
@@ -574,7 +574,7 @@
         resetListener();
         startTransition(R.layout.scene1);
         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
-        waitForEnd(400);
+        waitForEnd(1000);
     }
 
     @Test
diff --git a/tests/tests/tv/Android.mk b/tests/tests/tv/Android.mk
index 30429d3..8188d28 100644
--- a/tests/tests/tv/Android.mk
+++ b/tests/tests/tv/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_PACKAGE_NAME := CtsTvTestCases
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
diff --git a/tests/tests/uiautomation/Android.mk b/tests/tests/uiautomation/Android.mk
index eb6ed68..347c55f 100644
--- a/tests/tests/uiautomation/Android.mk
+++ b/tests/tests/uiautomation/Android.mk
@@ -23,7 +23,9 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ub-uiautomator legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/uiautomation/AndroidTest.xml b/tests/tests/uiautomation/AndroidTest.xml
index 183a08e..c4bcdd6 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
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 9d33a0a..05395b3 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -19,12 +19,12 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.UiAutomation;
-import android.app.uiautomation.cts.R;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.view.FrameStats;
@@ -61,6 +61,7 @@
         grantWriteSecureSettingsPermission(uiAutomation);
     }
 
+    @Presubmit
     public void testWindowContentFrameStats() throws Exception {
         Activity activity = null;
         try {
@@ -169,6 +170,7 @@
         }
     }
 
+    @Presubmit
     public void testWindowAnimationFrameStats() throws Exception {
         Activity firstActivity = null;
         Activity secondActivity = null;
@@ -265,6 +267,7 @@
         assertEquals(stats.getEndTimeNano(), FrameStats.UNDEFINED_TIME_NANO);
     }
 
+    @Presubmit
     public void testUsingUiAutomationAfterDestroy_shouldThrowException() {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         uiAutomation.destroy();
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
index cf46157..ca598bc 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
@@ -49,7 +49,7 @@
 
     public boolean isConnected() {
         try {
-            if (getRootInActiveWindow() == null) {
+            if (getServiceInfo() == null) {
                 return false;
             }
             return true;
diff --git a/tests/tests/uidisolation/Android.mk b/tests/tests/uidisolation/Android.mk
index e9ee3e9..a820c95 100644
--- a/tests/tests/uidisolation/Android.mk
+++ b/tests/tests/uidisolation/Android.mk
@@ -24,9 +24,13 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctstestserver legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctstestserver
 
-LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    org.apache.http.legacy \
+    android.test.runner.stubs \
+    android.test.base.stubs \
+
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/uidisolation/AndroidTest.xml b/tests/tests/uidisolation/AndroidTest.xml
index 02df495..0a83f12 100644
--- a/tests/tests/uidisolation/AndroidTest.xml
+++ b/tests/tests/uidisolation/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <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="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/uirendering/Android.mk b/tests/tests/uirendering/Android.mk
index 9cc07b9..ec98a0e 100644
--- a/tests/tests/uirendering/Android.mk
+++ b/tests/tests/uirendering/Android.mk
@@ -24,15 +24,14 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
     ctsdeviceutillegacy \
     ctstestrunner \
     mockito-target-minus-junit4 \
-    android-support-test \
-    legacy-android-test
+    android-support-test
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/uirendering/AndroidManifest.xml b/tests/tests/uirendering/AndroidManifest.xml
index b100266..c11c1dd 100644
--- a/tests/tests/uirendering/AndroidManifest.xml
+++ b/tests/tests/uirendering/AndroidManifest.xml
@@ -31,13 +31,10 @@
                   android:theme="@style/WhiteBackgroundTheme"
                   android:screenOrientation="locked"
                   android:resizeableActivity="false" />
-        <activity android:name="android.uirendering.cts.testinfrastructure.MaterialActivity"
-                  android:theme="@android:style/Theme.Material.Light"
-                  android:screenOrientation="locked" />
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="android.uirendering.cts.runner.UiRenderingRunner"
                      android:targetPackage="android.uirendering.cts">
     </instrumentation>
 
diff --git a/tests/tests/uirendering/AndroidTest.xml b/tests/tests/uirendering/AndroidTest.xml
index b8855b1..17ec76f 100644
--- a/tests/tests/uirendering/AndroidTest.xml
+++ b/tests/tests/uirendering/AndroidTest.xml
@@ -23,5 +23,6 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.uirendering.cts" />
         <option name="runtime-hint" value="11m55s" />
+        <option name="runner" value="android.uirendering.cts.runner.UiRenderingRunner" />
     </test>
 </configuration>
diff --git a/tests/tests/uirendering/res/drawable/circle.xml b/tests/tests/uirendering/res/drawable/circle.xml
new file mode 100644
index 0000000..9d47a8a
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable/circle.xml
@@ -0,0 +1,29 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="90px"
+        android:width="90px"
+        android:viewportHeight="1"
+        android:viewportWidth="1" >
+
+    <group>
+        <path
+            android:name="box0"
+            android:pathData="m0,0.5a0.5,0.5 0 1,0 1,0a0.5,0.5 0 1,0 -1,0"
+            android:fillColor="#FF0000" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/layout/simple_shadow_layout.xml b/tests/tests/uirendering/res/layout/simple_shadow_layout.xml
index 2f21df0..042c2a9 100644
--- a/tests/tests/uirendering/res/layout/simple_shadow_layout.xml
+++ b/tests/tests/uirendering/res/layout/simple_shadow_layout.xml
@@ -17,7 +17,8 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/test_width"
     android:layout_height="@dimen/test_height">
-    <View android:layout_width="40px"
+    <View android:id="@+id/shadow_view"
+        android:layout_width="40px"
         android:layout_height="40px"
         android:translationX="25px"
         android:translationY="25px"
diff --git a/tests/tests/uirendering/res/values-television/dimens.xml b/tests/tests/uirendering/res/values-television/dimens.xml
new file mode 100644
index 0000000..6d5b6d1
--- /dev/null
+++ b/tests/tests/uirendering/res/values-television/dimens.xml
@@ -0,0 +1,18 @@
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+       Licensed under the Apache License, Version 2.0 (the "License");
+       you may not use this file except in compliance with the License.
+       You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing, software
+       distributed under the License is distributed on an "AS IS" BASIS,
+       WITHOUT 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>
+    <item type="dimen" format="float" name="expected_ambient_shadow_alpha">0.15</item>
+    <item type="dimen" format="float" name="expected_spot_shadow_alpha">0.3</item>
+</resources>
diff --git a/tests/tests/uirendering/res/values/dimens.xml b/tests/tests/uirendering/res/values/dimens.xml
index 1c304d3..4228611 100644
--- a/tests/tests/uirendering/res/values/dimens.xml
+++ b/tests/tests/uirendering/res/values/dimens.xml
@@ -15,4 +15,7 @@
 <resources>
     <dimen name="test_width">90px</dimen>
     <dimen name="test_height">90px</dimen>
+
+    <item type="dimen" format="float" name="expected_ambient_shadow_alpha">0.039</item>
+    <item type="dimen" format="float" name="expected_spot_shadow_alpha">0.19</item>
 </resources>
diff --git a/tests/tests/uirendering/res/values/themes.xml b/tests/tests/uirendering/res/values/themes.xml
index f08f029..c12ca95 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.Holo.NoActionBar.Fullscreen">
+    <style name="WhiteBackgroundTheme" 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>
@@ -22,5 +22,7 @@
         <item name="android:windowBackground">@android:color/white</item>
         <item name="android:windowContentTransitions">false</item>
         <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:ambientShadowAlpha">0.039</item>
+        <item name="android:spotShadowAlpha">0.19</item>
     </style>
 </resources>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointVerifier.java
index c97e020..31cd282 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointVerifier.java
@@ -28,10 +28,10 @@
  */
 public class SamplePointVerifier extends BitmapVerifier {
     private static final String TAG = "SamplePoint";
-    private static final int DEFAULT_TOLERANCE = 20;
+    public static final int DEFAULT_TOLERANCE = 20;
     private final Point[] mTestPoints;
     private final int[] mExpectedColors;
-    private final int mTolerance;
+    protected final int mTolerance;
 
     public SamplePointVerifier(Point[] testPoints, int[] expectedColors) {
         this(testPoints, expectedColors, DEFAULT_TOLERANCE);
@@ -74,4 +74,18 @@
     protected boolean verifyPixel(int color, int expectedColor) {
         return CompareUtils.verifyPixelWithThreshold(color, expectedColor, mTolerance);
     }
+
+    public interface VerifyPixelColor {
+        boolean verifyPixel(int color);
+    }
+
+    public static SamplePointVerifier create(Point[] testPoints, int[] expectedColors,
+            int tolerance, VerifyPixelColor verifier) {
+        return new SamplePointVerifier(testPoints, expectedColors, tolerance) {
+            @Override
+            protected boolean verifyPixel(int color, int expectedColor) {
+                return super.verifyPixel(color, expectedColor) && verifier.verifyPixel(color);
+            }
+        };
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
new file mode 100644
index 0000000..423b416
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.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.uirendering.cts.runner;
+
+import android.support.test.runner.AndroidJUnitRunner;
+
+/**
+ * TODO: Do some cool stuff we also want like sharing DrawActivity cross-class.
+ */
+public class UiRenderingRunner extends AndroidJUnitRunner {
+
+    @Override
+    protected void waitForActivitiesToComplete() {
+        // No.
+    }
+
+    @Override
+    public void onDestroy() {
+        // Ok now wait if necessary
+        super.waitForActivitiesToComplete();
+
+        super.onDestroy();
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java
new file mode 100644
index 0000000..8c777f8
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.testclasses;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+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.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AlphaBlendTest extends ActivityTestBase {
+
+    class ViewWithAlpha extends View {
+        public ViewWithAlpha(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawColor(Color.RED);
+            canvas.drawColor(Color.BLUE);
+        }
+
+        @Override
+        public boolean hasOverlappingRendering() {
+            return false;
+        }
+    }
+
+    /*
+     * The following test verifies that a RED and BLUE paints on a non-overlapping view with a 0.5f
+     * alpha blends correctly with a BLACK parent (without using an offscreen surface).
+     */
+    @Test
+    public void testBlendAlphaNonOverlappingView() {
+
+        ViewInitializer initializer = new ViewInitializer() {
+
+            @Override
+            public void initializeView(View view) {
+                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                root.setBackgroundColor(Color.BLACK);
+
+                final ViewWithAlpha child = new ViewWithAlpha(view.getContext());
+
+                child.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.FILL_PARENT,  ViewGroup.LayoutParams.FILL_PARENT));
+                child.setAlpha(0.5f);
+                root.addView(child);
+            }
+        };
+
+        createTest()
+            .addLayout(R.layout.frame_layout, initializer, true)
+            .runWithVerifier(new ColorVerifier(0xff40007f));
+    }
+}
+
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
index ce7e4b5..c88a006 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
@@ -16,21 +16,25 @@
 
 package android.uirendering.cts.testclasses;
 
+import static org.junit.Assert.assertEquals;
+
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Picture;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
 import android.uirendering.cts.bitmapverifiers.ColorCountVerifier;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.ViewInitializer;
 import android.view.FrameMetrics;
@@ -215,4 +219,26 @@
                 .addLayout(R.layout.frame_layout, initializer, true)
                 .runWithAnimationVerifier(new BlueOrRedVerifier());
     }
+
+    @Test
+    public void testCreateFromPicture() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        Picture picture = new Picture();
+        {
+            Canvas canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT);
+            Paint p = new Paint();
+            p.setAntiAlias(false);
+            p.setColor(Color.BLUE);
+            canvas.drawRect(rect, p);
+            picture.endRecording();
+        }
+        Bitmap bitmap = Bitmap.createBitmap(picture, picture.getWidth(),
+                picture.getHeight(), Bitmap.Config.ARGB_8888);
+        assertEquals(TEST_WIDTH, bitmap.getWidth());
+        assertEquals(TEST_HEIGHT, bitmap.getHeight());
+        assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
+        createTest().addCanvasClient((canvas, width, height) -> {
+            canvas.drawBitmap(bitmap, 0, 0, null);
+        }, true).runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE, rect));
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
index 1acd70a..2aaf432 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
@@ -15,6 +15,7 @@
  */
 package android.uirendering.cts.testclasses;
 
+import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -29,6 +30,7 @@
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -123,12 +125,11 @@
     }
 
 
-    @Override
+    @Before
     public void setUp() {
-        super.setUp();
-
         // temporary - ensure test isn't capturing window bg only
-        getInstrumentation().runOnMainSync(() -> getActivity().getWindow().setBackgroundDrawable(
+        final Activity activity = getActivity();
+        getInstrumentation().runOnMainSync(() -> activity.getWindow().setBackgroundDrawable(
                         new ColorDrawable(Color.GREEN)));
 
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java
new file mode 100644
index 0000000..4fd227d
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.testclasses;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ColorFilterTests extends ActivityTestBase {
+
+    @Test
+    public void testColorMatrix() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Bitmap whiteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                    whiteBitmap.eraseColor(Color.WHITE);
+
+                    Paint paint = new Paint();
+                    canvas.drawBitmap(whiteBitmap, 0, 0, paint);
+
+                    paint.setAntiAlias(true);
+                    paint.setColorFilter(new ColorMatrixColorFilter(new float[] {
+                            -1, 0, 0, 0, 255,
+                            0, -1, 0, 0, 255,
+                            0, 0, -1, 0, 255,
+                            0, 0, 0, 1, 0
+                            }));
+                    canvas.drawBitmap(whiteBitmap, 0, 0, paint);
+
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLACK));
+    }
+
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorSpaceTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorSpaceTests.java
index 13c8e91..e950c3a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorSpaceTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorSpaceTests.java
@@ -64,7 +64,7 @@
         createTest()
                 .addCanvasClient("Draw_DisplayP3_8888",
                         (c, w, h) -> drawAsset(c, bitmap8888), true)
-                .addCanvasClientWithoutUsingPicture(
+                .addCanvasClient(
                         (c, w, h) -> drawAsset(c, bitmapHardware), true)
                 .runWithVerifier(new SamplePointVerifier(
                         new Point[] {
@@ -96,7 +96,7 @@
         createTest()
                 .addCanvasClient("Draw_ProPhotoRGB_8888",
                         (c, w, h) -> drawAsset(c, bitmap8888), true)
-                .addCanvasClientWithoutUsingPicture(
+                .addCanvasClient(
                         (c, w, h) -> drawAsset(c, bitmapHardware), true)
                 .runWithVerifier(new SamplePointVerifier(
                         new Point[] {
@@ -129,7 +129,7 @@
         createTest()
                 .addCanvasClient("Draw_AdobeRGB_Translucent_8888",
                         (c, w, h) -> drawTranslucentAsset(c, bitmap8888), true)
-                .addCanvasClientWithoutUsingPicture(
+                .addCanvasClient(
                         (c, w, h) -> drawTranslucentAsset(c, bitmapHardware), true)
                 .runWithVerifier(new SamplePointVerifier(
                         new Point[] { point(0, 0) },
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
index 25abcbf..57462b6 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
@@ -15,38 +15,37 @@
  */
 package android.uirendering.cts.testclasses;
 
+import static android.uirendering.cts.util.MockVsyncHelper.nextFrame;
+
 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 static org.mockito.Matchers.anyFloat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.app.Activity;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
-import android.support.test.rule.ActivityTestRule;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier;
-import android.uirendering.cts.testinfrastructure.MaterialActivity;
+import android.uirendering.cts.testinfrastructure.Tracer;
 import android.uirendering.cts.util.BitmapAsserter;
+import android.uirendering.cts.util.MockVsyncHelper;
+import android.view.ContextThemeWrapper;
 import android.widget.EdgeEffect;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
-@MediumTest
+@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class EdgeEffectTests {
 
@@ -54,26 +53,26 @@
     private static final int HEIGHT = 90;
 
     @Rule
-    public TestName name = new TestName();
-
-    @Rule
-    public ActivityTestRule<MaterialActivity> mActivityRule = new ActivityTestRule<>(
-            MaterialActivity.class);
+    public Tracer name = new Tracer();
 
     private BitmapAsserter mBitmapAsserter = new BitmapAsserter(this.getClass().getSimpleName(),
             name.getMethodName());
 
+    private Context mThemeContext;
+
     interface EdgeEffectInitializer {
         void initialize(EdgeEffect edgeEffect);
     }
 
-    private Activity getActivity() {
-        return mActivityRule.getActivity();
+    private Context getContext() {
+        return mThemeContext;
     }
 
     @Before
     public void setUp() {
-        mBitmapAsserter.setUp(getActivity());
+        final Context targetContext = InstrumentationRegistry.getTargetContext();
+        mThemeContext = new ContextThemeWrapper(targetContext,
+                android.R.style.Theme_Material_Light);
     }
 
     private static class EdgeEffectValidator extends PerPixelBitmapVerifier {
@@ -100,7 +99,7 @@
         Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(bitmap);
         canvas.drawColor(Color.BLACK);
-        EdgeEffect edgeEffect = new EdgeEffect(getActivity());
+        EdgeEffect edgeEffect = new EdgeEffect(getContext());
         edgeEffect.setSize(WIDTH, HEIGHT);
         edgeEffect.setColor(Color.RED);
         assertEquals(Color.RED, edgeEffect.getColor());
@@ -150,7 +149,7 @@
 
     @Test
     public void testIsFinished() {
-        EdgeEffect effect = new EdgeEffect(getActivity());
+        EdgeEffect effect = new EdgeEffect(getContext());
         assertTrue(effect.isFinished());
         effect.onPull(0.5f);
         assertFalse(effect.isFinished());
@@ -158,7 +157,7 @@
 
     @Test
     public void testFinish() {
-        EdgeEffect effect = new EdgeEffect(getActivity());
+        EdgeEffect effect = new EdgeEffect(getContext());
         effect.onPull(1);
         effect.finish();
         assertTrue(effect.isFinished());
@@ -170,14 +169,14 @@
 
     @Test
     public void testGetColor() {
-        EdgeEffect effect = new EdgeEffect(getActivity());
+        EdgeEffect effect = new EdgeEffect(getContext());
         effect.setColor(Color.GREEN);
         assertEquals(Color.GREEN, effect.getColor());
     }
 
     @Test
     public void testGetMaxHeight() {
-        EdgeEffect edgeEffect = new EdgeEffect(getActivity());
+        EdgeEffect edgeEffect = new EdgeEffect(getContext());
         edgeEffect.setSize(200, 200);
         assertTrue(edgeEffect.getMaxHeight() <= 200 * 2 + 1);
         edgeEffect.setSize(200, 0);
@@ -191,30 +190,27 @@
     // validates changes to the alpha of draw commands produced by EdgeEffect
     // over the course of an animation
     private void verifyAlpha(EdgeEffectInitializer initializer, AlphaVerifier alphaVerifier) {
-        Canvas canvas = mock(Canvas.class);
-        ArgumentCaptor<Paint> captor = ArgumentCaptor.forClass(Paint.class);
-        EdgeEffect edgeEffect = new EdgeEffect(getActivity());
-        edgeEffect.setSize(200, 200);
-        initializer.initialize(edgeEffect);
-        edgeEffect.draw(canvas);
-        verify(canvas).drawCircle(anyFloat(), anyFloat(), anyFloat(), captor.capture());
-        int oldAlpha = captor.getValue().getAlpha();
-        for (int i = 0; i < 3; i++) {
-            try {
-                Thread.sleep(20);
-            } catch (InterruptedException e) {
-                fail();
-            }
-            canvas = mock(Canvas.class);
+        MockVsyncHelper.runOnVsyncThread(() -> {
+            Canvas canvas = mock(Canvas.class);
+            ArgumentCaptor<Paint> captor = ArgumentCaptor.forClass(Paint.class);
+            EdgeEffect edgeEffect = new EdgeEffect(getContext());
+            edgeEffect.setSize(200, 200);
+            initializer.initialize(edgeEffect);
             edgeEffect.draw(canvas);
             verify(canvas).drawCircle(anyFloat(), anyFloat(), anyFloat(), captor.capture());
-            int newAlpha = captor.getValue().getAlpha();
-            alphaVerifier.verify(oldAlpha, newAlpha);
-            oldAlpha = newAlpha;
-        }
+            int oldAlpha = captor.getValue().getAlpha();
+            for (int i = 0; i < 3; i++) {
+                nextFrame();
+                canvas = mock(Canvas.class);
+                edgeEffect.draw(canvas);
+                verify(canvas).drawCircle(anyFloat(), anyFloat(), anyFloat(), captor.capture());
+                int newAlpha = captor.getValue().getAlpha();
+                alphaVerifier.verify(oldAlpha, newAlpha);
+                oldAlpha = newAlpha;
+            }
+        });
     }
 
-    @LargeTest
     @Test
     public void testOnAbsorb() {
         verifyAlpha(edgeEffect -> {
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 0763e4b..8be7032 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -22,22 +22,24 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.Picture;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.NinePatchDrawable;
+import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.uirendering.cts.R;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.test.filters.MediumTest;
 import android.uirendering.cts.bitmapcomparers.ExactComparer;
 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
 import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.util.DisplayMetrics;
 
@@ -71,7 +73,7 @@
 
     @Test
     public void testDecodeResource() {
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -80,10 +82,70 @@
     }
 
     @Test
+    public void testCreateFromPicture() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        Picture picture = new Picture();
+        {
+            Canvas canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT);
+            Paint p = new Paint();
+            p.setAntiAlias(false);
+            p.setColor(Color.BLUE);
+            canvas.drawRect(rect, p);
+            picture.endRecording();
+        }
+        Bitmap hardwareBitmap = Bitmap.createBitmap(picture);
+        assertEquals(TEST_WIDTH, hardwareBitmap.getWidth());
+        assertEquals(TEST_HEIGHT, hardwareBitmap.getHeight());
+        assertEquals(Bitmap.Config.HARDWARE, hardwareBitmap.getConfig());
+        createTest().addCanvasClient((canvas, width, height) -> {
+            canvas.drawBitmap(hardwareBitmap, 0, 0, null);
+        }, true).runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE, rect));
+    }
+
+    @Test
+    public void testReadbackThroughPicture() {
+        Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
+                HARDWARE_OPTIONS);
+        Picture picture = new Picture();
+        {
+            Canvas canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT);
+            canvas.drawColor(Color.WHITE);
+            canvas.drawBitmap(hardwareBitmap, 0, 0, null);
+            picture.endRecording();
+        }
+        assertTrue(picture.requiresHardwareAcceleration());
+        Bitmap result = Bitmap.createBitmap(picture, picture.getWidth(), picture.getHeight(),
+                Bitmap.Config.ARGB_8888);
+        assertTrue(new GoldenImageVerifier(getActivity(),
+                R.drawable.golden_robot, new MSSIMComparer(0.95)).verify(result));
+    }
+
+    @Test
+    public void testCreateScaledBitmapFromPicture() {
+        Picture picture = new Picture();
+        {
+            Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
+            Canvas canvas = picture.beginRecording(bitmap.getWidth(), bitmap.getHeight());
+            Paint paint = new Paint();
+            paint.setFilterBitmap(true);
+            paint.setAntiAlias(true);
+            canvas.drawBitmap(bitmap, 0, 0, paint);
+            picture.endRecording();
+        }
+        assertTrue(picture.requiresHardwareAcceleration());
+        Bitmap scaled = Bitmap.createBitmap(picture, 24, 24, Bitmap.Config.HARDWARE);
+        createTest().addCanvasClient((canvas, width, height) -> {
+            assertEquals(Bitmap.Config.HARDWARE, scaled.getConfig());
+            canvas.drawBitmap(scaled, 0, 0, null);
+        }, true).runWithVerifier(new GoldenImageVerifier(getActivity(),
+                R.drawable.golden_hardwaretest_create_scaled, new MSSIMComparer(0.9)));
+    }
+
+    @Test
     public void testBitmapRegionDecode() throws IOException {
         InputStream inputStream = mRes.openRawResource(R.drawable.robot);
         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(10, 15, 34, 39),
                     HARDWARE_OPTIONS);
             canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -134,7 +196,7 @@
 
     @Test
     public void testNinePatch() {
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             InputStream is = mRes.openRawResource(R.drawable.blue_padded_square);
             NinePatchDrawable ninePatch = (NinePatchDrawable) Drawable.createFromResourceStream(
                     mRes, null, is, null, HARDWARE_OPTIONS);
@@ -154,7 +216,7 @@
 
     @Test
     public void testCreateScaledBitmap() {
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Bitmap scaled = Bitmap.createScaledBitmap(hardwareBitmap, 24, 24, false);
@@ -166,7 +228,7 @@
 
     @Test
     public void testCreateSubsetBitmap() {
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Matrix matrix = new Matrix();
@@ -180,7 +242,7 @@
 
     @Test
     public void testCreateTransformedBitmap() {
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
             Matrix matrix = new Matrix();
@@ -192,7 +254,6 @@
                 R.drawable.golden_hardwaretest_create_transformed, new MSSIMComparer(0.9)));
     }
 
-
     @Test
     public void testCompressHardware() {
         Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
@@ -201,10 +262,10 @@
         assertTrue(hardwareBitmap.compress(Bitmap.CompressFormat.PNG, 50, stream));
         Bitmap decoded = BitmapFactory.decodeStream(
                 new ByteArrayInputStream(stream.toByteArray()));
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             canvas.drawBitmap(hardwareBitmap, 0, 0, null);
-        }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        }, true).addCanvasClient((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             canvas.drawBitmap(decoded, 0, 0, null);
         }, true).runWithComparer(new MSSIMComparer(0.99));
@@ -231,10 +292,10 @@
         Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(), id, options);
         assertEquals(from, bitmap.getConfig());
 
-        createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        createTest().addCanvasClient((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             canvas.drawBitmap(bitmap, 0, 0, null);
-        }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+        }, true).addCanvasClient((canvas, width, height) -> {
             canvas.drawColor(Color.CYAN);
             Bitmap copy = bitmap.copy(to, false);
             assertNotNull(copy);
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 ed0110a..7f588bd 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -47,6 +47,7 @@
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -103,6 +104,17 @@
     }
 
     @Test
+    public void testLayerPaintXfermodeWithSoftware() {
+        createTest()
+                .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
+                    Paint paint = new Paint();
+                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+                    view.setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.TRANSPARENT));
+    }
+
+    @Test
     public void testLayerPaintColorFilter() {
         // Red, fully desaturated. Note that it's not 255/3 in each channel.
         // See ColorMatrix#setSaturation()
@@ -294,9 +306,9 @@
             .runWithVerifier(new RectVerifier(Color.WHITE, Color.GREEN, new Rect(40, 40, 70, 70)));
     }
 
-    // Note: This test will fail for Skia pipeline, but that is OK.
-    // TODO: delete this test when Skia pipeline is default and modify next test
+    // STOPSHIP: delete this test when Skia pipeline ships as the default and modify next test
     // testSaveLayerUnclippedWithColorFilterSW to run for both HW and SW
+    @Ignore
     @Test
     public void testSaveLayerUnclippedWithColorFilterHW() {
         // verify that HW can draw nested unclipped layers with chained color filters
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
index e4f9809..3de3d59 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
@@ -15,6 +15,12 @@
  */
 package android.uirendering.cts.testclasses;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.support.test.filters.MediumTest;
@@ -23,6 +29,9 @@
 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.util.CompareUtils;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,28 +40,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ShadowTests extends ActivityTestBase {
 
-    private class GrayScaleVerifier extends SamplePointVerifier {
-        public GrayScaleVerifier(Point[] testPoints, int[] expectedColors, int tolerance) {
-            super(testPoints, expectedColors, tolerance) ;
-        }
-
-        @Override
-        protected boolean verifyPixel(int color, int expectedColor) {
-            return super.verifyPixel(color, expectedColor)
-                    && CompareUtils.verifyPixelGrayScale(color, 1);
-        }
-    }
-
-    @Test
-    public void testShadowLayout() {
-        int shadowColorValue = 0xDB;
-        // Android TV theme overrides shadow opacity to be darker.
-        if (getActivity().getOnTv()) {
-            shadowColorValue = 0xBB;
-        }
-
-        // Use a higher threshold than default value (20), since we also double check gray scale;
-        GrayScaleVerifier verifier = new GrayScaleVerifier(
+    private static SamplePointVerifier createVerifier(int expectedColor,
+            SamplePointVerifier.VerifyPixelColor verifier) {
+        return SamplePointVerifier.create(
                 new Point[] {
                         // view area
                         new Point(25, 64),
@@ -64,13 +54,96 @@
                 new int[] {
                         Color.WHITE,
                         Color.WHITE,
-                        Color.rgb(shadowColorValue, shadowColorValue, shadowColorValue),
-                        Color.rgb(shadowColorValue, shadowColorValue, shadowColorValue),
-                },
-                64);
+                        expectedColor,
+                        expectedColor,
+                }, SamplePointVerifier.DEFAULT_TOLERANCE, verifier);
+    }
+
+    @Test
+    public void testShadowResources() {
+        final Context context = new ContextThemeWrapper(getInstrumentation().getTargetContext(),
+                android.R.style.Theme_Material_Light);
+        final Resources resources = context.getResources();
+        final TypedValue value = new TypedValue();
+
+        resources.getValue(R.dimen.expected_spot_shadow_alpha, value, false);
+        assertEquals(TypedValue.TYPE_FLOAT, value.type);
+        float expectedSpot = value.getFloat();
+
+        resources.getValue(R.dimen.expected_ambient_shadow_alpha, value, false);
+        assertEquals(TypedValue.TYPE_FLOAT, value.type);
+        float expectedAmbient = value.getFloat();
+
+        assertTrue(expectedSpot > 0);
+        assertTrue(expectedAmbient > 0);
+
+        TypedArray typedArray = context.obtainStyledAttributes(new int[] {
+                android.R.attr.spotShadowAlpha,
+                android.R.attr.ambientShadowAlpha,
+        });
+
+        assertEquals(expectedSpot, typedArray.getFloat(0, 0.0f), 0);
+        assertEquals(expectedAmbient, typedArray.getFloat(1, 0.0f), 0);
+    }
+
+    @Test
+    public void testShadowLayout() {
+        // Use a higher threshold than default value (20), since we also double check gray scale;
+        SamplePointVerifier verifier = createVerifier(0xffe6e6e6,
+                color -> CompareUtils.verifyPixelGrayScale(color, 1));
 
         createTest()
                 .addLayout(R.layout.simple_shadow_layout, null, true/* HW only */)
                 .runWithVerifier(verifier);
     }
+
+    @Test
+    public void testRedSpotShadow() {
+        SamplePointVerifier verifier = createVerifier(0xfff8e6e6,
+                color -> {
+                    if (color == Color.WHITE) return true;
+                    return Color.red(color) > Color.green(color)
+                            && Color.red(color) > Color.blue(color);
+                });
+
+        createTest()
+                .addLayout(R.layout.simple_shadow_layout, view -> {
+                    view.findViewById(R.id.shadow_view).setOutlineSpotShadowColor(Color.RED);
+                }, true/* HW only */)
+                .runWithVerifier(verifier);
+    }
+
+    @Test
+    public void testRedAmbientShadow() {
+        SamplePointVerifier verifier = createVerifier(0xffede6e6,
+                color -> {
+                    if (color == Color.WHITE) return true;
+                    return Color.red(color) > Color.green(color)
+                            && Color.red(color) > Color.blue(color);
+                });
+
+        createTest()
+                .addLayout(R.layout.simple_shadow_layout, view -> {
+                    view.findViewById(R.id.shadow_view).setOutlineAmbientShadowColor(Color.RED);
+                }, true/* HW only */)
+                .runWithVerifier(verifier);
+    }
+
+    @Test
+    public void testRedAmbientBlueSpotShadow() {
+        SamplePointVerifier verifier = createVerifier(0xffede6f8,
+                color -> {
+                    if (color == Color.WHITE) return true;
+                    return Color.red(color) > Color.green(color)
+                            && Color.blue(color) > Color.red(color);
+                });
+
+        createTest()
+                .addLayout(R.layout.simple_shadow_layout, view -> {
+                    View shadow = view.findViewById(R.id.shadow_view);
+                    shadow.setOutlineAmbientShadowColor(Color.RED);
+                    shadow.setOutlineSpotShadowColor(Color.BLUE);
+                }, true/* HW only */)
+                .runWithVerifier(verifier);
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SurfaceViewTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SurfaceViewTests.java
index 153bf5b..2e1ce85 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SurfaceViewTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SurfaceViewTests.java
@@ -15,17 +15,13 @@
  */
 package android.uirendering.cts.testclasses;
 
-import com.android.compatibility.common.util.SynchronousPixelCopy;
-
 import android.animation.ObjectAnimator;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
@@ -40,6 +36,8 @@
 import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.compatibility.common.util.SynchronousPixelCopy;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
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 ffb263d..f4db101 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
@@ -16,17 +16,29 @@
 
 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;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 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.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+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)
@@ -44,5 +56,94 @@
                 .runWithVerifier(
                         new RectVerifier(Color.WHITE, Color.RED, new Rect(0, 0, 25, 25)));
     }
+
+    class VectorDrawableView extends View {
+        private VectorDrawable mVd;
+        private Rect mVdBounds;
+
+        public VectorDrawableView(Context context) {
+            super(context);
+            mVd = (VectorDrawable) context.getResources().getDrawable(R.drawable.circle, null);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            mVd.setBounds(mVdBounds);
+            mVd.draw(canvas);
+        }
+
+        public void setVDSize(Rect vdBounds) {
+            mVdBounds = vdBounds;
+        }
+    }
+
+    /*
+     * The following test verifies that VectorDrawable.setBounds invalidates the bitmap cache.
+     */
+    @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();
+            }
+        };
+
+        createTest()
+            .addLayout(R.layout.frame_layout, initializer, true, testFinishedFence)
+            .runWithVerifier(new SamplePointVerifier(
+                new Point[] { new Point(0, 0) },
+                new int[] { 0xff0000ff }
+            ));
+    }
 }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/XfermodeTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/XfermodeTest.java
index acae80f..2bce3cd 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/XfermodeTest.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/XfermodeTest.java
@@ -15,6 +15,7 @@
  */
 package android.uirendering.cts.testclasses;
 
+import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -29,6 +30,7 @@
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -140,12 +142,11 @@
     }
 
 
-    @Override
+    @Before
     public void setUp() {
-        super.setUp();
-
         // temporary - ensure test isn't capturing window bg only
-        getInstrumentation().runOnMainSync(() -> getActivity().getWindow().setBackgroundDrawable(
+        final Activity activity = getActivity();
+        getInstrumentation().runOnMainSync(() -> activity.getWindow().setBackgroundDrawable(
                         new ColorDrawable(Color.GREEN)));
 
     }
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 57e7d3b..f94047d 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -15,8 +15,6 @@
  */
 package android.uirendering.cts.testinfrastructure;
 
-import com.android.compatibility.common.util.SynchronousPixelCopy;
-
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -31,12 +29,12 @@
 import android.util.Log;
 import android.view.PixelCopy;
 
+import com.android.compatibility.common.util.SynchronousPixelCopy;
+
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Rule;
-import org.junit.rules.TestName;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -62,7 +60,7 @@
     private static DrawActivity sActivity;
 
     @Rule
-    public TestName name = new TestName();
+    public Tracer name = new Tracer();
 
     private BitmapAsserter mBitmapAsserter = new BitmapAsserter(this.getClass().getSimpleName(),
             name.getMethodName());
@@ -101,11 +99,6 @@
         }
     }
 
-    @Before
-    public void setUp() {
-        mBitmapAsserter.setUp(getActivity());
-    }
-
     @After
     public void tearDown() {
         if (mTestCaseBuilder != null) {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 1d4d3f4..b12390e 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -23,11 +23,11 @@
 import android.graphics.Point;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.support.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;
@@ -35,6 +35,8 @@
 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;
 
@@ -52,6 +54,7 @@
     private View mView;
     private View mViewWrapper;
     private boolean mOnTv;
+    private DrawMonitor mDrawMonitor;
 
     public void onCreate(Bundle bundle){
         super.onCreate(bundle);
@@ -63,33 +66,7 @@
         mHandler = new RenderSpecHandler();
         int uiMode = getResources().getConfiguration().uiMode;
         mOnTv = (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
-
-        // log frame metrics
-        HandlerThread handlerThread = new HandlerThread("FrameMetrics");
-        handlerThread.start();
-        getWindow().addOnFrameMetricsAvailableListener(
-                new Window.OnFrameMetricsAvailableListener() {
-                    int mRtFrameCount = 0;
-                    @Override
-                    public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
-                            int dropCountSinceLastInvocation) {
-                        Log.d("UiRendering", "Window frame count " + mRtFrameCount
-                                + ", frame drops " + dropCountSinceLastInvocation);
-                        mRtFrameCount++;
-                    }
-                }, new Handler(handlerThread.getLooper()));
-
-        // log draw metrics
-        View view = new View(this);
-        setContentView(view);
-        view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
-            int mFrameCount;
-            @Override
-            public void onDraw() {
-                Log.d("UiRendering", "View tree frame count " + mFrameCount);
-                mFrameCount++;
-            }
-        });
+        mDrawMonitor = new DrawMonitor(getWindow());
     }
 
     public boolean getOnTv() {
@@ -210,18 +187,13 @@
     }
 
     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;
-            }
-            mView.post(() -> {
-                mView.getViewTreeObserver().removeOnDrawListener(this);
+            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]);
@@ -235,4 +207,62 @@
             });
         }
     }
+
+    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/MaterialActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/MaterialActivity.java
deleted file mode 100644
index 53df12c..0000000
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/MaterialActivity.java
+++ /dev/null
@@ -1,23 +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.uirendering.cts.testinfrastructure;
-
-import android.app.Activity;
-
-public class MaterialActivity extends Activity {
-
-}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/Tracer.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/Tracer.java
new file mode 100644
index 0000000..8ea72fc
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/Tracer.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 android.uirendering.cts.testinfrastructure;
+
+import android.os.Trace;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class Tracer implements TestRule {
+    private String mName;
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                mName = description.getMethodName();
+                Trace.beginSection(mName);
+                try {
+                    base.evaluate();
+                } finally {
+                    Trace.endSection();
+                }
+            }
+        };
+    }
+
+    public String getMethodName() {
+        return mName;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
index 1523283..47f4c89 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
@@ -27,7 +27,6 @@
 
 public class BitmapAsserter {
     private DifferenceVisualizer mDifferenceVisualizer;
-    private Context mContext;
     private String mClassName;
 
     public BitmapAsserter(String className, String name) {
@@ -43,11 +42,6 @@
         }
     }
 
-    public void setUp(Context context) {
-        mDifferenceVisualizer = new PassFailVisualizer();
-        mContext = context;
-    }
-
     /**
      * Compares the two bitmaps saved using the given test. If they fail, the files are saved using
      * the test name.
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/MockVsyncHelper.java b/tests/tests/uirendering/src/android/uirendering/cts/util/MockVsyncHelper.java
new file mode 100644
index 0000000..4109cc2
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/MockVsyncHelper.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.uirendering.cts.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.view.animation.AnimationUtils;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class MockVsyncHelper {
+
+    private static ExecutorService sExecutor = Executors.newSingleThreadExecutor();
+    private static Future<Thread> sExecutorThread;
+
+    static {
+        // We can't wait on the future here because the lambda cannot be executed until
+        // the class has finished loading
+        sExecutorThread = sExecutor.submit(() -> {
+            AnimationUtils.lockAnimationClock(16);
+            return Thread.currentThread();
+        });
+    }
+
+    private static boolean isOnExecutorThread() {
+        try {
+            return Thread.currentThread().equals(sExecutorThread.get());
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void nextFrame() {
+        assertTrue("nextFrame() must be called inside #unOnVsyncThread block",
+                isOnExecutorThread());
+        AnimationUtils.lockAnimationClock(AnimationUtils.currentAnimationTimeMillis() + 16);
+    }
+
+    public static void runOnVsyncThread(CallableVoid callable) {
+        assertFalse("Cannot runOnVsyncThread inside #runOnVsyncThread block",
+                isOnExecutorThread());
+        try {
+            sExecutor.submit(() -> {
+                callable.call();
+                return (Void) null;
+            }).get();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        } catch (ExecutionException e) {
+            SneakyThrow.sneakyThrow(e.getCause() != null ? e.getCause() : e);
+        }
+    }
+
+    public interface CallableVoid {
+        void call() throws Exception;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/SneakyThrow.java b/tests/tests/uirendering/src/android/uirendering/cts/util/SneakyThrow.java
new file mode 100644
index 0000000..d4f09bd
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/SneakyThrow.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.util;
+
+/**
+ * Provides a hacky method that always throws {@code t} even if {@code t} is a checked exception.
+ * and is not declared to be thrown.
+ *
+ * See
+ * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
+ */
+public class SneakyThrow {
+    /**
+     * A hacky method that always throws {@code t} even if {@code t} is a checked exception,
+     * and is not declared to be thrown.
+     */
+    public static void sneakyThrow(Throwable t) {
+        SneakyThrow.<RuntimeException>sneakyThrow_(t);
+    }
+
+    private static <T extends Throwable> void sneakyThrow_(Throwable t) throws T {
+        throw (T) t;
+    }
+}
diff --git a/tests/tests/util/Android.mk b/tests/tests/util/Android.mk
index 1bcc518..4dffce7 100644
--- a/tests/tests/util/Android.mk
+++ b/tests/tests/util/Android.mk
@@ -24,11 +24,12 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-annotations \
     android-support-test \
-    ctstestrunner \
-    legacy-android-test
+    ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/util/AndroidTest.xml b/tests/tests/util/AndroidTest.xml
index b022356..8be8204 100644
--- a/tests/tests/util/AndroidTest.xml
+++ b/tests/tests/util/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Util 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" />
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index e02ce28..e639b5c 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
@@ -35,8 +35,7 @@
     ctstestrunner \
     mockito-target-minus-junit4 \
     platform-test-annotations \
-    ub-uiautomator \
-    legacy-android-test
+    ub-uiautomator
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 823704c..e79a2e3 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -191,6 +191,13 @@
                   android:configChanges="orientation|screenSize"
                   android:colorMode="wideColorGamut" />
 
+        <activity android:name="android.view.cts.PixelCopyViewProducerDialogActivity"
+                  android:label="PixelCopyViewProducerDialogActivity"
+                  android:screenOrientation="portrait"
+                  android:rotationAnimation="jumpcut"
+                  android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
+                  android:configChanges="orientation|screenSize" />
+
         <activity android:name="android.view.cts.FocusFinderCtsActivity"
                   android:screenOrientation="locked"
                   android:label="FocusFinderCtsActivity">
@@ -288,15 +295,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.PanicPressBackActivity"
-                  android:screenOrientation="locked"
-                  android:label="PanicPressBackActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
         <activity android:name="android.view.cts.DragDropActivity"
                   android:screenOrientation="portrait"
                   android:label="DragDropActivity">
@@ -347,6 +345,18 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity android:name="android.view.cts.KeyEventInterceptTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.view.cts.TouchDelegateTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/AndroidTest.xml b/tests/tests/view/AndroidTest.xml
index 9ed52bc..0ae9f42 100644
--- a/tests/tests/view/AndroidTest.xml
+++ b/tests/tests/view/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS View test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/view/res/layout/focus_handling_initial_focus.xml b/tests/tests/view/res/layout/focus_handling_initial_focus.xml
index ad12d07..ddc67ab 100644
--- a/tests/tests/view/res/layout/focus_handling_initial_focus.xml
+++ b/tests/tests/view/res/layout/focus_handling_initial_focus.xml
@@ -19,17 +19,17 @@
     <requestFocus />
     <View
         android:id="@+id/focusable1"
-        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:layout_width="10dp"
         android:layout_height="10dp" />
     <View
         android:id="@+id/focusable2"
-        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:layout_width="10dp"
         android:layout_height="10dp" />
     <View
         android:id="@+id/focusable3"
-        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:focusedByDefault="true"
         android:layout_width="10dp"
         android:layout_height="10dp" />
diff --git a/tests/tests/view/res/layout/focus_handling_layout.xml b/tests/tests/view/res/layout/focus_handling_layout.xml
index 6966e53..d2f7778 100644
--- a/tests/tests/view/res/layout/focus_handling_layout.xml
+++ b/tests/tests/view/res/layout/focus_handling_layout.xml
@@ -22,14 +22,14 @@
 
     <View
         android:id="@+id/view1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="50dp"
+        android:layout_height="30dp"
         android:text="@string/id_ok"/>
 
     <View
         android:id="@+id/view2"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="50dp"
+        android:layout_height="30dp"
         android:layout_toRightOf="@id/view1"
         android:layout_alignTop="@id/view1"
         android:nextFocusLeft="@id/view1"
@@ -37,8 +37,8 @@
 
     <View
         android:id="@+id/view3"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="50dp"
+        android:layout_height="30dp"
         android:layout_below="@id/view1"
         android:layout_alignLeft="@id/view1"
         android:nextFocusUp="@id/view1"
@@ -46,8 +46,8 @@
 
     <View
         android:id="@+id/view4"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="50dp"
+        android:layout_height="30dp"
         android:layout_toRightOf="@id/view3"
         android:layout_alignTop="@id/view3"
         android:layout_alignRight="@id/view2"
@@ -57,8 +57,8 @@
 
     <LinearLayout
         android:id="@+id/auto_test_area"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
         android:orientation="vertical"
         android:layout_below="@id/view4"
         android:layout_alignParentLeft="true">
diff --git a/tests/tests/view/res/layout/key_fallback_layout.xml b/tests/tests/view/res/layout/key_fallback_layout.xml
new file mode 100644
index 0000000..8d7b1a3
--- /dev/null
+++ b/tests/tests/view/res/layout/key_fallback_layout.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/higher_in_normal"
+            android:translationZ="3dp"
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/last_in_normal"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/higher_group"
+        android:translationZ="1dp"
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TextView
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/lower_in_higher"
+            android:focusable="true"
+            android:translationZ="-2dp"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/last_in_higher"
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TextView
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/last_button"
+            android:focusable="true"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml b/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml
new file mode 100644
index 0000000..0780046
--- /dev/null
+++ b/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/layout"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+    />
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/view_outlineshadowcolor.xml b/tests/tests/view/res/layout/view_outlineshadowcolor.xml
new file mode 100644
index 0000000..d366b00
--- /dev/null
+++ b/tests/tests/view/res/layout/view_outlineshadowcolor.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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/main_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <View android:id="@+id/default_shadow"
+            android:layout_width="match_parent"
+            android:layout_height="50dp" />
+
+    <View android:id="@+id/red_shadow"
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:outlineAmbientShadowColor="@color/red" />
+
+    <View android:id="@+id/blue_shadow"
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:outlineSpotShadowColor="#0000FF" />
+
+    <View android:id="@+id/green_shadow"
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:outlineSpotShadowColor="#FF00FF00"
+            android:outlineAmbientShadowColor="@color/green" />
+
+</LinearLayout>
diff --git a/tests/tests/view/res/values/colors.xml b/tests/tests/view/res/values/colors.xml
index f3cc325..790ffe6 100644
--- a/tests/tests/view/res/values/colors.xml
+++ b/tests/tests/view/res/values/colors.xml
@@ -23,4 +23,6 @@
     <color name="testcolor1">#ff00ff00</color>
     <color name="testcolor2">#ffff0000</color>
     <color name="failColor">#ff0000ff</color>
+    <color name="red">#ffff0000</color>
+    <color name="green">#ff00ff00</color>
 </resources>
diff --git a/tests/tests/view/src/android/view/cts/DragDropTest.java b/tests/tests/view/src/android/view/cts/DragDropTest.java
index 39ae2d3..2ae12f0 100644
--- a/tests/tests/view/src/android/view/cts/DragDropTest.java
+++ b/tests/tests/view/src/android/view/cts/DragDropTest.java
@@ -25,6 +25,8 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
@@ -35,8 +37,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.internal.util.ArrayUtils;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -44,9 +44,10 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Objects;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
 
 @RunWith(AndroidJUnit4.class)
 public class DragDropTest {
@@ -64,60 +65,83 @@
     private CountDownLatch mStartReceived;
     private CountDownLatch mEndReceived;
 
-    private static boolean equal(ClipDescription d1, ClipDescription d2) {
-        if ((d1 == null) != (d2 == null)) {
-            return false;
-        }
-        if (d1 == null) {
+    private AssertionError mMainThreadAssertionError;
+
+    /**
+     * Check whether two objects have the same binary data when dumped into Parcels
+     * @return True if the objects are equal
+     */
+    private static boolean compareParcelables(Parcelable obj1, Parcelable obj2) {
+        if (obj1 == null && obj2 == null) {
             return true;
         }
-        return d1.getLabel().equals(d2.getLabel()) &&
-                d1.getMimeTypeCount() == 1 && d2.getMimeTypeCount() == 1 &&
-                d1.getMimeType(0).equals(d2.getMimeType(0));
-    }
-
-    private static boolean equal(ClipData.Item i1, ClipData.Item i2) {
-        return Objects.equals(i1.getIntent(), i2.getIntent()) &&
-                Objects.equals(i1.getHtmlText(), i2.getHtmlText()) &&
-                Objects.equals(i1.getText(), i2.getText()) &&
-                Objects.equals(i1.getUri(), i2.getUri());
-    }
-
-    private static boolean equal(ClipData d1, ClipData d2) {
-        if ((d1 == null) != (d2 == null)) {
+        if (obj1 == null || obj2 == null) {
             return false;
         }
-        if (d1 == null) {
-            return true;
-        }
-        return equal(d1.getDescription(), d2.getDescription()) &&
-                Objects.equals(d1.getIcon(), d2.getIcon()) &&
-                d1.getItemCount() == 1 && d2.getItemCount() == 1 &&
-                equal(d1.getItemAt(0), d2.getItemAt(0));
+        Parcel p1 = Parcel.obtain();
+        obj1.writeToParcel(p1, 0);
+        Parcel p2 = Parcel.obtain();
+        obj2.writeToParcel(p2, 0);
+        boolean result = Arrays.equals(p1.marshall(), p2.marshall());
+        p1.recycle();
+        p2.recycle();
+        return result;
     }
 
-    private static boolean equal(DragEvent ev1, DragEvent ev2) {
-        return ev1.getAction() == ev2.getAction() &&
-                ev1.getX() == ev2.getX() &&
-                ev1.getY() == ev2.getY() &&
-                equal(ev1.getClipData(), ev2.getClipData()) &&
-                equal(ev1.getClipDescription(), ev2.getClipDescription()) &&
-                Objects.equals(ev1.getDragAndDropPermissions(), ev2.getDragAndDropPermissions()) &&
-                Objects.equals(ev1.getLocalState(), ev2.getLocalState()) &&
-                ev1.getResult() == ev2.getResult();
-    }
+    private static final ClipDescription sClipDescription =
+            new ClipDescription("TestLabel", new String[]{"text/plain"});
+    private static final ClipData sClipData =
+            new ClipData(sClipDescription, new ClipData.Item("TestText"));
+    private static final Object sLocalState = new Object(); // just check if null or not
 
     class LogEntry {
-        public View v;
-        public DragEvent ev;
+        public View view;
 
-        public LogEntry(View v, DragEvent ev) {
-            this.v = v;
-            this.ev = DragEvent.obtain(ev);
+        // Public DragEvent fields
+        public int action; // DragEvent.getAction()
+        public float x; // DragEvent.getX()
+        public float y; // DragEvent.getY()
+        public ClipData clipData; // DragEvent.getClipData()
+        public ClipDescription clipDescription; // DragEvent.getClipDescription()
+        public Object localState; // DragEvent.getLocalState()
+        public boolean result; // DragEvent.getResult()
+
+        LogEntry(View v, int action, float x, float y, ClipData clipData,
+                ClipDescription clipDescription, Object localState, boolean result) {
+            this.view = v;
+            this.action = action;
+            this.x = x;
+            this.y = y;
+            this.clipData = clipData;
+            this.clipDescription = clipDescription;
+            this.localState = localState;
+            this.result = result;
         }
 
-        public boolean eq(LogEntry other) {
-            return v == other.v && equal(ev, other.ev);
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof LogEntry)) {
+                return false;
+            }
+            final LogEntry other = (LogEntry) obj;
+            return view == other.view && action == other.action
+                    && x == other.x && y == other.y
+                    && compareParcelables(clipData, other.clipData)
+                    && compareParcelables(clipDescription, other.clipDescription)
+                    && localState == other.localState
+                    && result == other.result;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("DragEvent {action=").append(action).append(" x=").append(x).append(" y=")
+                    .append(y).append(" result=").append(result).append("}")
+                    .append(" @ ").append(view);
+            return sb.toString();
         }
     }
 
@@ -126,19 +150,18 @@
     final private ArrayList<LogEntry> mActual = new ArrayList<LogEntry> ();
     final private ArrayList<LogEntry> mExpected = new ArrayList<LogEntry> ();
 
-    private static ClipDescription createClipDescription() {
-        return new ClipDescription("TestLabel", new String[]{"text/plain"});
+    private static ClipData obtainClipData(int action) {
+        if (action == DragEvent.ACTION_DROP) {
+            return sClipData;
+        }
+        return null;
     }
 
-    private static ClipData createClipData() {
-        return new ClipData(createClipDescription(), new ClipData.Item("TestText"));
-    }
-
-    static private DragEvent obtainDragEvent(int action, int x, int y, boolean result) {
-        final ClipDescription description =
-                action != DragEvent.ACTION_DRAG_ENDED ? createClipDescription() : null;
-        final ClipData data = action == DragEvent.ACTION_DROP ? createClipData() : null;
-        return DragEvent.obtain(action, x, y, null, description, data, null, result);
+    private static ClipDescription obtainClipDescription(int action) {
+        if (action == DragEvent.ACTION_DRAG_ENDED) {
+            return null;
+        }
+        return sClipDescription;
     }
 
     private void logEvent(View v, DragEvent ev) {
@@ -148,19 +171,23 @@
         if (ev.getAction() == DragEvent.ACTION_DRAG_ENDED) {
             mEndReceived.countDown();
         }
-        mActual.add(new LogEntry(v, ev));
+        mActual.add(new LogEntry(v, ev.getAction(), ev.getX(), ev.getY(), ev.getClipData(),
+                ev.getClipDescription(), ev.getLocalState(), ev.getResult()));
     }
 
     // Add expected event for a view, with zero coordinates.
     private void expectEvent5(int action, int viewId) {
         View v = mActivity.findViewById(viewId);
-        mExpected.add(new LogEntry(v, obtainDragEvent(action, 0, 0, false)));
+        mExpected.add(new LogEntry(v, action, 0, 0, obtainClipData(action),
+                obtainClipDescription(action), sLocalState, false));
     }
 
     // Add expected event for a view.
-    private void expectEndEvent(int viewId, int x, int y, boolean result) {
+    private void expectEndEvent(int viewId, float x, float y, boolean result) {
         View v = mActivity.findViewById(viewId);
-        mExpected.add(new LogEntry(v, obtainDragEvent(DragEvent.ACTION_DRAG_ENDED, x, y, result)));
+        int action = DragEvent.ACTION_DRAG_ENDED;
+        mExpected.add(new LogEntry(v, action, x, y, obtainClipData(action),
+                obtainClipDescription(action), sLocalState, result));
     }
 
     // Add expected successful-end event for a view.
@@ -173,9 +200,12 @@
     private void expectEndEventFailure6(int viewId, int releaseViewId) {
         View v = mActivity.findViewById(viewId);
         View release = mActivity.findViewById(releaseViewId);
-        int [] releaseLoc = release.getLocationOnScreen();
-        mExpected.add(new LogEntry(v, obtainDragEvent(DragEvent.ACTION_DRAG_ENDED,
-                releaseLoc[0] + 6, releaseLoc[1] + 6, false)));
+        int [] releaseLoc = new int[2];
+        release.getLocationOnScreen(releaseLoc);
+        int action = DragEvent.ACTION_DRAG_ENDED;
+        mExpected.add(new LogEntry(v, action,
+                releaseLoc[0] + 6, releaseLoc[1] + 6, obtainClipData(action),
+                obtainClipDescription(action), sLocalState, false));
     }
 
     // Add expected event for a view, with coordinates over view locationViewId, with the specified
@@ -183,11 +213,14 @@
     private void expectEventWithOffset(int action, int viewId, int locationViewId, int offset) {
         View v = mActivity.findViewById(viewId);
         View locationView = mActivity.findViewById(locationViewId);
-        int [] viewLocation = v.getLocationOnScreen();
-        int [] locationViewLocation = locationView.getLocationOnScreen();
-        mExpected.add(new LogEntry(v, obtainDragEvent(action,
+        int [] viewLocation = new int[2];
+        v.getLocationOnScreen(viewLocation);
+        int [] locationViewLocation = new int[2];
+        locationView.getLocationOnScreen(locationViewLocation);
+        mExpected.add(new LogEntry(v, action,
                 locationViewLocation[0] - viewLocation[0] + offset,
-                locationViewLocation[1] - viewLocation[1] + offset, false)));
+                locationViewLocation[1] - viewLocation[1] + offset, obtainClipData(action),
+                obtainClipDescription(action), sLocalState, false));
     }
 
     private void expectEvent5(int action, int viewId, int locationViewId) {
@@ -203,7 +236,8 @@
     private void injectMouseWithOffset(int viewId, int action, int offset) {
         runOnMain(() -> {
             View v = mActivity.findViewById(viewId);
-            int [] destLoc = v.getLocationOnScreen();
+            int [] destLoc = new int [2];
+            v.getLocationOnScreen(destLoc);
             long downTime = SystemClock.uptimeMillis();
             MotionEvent event = MotionEvent.obtain(downTime, downTime, action,
                     destLoc[0] + offset, destLoc[1] + offset, 1);
@@ -237,8 +271,7 @@
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < log.size(); ++i) {
             LogEntry e = log.get(i);
-            sb.append("#").append(i + 1).append(": ").append(e.ev).append(" @ ").
-                    append(e.v.toString()).append('\n');
+            sb.append("#").append(i + 1).append(": ").append(e).append('\n');
         }
         return sb.toString();
     }
@@ -263,7 +296,7 @@
             }
 
             for (int i = 0; i < mActual.size(); ++i) {
-                if (!mActual.get(i).eq(mExpected.get(i))) {
+                if (!mActual.get(i).equals(mExpected.get(i))) {
                     failWithLogs("Actual event #" + (i + 1) + " is different from expected");
                 }
             }
@@ -309,8 +342,18 @@
         }
     }
 
-    private void runOnMain(Runnable runner) {
-        mInstrumentation.runOnMainSync(runner);
+    private void runOnMain(Runnable runner) throws AssertionError {
+        mMainThreadAssertionError = null;
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                runner.run();
+            } catch (AssertionError error) {
+                mMainThreadAssertionError = error;
+            }
+        });
+        if (mMainThreadAssertionError != null) {
+            throw mMainThreadAssertionError;
+        }
     }
 
     private void startDrag() {
@@ -321,7 +364,7 @@
             // Start drag.
             View v = mActivity.findViewById(R.id.draggable);
             assertTrue("Couldn't start drag",
-                    v.startDragAndDrop(createClipData(), new View.DragShadowBuilder(v), null, 0));
+                    v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
         });
 
         try {
@@ -585,6 +628,11 @@
         verifyEventLog();
     }
 
+    private boolean drawableStateContains(int resourceId, int attr) {
+        return IntStream.of(mActivity.findViewById(resourceId).getDrawableState())
+                .anyMatch(x -> x == attr);
+    }
+
     /**
      * Tests that state_drag_hovered and state_drag_can_accept are set correctly.
      */
@@ -600,52 +648,38 @@
                 logEvent(v, ev);
                 return true;
             });
-            assertFalse(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_can_accept));
+            assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
         });
 
         startDrag();
 
         runOnMain(() -> {
-            assertFalse(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_hovered));
-            assertTrue(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_can_accept));
+            assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
+            assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
         });
 
         // Move mouse into the view.
         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
         runOnMain(() -> {
-            assertTrue(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_hovered));
+            assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
         });
 
         // Move out.
         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
         runOnMain(() -> {
-            assertFalse(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_hovered));
+            assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
         });
 
         // Move in.
         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
         runOnMain(() -> {
-            assertTrue(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_hovered));
+            assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
         });
 
         // Release there.
         injectMouse5(R.id.inner, MotionEvent.ACTION_UP);
         runOnMain(() -> {
-            assertFalse(ArrayUtils.contains(
-                    mActivity.findViewById(R.id.inner).getDrawableState(),
-                    android.R.attr.state_drag_hovered));
+            assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
         });
     }
 }
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/FocusFinderTest.java b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
index 0f2119e..2505111 100644
--- a/tests/tests/view/src/android/view/cts/FocusFinderTest.java
+++ b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
@@ -426,7 +426,6 @@
 
         // While we don't want to test details, we should at least verify basic correctness
         // like "left-to-right" ordering in well-behaved layouts
-        assertEquals(mLayout.findFocus(), mTopLeft);
         verifyNextFocus(mTopLeft, View.FOCUS_FORWARD, mTopRight);
         verifyNextFocus(mTopRight, View.FOCUS_FORWARD, mBottomLeft);
         verifyNextFocus(mBottomLeft, View.FOCUS_FORWARD, mBottomRight);
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
new file mode 100644
index 0000000..598552b
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+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.
+ * The test will fail if any of these keys are received by the activity.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class KeyEventInterceptTest {
+    private KeyEventInterceptTestActivity mActivity;
+    private Instrumentation mInstrumentation;
+
+    @Rule
+    public ActivityTestRule<KeyEventInterceptTestActivity> mActivityRule =
+            new ActivityTestRule<>(KeyEventInterceptTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(mActivity::hasWindowFocus);
+    }
+
+    @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);
+    }
+
+    private void testKey(int keyCode) {
+        sendKey(keyCode);
+        assertKeyNotReceived();
+    }
+
+    private void sendKey(int keyCodeToSend) {
+        long downTime = SystemClock.uptimeMillis();
+        injectEvent(new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                keyCodeToSend, 0, 0));
+        injectEvent(new KeyEvent(downTime, downTime + 1, KeyEvent.ACTION_UP,
+                keyCodeToSend, 0, 0));
+    }
+
+    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");
+        }
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
new file mode 100644
index 0000000..18e0713
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.view.KeyEvent;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+
+public class KeyEventInterceptTestActivity extends Activity {
+    final BlockingQueue<KeyEvent> mKeyEvents = new LinkedBlockingDeque<>();
+
+    @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
+        mKeyEvents.add(event);
+        return true;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
index 40d95f7..bea5aba 100644
--- a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
+++ b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
@@ -25,7 +25,7 @@
 import android.app.Activity;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.graphics.ImageDecoder;
 import android.graphics.drawable.BitmapDrawable;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -40,6 +40,8 @@
 
 import com.android.compatibility.common.util.WidgetTestUtils;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -324,7 +326,14 @@
     }
 
     private void verifyDrawableContent(BitmapDrawable b, int resId) {
-        Bitmap expected = BitmapFactory.decodeResource(mActivity.getResources(), resId);
-        WidgetTestUtils.assertEquals(expected, b.getBitmap());
+        try {
+            ImageDecoder.Source src = ImageDecoder.createSource(mActivity.getResources(), resId);
+            Bitmap expected = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+            WidgetTestUtils.assertEquals(expected, b.getBitmap());
+        } catch (java.io.IOException e) {
+            Assert.fail("the resource could not be decoded");
+        }
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/PanicPressBackActivity.java b/tests/tests/view/src/android/view/cts/PanicPressBackActivity.java
deleted file mode 100644
index bb52491..0000000
--- a/tests/tests/view/src/android/view/cts/PanicPressBackActivity.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.view.cts;
-
-import android.app.Activity;
-
-import java.util.concurrent.CountDownLatch;
-
-public class PanicPressBackActivity extends Activity {
-
-    private boolean mWasPaused;
-
-    public final CountDownLatch mWaitForPanicBackLatch = new CountDownLatch(1);
-
-    @Override
-    public void onBackPressed() {
-        // Prevent back press from exiting app
-    }
-
-    @Override
-    public void onPause() {
-        mWaitForPanicBackLatch.countDown();
-        super.onPause();
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/PanicPressBackTest.java b/tests/tests/view/src/android/view/cts/PanicPressBackTest.java
deleted file mode 100644
index 8a0bf31..0000000
--- a/tests/tests/view/src/android/view/cts/PanicPressBackTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.cts;
-
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
-
-import android.app.UiAutomation;
-import android.content.pm.PackageManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class PanicPressBackTest {
-    static final String TAG = "PanicPressBackTest";
-
-    @Rule
-    public ActivityTestRule<PanicPressBackActivity> mActivityRule =
-            new ActivityTestRule<>(PanicPressBackActivity.class);
-
-    private static final int PANIC_PRESS_COUNT = 4;
-    private PanicPressBackActivity mActivity;
-
-    @Before
-    public void setUp() {
-        mActivity = mActivityRule.getActivity();
-    }
-
-    /**
-     * Tests to ensure that the foregrounded app does not handle back button panic press on
-     * non-watch devices
-     */
-    @Test
-    public void testNonWatchBackPanicDoesNothing() throws Exception {
-        // Only run for non-watch devices
-        if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            return;
-        }
-
-        final UiAutomation automation = InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation();
-
-        // Press back button PANIC_PRESS_COUNT times
-        long startTime = System.currentTimeMillis();
-        for (int i = 0; i < PANIC_PRESS_COUNT; ++i) {
-            long currentTime = startTime + i;
-            automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN,
-                    KeyEvent.KEYCODE_BACK, 0), true);
-            automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_UP,
-                    KeyEvent.KEYCODE_BACK, 0), true);
-        }
-
-        // Wait multi press time out plus some time to give the system time to respond
-        long timeoutMs = ViewConfiguration.getMultiPressTimeout() + TimeUnit.SECONDS.toMillis(1);
-
-        // Assert activity was not stopped, indicating panic press was not able to exit the app
-        assertFalse(mActivity.mWaitForPanicBackLatch.await(timeoutMs, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * Tests to ensure that the foregrounded app does handle back button panic press on watch
-     * devices
-     */
-    @Test
-    public void testWatchBackPanicReceivesHomeRequest() throws Exception {
-        // Only run for watch devices
-        if (!mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            return;
-        }
-
-        final UiAutomation automation = InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation();
-
-        // Press back button PANIC_PRESS_COUNT times
-        long startTime = System.currentTimeMillis();
-        for (int i = 0; i < PANIC_PRESS_COUNT; ++i) {
-            // Assert activity hasn't stopped yet
-            assertFalse(mActivity.mWaitForPanicBackLatch.await(0, TimeUnit.MILLISECONDS));
-            long currentTime = startTime + i;
-            automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN,
-                    KeyEvent.KEYCODE_BACK, 0), true);
-            automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_UP,
-                    KeyEvent.KEYCODE_BACK, 0), true);
-        }
-
-        // Wait multi press time out plus some time to give the system time to respond
-        long timeoutMs = ViewConfiguration.getMultiPressTimeout() + TimeUnit.SECONDS.toMillis(1);
-
-        // Assert activity was stopped, indicating that panic press was able to exit the app
-        assertTrue(mActivity.mWaitForPanicBackLatch.await(timeoutMs, TimeUnit.MILLISECONDS));
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTest.java b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
index 0cbd0ca..2088b7c 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTest.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
@@ -83,6 +83,10 @@
                     PixelCopyWideGamutViewProducerActivity.class, false, false);
 
     @Rule
+    public ActivityTestRule<PixelCopyViewProducerDialogActivity> mDialogSourceActivityRule =
+            new ActivityTestRule<>(PixelCopyViewProducerDialogActivity.class, false, false);
+
+    @Rule
     public SurfaceTextureRule mSurfaceRule = new SurfaceTextureRule();
 
     private Instrumentation mInstrumentation;
@@ -458,6 +462,116 @@
         } while (activity.rotate());
     }
 
+    private Window waitForDialogProducerActivity() {
+        PixelCopyViewProducerActivity activity =
+                mDialogSourceActivityRule.launchActivity(null);
+        activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        return activity.getWindow();
+    }
+
+    private Rect makeDialogRect(int left, int top, int right, int bottom) {
+        Rect r = new Rect(left, top, right, bottom);
+        mDialogSourceActivityRule.getActivity().normalizedToSurface(r);
+        return r;
+    }
+
+    @Test
+    public void testDialogProducer() {
+        Bitmap bitmap;
+        Window window = waitForDialogProducerActivity();
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+        do {
+            Rect src = makeDialogRect(0, 0, 100, 100);
+            bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
+            int result = mCopyHelper.request(window, src, bitmap);
+            assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+            assertEquals(Config.ARGB_8888, bitmap.getConfig());
+            assertBitmapQuadColor(bitmap,
+                    Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+            assertBitmapEdgeColor(bitmap, Color.YELLOW);
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testDialogProducerCropTopLeft() {
+        Window window = waitForDialogProducerActivity();
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+        do {
+            int result = mCopyHelper.request(window, makeDialogRect(0, 0, 50, 50), bitmap);
+            assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
+            assertBitmapQuadColor(bitmap,
+                    Color.RED, Color.RED, Color.RED, Color.RED);
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testDialogProducerCropCenter() {
+        Window window = waitForDialogProducerActivity();
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+        do {
+            int result = mCopyHelper.request(window, makeDialogRect(25, 25, 75, 75), bitmap);
+            assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
+            assertBitmapQuadColor(bitmap,
+                    Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testDialogProducerCropBottomHalf() {
+        Window window = waitForDialogProducerActivity();
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+        do {
+            int result = mCopyHelper.request(window, makeDialogRect(0, 50, 100, 100), bitmap);
+            assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
+            assertBitmapQuadColor(bitmap,
+                    Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testDialogProducerScaling() {
+        // Since we only sample mid-pixel of each qudrant, filtering
+        // quality isn't tested
+        Window window = waitForDialogProducerActivity();
+        Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+        do {
+            int result = mCopyHelper.request(window, bitmap);
+            assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
+            // Make sure nothing messed with the bitmap
+            assertEquals(20, bitmap.getWidth());
+            assertEquals(20, bitmap.getHeight());
+            assertEquals(Config.ARGB_8888, bitmap.getConfig());
+            assertBitmapQuadColor(bitmap,
+                    Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testDialogProducerCopyToRGBA16F() {
+        Window window = waitForDialogProducerActivity();
+        PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
+
+        Bitmap bitmap;
+        do {
+            Rect src = makeDialogRect(0, 0, 100, 100);
+            bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
+            int result = mCopyHelper.request(window, src, bitmap);
+            // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
+            // not support for float textures
+            if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
+                assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+                assertEquals(Config.RGBA_F16, bitmap.getConfig());
+                assertBitmapQuadColor(bitmap,
+                        Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+                assertBitmapEdgeColor(bitmap, Color.YELLOW);
+            }
+        } while (activity.rotate());
+    }
+
     private static void assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y,
             ByteBuffer dst, float r, float g, float b, float a) {
         int index = y * bitmap.getRowBytes() + (x << 3);
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyViewProducerDialogActivity.java b/tests/tests/view/src/android/view/cts/PixelCopyViewProducerDialogActivity.java
new file mode 100644
index 0000000..a213758
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/PixelCopyViewProducerDialogActivity.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.view.cts;
+
+import android.os.Bundle;
+
+public class PixelCopyViewProducerDialogActivity extends PixelCopyViewProducerActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public boolean rotate() {
+        return false;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/PointerCaptureTest.java b/tests/tests/view/src/android/view/cts/PointerCaptureTest.java
index 139232f..e6068ce 100644
--- a/tests/tests/view/src/android/view/cts/PointerCaptureTest.java
+++ b/tests/tests/view/src/android/view/cts/PointerCaptureTest.java
@@ -234,7 +234,8 @@
         // condition.
         CtsTouchUtils.emulateLongPressOnView(mInstrumentation, mTarget, 0, 0);
         PollingCheck.waitFor(TIMEOUT_DELTA, () -> !mOuter.hasWindowFocus());
-        assertPointerCapture(false);
+        PollingCheck.waitFor(TIMEOUT_DELTA,
+                () -> !mTarget.hasPointerCapture() && !mActivity.hasPointerCapture());
 
         mInstrumentation.sendCharacterSync(KeyEvent.KEYCODE_BACK);
         PollingCheck.waitFor(TIMEOUT_DELTA, () -> mOuter.hasWindowFocus());
diff --git a/tests/tests/view/src/android/view/cts/TooltipTest.java b/tests/tests/view/src/android/view/cts/TooltipTest.java
index 1717763..90ab139 100644
--- a/tests/tests/view/src/android/view/cts/TooltipTest.java
+++ b/tests/tests/view/src/android/view/cts/TooltipTest.java
@@ -182,9 +182,13 @@
         mInstrumentation.sendPointerSync(event);
     }
 
+    private void injectHoverMove(int source, View target, int offsetX, int offsetY) {
+        injectMotionEvent(obtainMotionEvent(
+                    source, target, MotionEvent.ACTION_HOVER_MOVE, offsetX,  offsetY));
+    }
+
     private void injectHoverMove(View target, int offsetX, int offsetY) {
-        injectMotionEvent(obtainMouseEvent(
-                target, MotionEvent.ACTION_HOVER_MOVE, offsetX,  offsetY));
+        injectHoverMove(InputDevice.SOURCE_MOUSE, target, offsetX,  offsetY);
     }
 
     private void injectHoverMove(View target) {
@@ -197,13 +201,18 @@
     }
 
     private static MotionEvent obtainMouseEvent(View target, int action, int offsetX, int offsetY) {
+        return obtainMotionEvent(InputDevice.SOURCE_MOUSE, target, action, offsetX, offsetY);
+    }
+
+    private static MotionEvent obtainMotionEvent(
+                int source, View target, int action, int offsetX, int offsetY) {
         final long eventTime = SystemClock.uptimeMillis();
         final int[] xy = new int[2];
         target.getLocationOnScreen(xy);
         MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action,
                 xy[0] + target.getWidth() / 2 + offsetX, xy[1] + target.getHeight() / 2 + offsetY,
                 0);
-        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setSource(source);
         return event;
     }
 
@@ -250,17 +259,16 @@
     }
 
     @Test
-    public void testNoTooltipOnDisabledView() throws Throwable {
+    public void testTooltipOnDisabledView() throws Throwable {
         mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
 
+        // Long click has no effect on a disabled view.
         injectLongClick(mTooltipView);
         assertFalse(hasTooltip(mTooltipView));
 
-        injectLongEnter(mTooltipView);
-        assertFalse(hasTooltip(mTooltipView));
-
+        // Hover does show the tooltip on a disabled view.
         injectLongHoverMove(mTooltipView);
-        assertFalse(hasTooltip(mTooltipView));
+        assertTrue(hasTooltip(mTooltipView));
     }
 
     @Test
@@ -656,7 +664,8 @@
         injectHoverMove(mTooltipView);
         mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
-        assertFalse(hasTooltip(mTooltipView));
+        // Disabled view still displays a hover tooltip.
+        assertTrue(hasTooltip(mTooltipView));
     }
 
     @Test
@@ -787,6 +796,76 @@
     }
 
     @Test
+    public void testMouseHoverWithJitter() throws Throwable {
+        testHoverWithJitter(InputDevice.SOURCE_MOUSE);
+    }
+
+    @Test
+    public void testStylusHoverWithJitter() throws Throwable {
+        testHoverWithJitter(InputDevice.SOURCE_STYLUS);
+    }
+
+    @Test
+    public void testTouchscreenHoverWithJitter() throws Throwable {
+        testHoverWithJitter(InputDevice.SOURCE_TOUCHSCREEN);
+    }
+
+    private void testHoverWithJitter(int source) {
+        final int hoverSlop = ViewConfiguration.get(mTooltipView.getContext()).getScaledHoverSlop();
+        if (hoverSlop == 0) {
+            // Zero hoverSlop makes this test redundant.
+            return;
+        }
+
+        final int tooltipTimeout = ViewConfiguration.getHoverTooltipShowTimeout();
+        final long halfTimeout = tooltipTimeout / 2;
+        assertTrue(halfTimeout + WAIT_MARGIN < tooltipTimeout);
+
+        // Imitate strong jitter (above hoverSlop threshold). No tooltip should be shown.
+        int jitterHigh = hoverSlop + 1;
+        assertTrue(jitterHigh <= mTooltipView.getWidth());
+        assertTrue(jitterHigh <= mTooltipView.getHeight());
+
+        injectHoverMove(source, mTooltipView, 0, 0);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        injectHoverMove(source, mTooltipView, jitterHigh, 0);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        injectHoverMove(source, mTooltipView, 0, 0);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        injectHoverMove(source, mTooltipView, 0, jitterHigh);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        // Jitter below threshold should be ignored and the tooltip should be shown.
+        injectHoverMove(source, mTooltipView, 0, 0);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        int jitterLow = hoverSlop - 1;
+        injectHoverMove(source, mTooltipView, jitterLow, 0);
+        waitOut(halfTimeout);
+        assertTrue(hasTooltip(mTooltipView));
+
+        // Dismiss the tooltip
+        injectShortClick(mTooltipView);
+        assertFalse(hasTooltip(mTooltipView));
+
+        injectHoverMove(source, mTooltipView, 0, 0);
+        waitOut(halfTimeout);
+        assertFalse(hasTooltip(mTooltipView));
+
+        injectHoverMove(source, mTooltipView, 0, jitterLow);
+        waitOut(halfTimeout);
+        assertTrue(hasTooltip(mTooltipView));
+    }
+
+    @Test
     public void testTooltipInPopup() throws Throwable {
         TextView popupContent = new TextView(mActivity);
 
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
index 1fd3d8c..9c94d4c 100644
--- a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
@@ -16,24 +16,19 @@
 
 package android.view.cts;
 
-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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
-import android.app.Activity;
 import android.app.Instrumentation;
+import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 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.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
+
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -44,37 +39,145 @@
 @RunWith(AndroidJUnit4.class)
 public class TouchDelegateTest {
     private Instrumentation mInstrumentation;
-    private Activity mActivity;
-    private Button mButton;
+    private TouchDelegateTestActivity mActivity;
 
     @Rule
-    public ActivityTestRule<MockActivity> mActivityRule =
-            new ActivityTestRule<>(MockActivity.class);
+    public ActivityTestRule<TouchDelegateTestActivity> mActivityRule =
+            new ActivityTestRule<>(TouchDelegateTestActivity.class);
 
     @Before
     public void setup() throws Throwable {
         mActivity = mActivityRule.getActivity();
+        mActivity.resetCounters();
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-
-        mButton = new Button(mActivity);
-        mActivityRule.runOnUiThread(() -> mActivity.addContentView(
-                mButton, new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)));
         mInstrumentation.waitForIdleSync();
     }
 
-    @UiThreadTest
     @Test
-    public void testOnTouchEvent() {
-        // test callback of onTouchEvent
-        final View view = new View(mActivity);
-        final TouchDelegate touchDelegate = mock(TouchDelegate.class);
-        view.setTouchDelegate(touchDelegate);
+    public void testParentClick() {
+        // If only clicking parent, button should not receive click
+        clickParent();
+        assertEquals(0, mActivity.getButtonClickCount());
+        assertEquals(1, mActivity.getParentClickCount());
 
-        final int xInside = (mButton.getLeft() + mButton.getRight()) / 3;
-        final int yInside = (mButton.getTop() + mButton.getBottom()) / 3;
+        // When clicking TouchDelegate area, both parent and button
+        // should receive DOWN and UP events. However, click will only be generated for the button
+        mActivity.resetCounters();
+        clickTouchDelegateArea();
+        assertEquals(1, mActivity.getButtonClickCount());
+        assertEquals(0, mActivity.getParentClickCount());
 
-        view.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, xInside, yInside, 0));
-        verify(touchDelegate, times(1)).onTouchEvent(any(MotionEvent.class));
+        // Ensure parent can still receive clicks after TouchDelegate has been activated once
+        mActivity.resetCounters();
+        clickParent();
+        assertEquals(0, mActivity.getButtonClickCount());
+        assertEquals(1, mActivity.getParentClickCount());
+    }
+
+    @Test
+    public void testCancelEvent() {
+        // Ensure events with ACTION_CANCEL are received by the TouchDelegate
+        final long downTime = SystemClock.uptimeMillis();
+        dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY,
+                downTime);
+        dispatchMotionEventToActivity(MotionEvent.ACTION_CANCEL, mActivity.touchDelegateY,
+                downTime);
+        mInstrumentation.waitForIdleSync();
+
+        ensureOldestActionEquals(MotionEvent.ACTION_DOWN);
+        ensureOldestActionEquals(MotionEvent.ACTION_CANCEL);
+
+        assertNull(mActivity.removeOldestButtonEvent());
+        assertEquals(0, mActivity.getButtonClickCount());
+        assertEquals(0, mActivity.getParentClickCount());
+    }
+
+    @Test
+    public void testTwoPointers() {
+        // 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);
+        int actionPointer1Down =
+                (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_DOWN;
+        dispatchMultiTouchMotionEventToActivity(actionPointer1Down, 2,
+                mActivity.touchDelegateY, downTime);
+        dispatchMultiTouchMotionEventToActivity(MotionEvent.ACTION_MOVE, 2,
+                mActivity.touchDelegateY, downTime);
+        int actionPointer1Up =
+                (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_UP;
+        dispatchMultiTouchMotionEventToActivity(actionPointer1Up, 2,
+                mActivity.touchDelegateY, downTime);
+        dispatchMotionEventToActivity(MotionEvent.ACTION_UP, mActivity.touchDelegateY, downTime);
+        mInstrumentation.waitForIdleSync();
+
+        ensureOldestActionEquals(MotionEvent.ACTION_DOWN);
+        ensureOldestActionEquals(MotionEvent.ACTION_MOVE);
+        ensureOldestActionEquals(MotionEvent.ACTION_POINTER_DOWN);
+        ensureOldestActionEquals(MotionEvent.ACTION_MOVE);
+        ensureOldestActionEquals(MotionEvent.ACTION_POINTER_UP);
+        ensureOldestActionEquals(MotionEvent.ACTION_UP);
+    }
+
+    private void ensureOldestActionEquals(int action) {
+        MotionEvent event = mActivity.removeOldestButtonEvent();
+        assertNotNull(event);
+        assertEquals(action, event.getActionMasked());
+        event.recycle();
+    }
+
+    private void clickParent() {
+        click(mActivity.parentViewY);
+    }
+
+    private void clickTouchDelegateArea() {
+        click(mActivity.touchDelegateY);
+    }
+
+    // Low-level input-handling functions for the activity
+
+    private void click(int y) {
+        final long downTime = SystemClock.uptimeMillis();
+        dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, y, downTime);
+        dispatchMotionEventToActivity(MotionEvent.ACTION_UP, y, downTime);
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void dispatchMotionEventToActivity(int action, int y, long downTime) {
+        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.recycle();
+        });
+    }
+
+    private void dispatchMultiTouchMotionEventToActivity(int action, int pointerCount,
+            int y, long downTime) {
+        mActivity.runOnUiThread(() -> {
+            final long eventTime = SystemClock.uptimeMillis();
+            MotionEvent.PointerProperties[] properties =
+                    new MotionEvent.PointerProperties[pointerCount];
+            MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+
+            for (int i = 0; i < pointerCount; i++) {
+                properties[i] = new MotionEvent.PointerProperties();
+                properties[i].id = i;
+                properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
+                coords[i] = new MotionEvent.PointerCoords();
+                coords[i].x = mActivity.x + i * 10; // small offset so that pointers do not overlap
+                coords[i].y = y;
+            }
+
+            final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, pointerCount,
+                    properties, coords, 0, 0, 0, 0,
+                    0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+
+            mActivity.dispatchTouchEvent(event);
+            event.recycle();
+        });
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
new file mode 100644
index 0000000..0f01a95
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
@@ -0,0 +1,117 @@
+/*
+ * 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.view.cts;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * The layout is:
+ *                                    |       <-- top edge of RelativeLayout (parent)
+ *                                    |
+ *                                    |
+ *                                    |       (touches in this region should go to parent only)
+ *                                    |
+ *                                    |
+ *                                    |       <-- TouchDelegate boundary
+ *                                    |
+ *                                    |
+ *                                    |       (touches in this region should go to button + parent)
+ *                                    |
+ *                                    |
+ *                                    |       <-- Button top boundary
+ *                                    |
+ */
+public class TouchDelegateTestActivity extends Activity {
+    // Counters for click events. Must use waitForIdleSync() before accessing these
+    private volatile int mParentClickCount = 0;
+    private volatile int mButtonClickCount = 0;
+    // Storage for MotionEvents received by the TouchDelegate
+    private Queue<MotionEvent> mButtonEvents = new ArrayDeque<>();
+
+    // Coordinates for injecting input events from the test
+    public int x; // common X coordinate for all input events - center of the screen
+    public int touchDelegateY; // Y coordinate for touches inside TouchDelegate area
+    public int parentViewY; // Y coordinate for touches inside parent area only
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.touch_delegate_test_activity_layout);
+
+        final Button button = findViewById(R.id.button);
+        final View parent = findViewById(R.id.layout);
+
+        parent.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        final int[] parentLocation = new int[2];
+                        parent.getLocationOnScreen(parentLocation);
+                        final int[] buttonLocation = new int[2];
+                        button.getLocationOnScreen(buttonLocation);
+                        x = parentLocation[0] + parent.getWidth() / 2;
+                        final int gap = buttonLocation[1] - parentLocation[1];
+
+                        Rect rect = new Rect();
+                        button.getHitRect(rect);
+                        rect.top -= gap / 2; // TouchDelegate is halfway between button and parent
+                        parent.setTouchDelegate(new TouchDelegate(rect, button));
+                        touchDelegateY = buttonLocation[1] - gap / 4;
+                        parentViewY = parentLocation[1] + gap / 4;
+                    }
+                });
+
+        parent.setOnClickListener(v -> mParentClickCount++);
+        button.setOnClickListener(v -> mButtonClickCount++);
+        button.setOnTouchListener((v, event) -> {
+            mButtonEvents.add(MotionEvent.obtain(event));
+            return TouchDelegateTestActivity.super.onTouchEvent(event);
+        });
+    }
+
+    void resetCounters() {
+        mParentClickCount = 0;
+        mButtonClickCount = 0;
+        mButtonEvents.clear();
+    }
+
+    int getButtonClickCount() {
+        return mButtonClickCount;
+    }
+
+    int getParentClickCount() {
+        return mParentClickCount;
+    }
+
+    /**
+     * Remove and return the oldest MotionEvent. Caller must recycle the returned object.
+     * @return the oldest MotionEvent that the Button has received
+     */
+    MotionEvent removeOldestButtonEvent() {
+        return mButtonEvents.poll();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
index 774aadb..955cf7c 100644
--- a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
+++ b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
@@ -174,7 +174,7 @@
     }
 
     private void addMovement() {
-        if (mTime >= mLastTime) {
+        if (mTime > mLastTime) {
             MotionEvent ev = MotionEvent.obtain(0L, mTime, MotionEvent.ACTION_MOVE, mPx, mPy, 0);
             mVelocityTracker.addMovement(ev);
             ev.recycle();
@@ -201,9 +201,9 @@
         if (errorVx > tolerance || errorVy > tolerance) {
             fail(String.format("Velocity exceeds tolerance of %6.1f%%: "
                     + "expected vx=%6.1f, vy=%6.1f. "
-                    + "actual vx=%6.1f (%6.1f%%), vy=%6.1f (%6.1f%%)",
+                    + "actual vx=%6.1f (%6.1f%%), vy=%6.1f (%6.1f%%). %s",
                     tolerance * 100.0f, mVx, mVy,
-                    estimatedVx, errorVx * 100.0f, estimatedVy, errorVy * 100.0f));
+                    estimatedVx, errorVx * 100.0f, estimatedVy, errorVy * 100.0f, message));
         }
     }
 
diff --git a/tests/tests/view/src/android/view/cts/ViewOutlineProviderTest.java b/tests/tests/view/src/android/view/cts/ViewOutlineProviderTest.java
index 8cd0d37..cfab88f 100644
--- a/tests/tests/view/src/android/view/cts/ViewOutlineProviderTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewOutlineProviderTest.java
@@ -45,11 +45,18 @@
         mContext = InstrumentationRegistry.getTargetContext();
     }
 
+    private void setViewLeftTopRightBottom(View view, int left, int top, int right, int bottom) {
+        view.setLeft(left);
+        view.setTop(top);
+        view.setRight(right);
+        view.setBottom(bottom);
+    }
+
     @UiThreadTest
     @Test
     public void testBackground() {
         View view = new View(mContext);
-        view.setLeftTopRightBottom(100, 200, 300, 400);
+        setViewLeftTopRightBottom(view, 100, 200, 300, 400);
 
         Outline outline = new Outline();
         outline.setAlpha(1.0f);
@@ -84,13 +91,13 @@
         Rect queryRect = new Rect();
         outline.setAlpha(0.123f);
 
-        view.setLeftTopRightBottom(1, 2, 3, 4);
+        setViewLeftTopRightBottom(view, 1, 2, 3, 4);
         ViewOutlineProvider.BOUNDS.getOutline(view, outline);
         outline.getRect(queryRect);
         assertEquals(new Rect(0, 0, 2, 2), queryRect); // local width/height
         assertEquals(0.123f, outline.getAlpha(), 0f); // alpha not changed
 
-        view.setLeftTopRightBottom(100, 200, 300, 400);
+        setViewLeftTopRightBottom(view, 100, 200, 300, 400);
         ViewOutlineProvider.BOUNDS.getOutline(view, outline);
         outline.getRect(queryRect);
         assertEquals(new Rect(0, 0, 200, 200), queryRect); // local width/height
@@ -106,7 +113,7 @@
         Rect queryRect = new Rect();
         outline.setAlpha(0.123f);
 
-        view.setLeftTopRightBottom(10, 20, 30, 40);
+        setViewLeftTopRightBottom(view, 10, 20, 30, 40);
         view.setPadding(0, 0, 0, 0);
         ViewOutlineProvider.PADDED_BOUNDS.getOutline(view, outline);
         outline.getRect(queryRect);
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 831aa15..6b64f82 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -77,6 +78,7 @@
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
@@ -99,6 +101,7 @@
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.compatibility.common.util.CtsMouseUtil;
 import com.android.compatibility.common.util.CtsTouchUtils;
@@ -133,6 +136,7 @@
     private ViewTestCtsActivity mActivity;
     private Resources mResources;
     private MockViewParent mMockParent;
+    private Context mContext;
 
     @Rule
     public ActivityTestRule<ViewTestCtsActivity> mActivityRule =
@@ -145,11 +149,13 @@
     @Before
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
         mActivity = mActivityRule.getActivity();
         PollingCheck.waitFor(mActivity::hasWindowFocus);
         mResources = mActivity.getResources();
         mMockParent = new MockViewParent(mActivity);
-        assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS));
+        PollingCheck.waitFor(5 * DateUtils.SECOND_IN_MILLIS, mActivity::hasWindowFocus);
+        assertTrue(mActivity.hasWindowFocus());
     }
 
     @Test
@@ -340,14 +346,45 @@
 
     @Test
     public void testFindViewById() {
+        // verify view can find self
         View parent = mActivity.findViewById(R.id.viewlayout_root);
         assertSame(parent, parent.findViewById(R.id.viewlayout_root));
 
+        // find expected view type
         View view = parent.findViewById(R.id.mock_view);
         assertTrue(view instanceof MockView);
     }
 
     @Test
+    public void testRequireViewById() {
+        View parent = mActivity.findViewById(R.id.viewlayout_root);
+
+        View requiredView = parent.requireViewById(R.id.mock_view);
+        View foundView = parent.findViewById(R.id.mock_view);
+        assertSame(foundView, requiredView);
+        assertTrue(requiredView instanceof MockView);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRequireViewByIdNoId() {
+        View parent = mActivity.findViewById(R.id.viewlayout_root);
+        parent.requireViewById(View.NO_ID);
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRequireViewByIdInvalid() {
+        View parent = mActivity.findViewById(R.id.viewlayout_root);
+        parent.requireViewById(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRequireViewByIdNotFound() {
+        View parent = mActivity.findViewById(R.id.viewlayout_root);
+        parent.requireViewById(R.id.view); // id not present in view_layout
+    }
+
+    @Test
     public void testAccessTouchDelegate() throws Throwable {
         final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
         Rect rect = new Rect();
@@ -500,6 +537,31 @@
 
         Bitmap bitmap = BitmapFactory.decodeResource(mResources, R.drawable.icon_blue);
         assertNotNull(PointerIcon.create(bitmap, 0, 0));
+        assertNotNull(PointerIcon.create(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2));
+
+        try {
+            PointerIcon.create(bitmap, -1, 0);
+            fail("Hotspot x can not be < 0");
+        } catch (IllegalArgumentException ignore) {
+        }
+
+        try {
+            PointerIcon.create(bitmap, 0, -1);
+            fail("Hotspot y can not be < 0");
+        } catch (IllegalArgumentException ignore) {
+        }
+
+        try {
+            PointerIcon.create(bitmap, bitmap.getWidth(), 0);
+            fail("Hotspot x cannot be >= width");
+        } catch (IllegalArgumentException ignore) {
+        }
+
+        try {
+            PointerIcon.create(bitmap, 0, bitmap.getHeight());
+            fail("Hotspot x cannot be >= height");
+        } catch (IllegalArgumentException e) {
+        }
     }
 
     private void assertSystemPointerIcon(int style) {
@@ -2385,6 +2447,109 @@
     }
 
     @Test
+    public void testKeyFallback() throws Throwable {
+        MockFallbackListener listener = new MockFallbackListener();
+        ViewGroup viewGroup = (ViewGroup) mActivity.findViewById(R.id.viewlayout_root);
+        // Attaching a fallback handler
+        TextView mockView1 = new TextView(mActivity);
+        mockView1.addKeyFallbackListener(listener);
+
+        // Before the view is attached, it shouldn't respond to anything
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertFalse(listener.fired());
+
+        // Once attached, it should start receiving fallback events
+        mActivityRule.runOnUiThread(() -> viewGroup.addView(mockView1));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertTrue(listener.fired());
+        listener.reset();
+
+        // If multiple on one view, last added should receive event first
+        MockFallbackListener listener2 = new MockFallbackListener();
+        listener2.mReturnVal = true;
+        mActivityRule.runOnUiThread(() -> mockView1.addKeyFallbackListener(listener2));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertTrue(listener2.fired());
+        assertFalse(listener.fired());
+        listener2.reset();
+
+        // If removed, it should not receive fallbacks anymore
+        mActivityRule.runOnUiThread(() -> {
+            mockView1.removeKeyFallbackListener(listener);
+            mockView1.removeKeyFallbackListener(listener2);
+        });
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertFalse(listener.fired());
+
+        mActivityRule.runOnUiThread(() -> mActivity.setContentView(R.layout.key_fallback_layout));
+        mInstrumentation.waitForIdleSync();
+        View higherInNormal = mActivity.findViewById(R.id.higher_in_normal);
+        View higherGroup = mActivity.findViewById(R.id.higher_group);
+        View lowerInHigher = mActivity.findViewById(R.id.lower_in_higher);
+        View lastButton = mActivity.findViewById(R.id.last_button);
+        View lastInHigher = mActivity.findViewById(R.id.last_in_higher);
+        View lastInNormal = mActivity.findViewById(R.id.last_in_normal);
+
+        View[] allViews = new View[]{higherInNormal, higherGroup, lowerInHigher, lastButton,
+                lastInHigher, lastInNormal};
+
+        // Test ordering by depth
+        listener.mReturnVal = true;
+        mActivityRule.runOnUiThread(() -> {
+            for (View v : allViews) {
+                v.addKeyFallbackListener(listener);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(lastInHigher, listener.mLastView);
+        listener.reset();
+
+        mActivityRule.runOnUiThread(() -> lastInHigher.removeKeyFallbackListener(listener));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(lowerInHigher, listener.mLastView);
+        listener.reset();
+
+        mActivityRule.runOnUiThread(() -> lowerInHigher.removeKeyFallbackListener(listener));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(higherGroup, listener.mLastView);
+        listener.reset();
+
+        mActivityRule.runOnUiThread(() -> higherGroup.removeKeyFallbackListener(listener));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(lastButton, listener.mLastView);
+        listener.reset();
+
+        mActivityRule.runOnUiThread(() -> lastButton.removeKeyFallbackListener(listener));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(higherInNormal, listener.mLastView);
+        listener.reset();
+
+        mActivityRule.runOnUiThread(() -> higherInNormal.removeKeyFallbackListener(listener));
+        mInstrumentation.waitForIdleSync();
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertEquals(lastInNormal, listener.mLastView);
+        listener.reset();
+
+        // Test "capture"
+        mActivityRule.runOnUiThread(() -> lastInNormal.requestFocus());
+        mInstrumentation.waitForIdleSync();
+        lastInNormal.setOnKeyListener((v, keyCode, event)
+                -> (keyCode == KeyEvent.KEYCODE_B && event.getAction() == KeyEvent.ACTION_UP));
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+        assertTrue(listener.fired()); // checks that both up and down were received
+        listener.reset();
+    }
+
+    @Test
     public void testWindowVisibilityChanged() throws Throwable {
         final MockView mockView = new MockView(mActivity);
         final ViewGroup viewGroup = (ViewGroup) mActivity.findViewById(R.id.viewlayout_root);
@@ -3884,7 +4049,9 @@
         final MockEditText editText = new MockEditText(mActivity);
 
         mActivityRule.runOnUiThread(() -> {
-            viewGroup.addView(editText);
+            // Give a fixed size since, on most devices, the edittext is off-screen
+            // and therefore doesn't get laid-out properly.
+            viewGroup.addView(editText, 100, 30);
             editText.requestFocus();
         });
         mInstrumentation.waitForIdleSync();
@@ -4125,18 +4292,45 @@
         assertTrue(overridingView.hasOverlappingRendering());
     }
 
+    private boolean startDragAndDrop(View view, View.DragShadowBuilder shadowBuilder) {
+        final Point size = new Point();
+        mActivity.getDisplay().getSize(size);
+        final MotionEvent event = MotionEvent.obtain(
+                SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_DOWN, size.x / 2, size.y / 2, 1);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        mInstrumentation.getUiAutomation().injectInputEvent(event, true);
+
+        return view.startDragAndDrop(ClipData.newPlainText("", ""), shadowBuilder, view, 0);
+    }
+
+    private static View.DragShadowBuilder createDragShadowBuidler() {
+        View.DragShadowBuilder shadowBuilder = mock(View.DragShadowBuilder.class);
+        doAnswer(a -> {
+            final Point outPoint = (Point) a.getArguments()[0];
+            outPoint.x = 1;
+            outPoint.y = 1;
+            return null;
+        }).when(shadowBuilder).onProvideShadowMetrics(any(), any());
+        return shadowBuilder;
+    }
+
     @Test
     public void testUpdateDragShadow() {
         View view = mActivity.findViewById(R.id.fit_windows);
         assertTrue(view.isAttachedToWindow());
 
-        View.DragShadowBuilder shadowBuilder = mock(View.DragShadowBuilder.class);
-        view.startDragAndDrop(ClipData.newPlainText("", ""), shadowBuilder, view, 0);
-        reset(shadowBuilder);
-
-        view.updateDragShadow(shadowBuilder);
-        // TODO: Verify with the canvas from the drag surface instead.
-        verify(shadowBuilder).onDrawShadow(any(Canvas.class));
+        final View.DragShadowBuilder shadowBuilder = createDragShadowBuidler();
+        try {
+            assertTrue("Could not start drag and drop", startDragAndDrop(view, shadowBuilder));
+            reset(shadowBuilder);
+            view.updateDragShadow(shadowBuilder);
+            // TODO: Verify with the canvas from the drag surface instead.
+            verify(shadowBuilder).onDrawShadow(any(Canvas.class));
+        } finally {
+            // Ensure to cancel drag and drop operation so that it does not affect other tests.
+            view.cancelDragAndDrop();
+        }
     }
 
     @Test
@@ -4144,12 +4338,18 @@
         View view = new View(mActivity);
         assertFalse(view.isAttachedToWindow());
 
-        View.DragShadowBuilder shadowBuilder = mock(View.DragShadowBuilder.class);
-        view.startDragAndDrop(ClipData.newPlainText("", ""), shadowBuilder, view, 0);
-        reset(shadowBuilder);
+        View.DragShadowBuilder shadowBuilder = createDragShadowBuidler();
+        try {
+            assertFalse("Drag and drop for detached view must fail",
+                    startDragAndDrop(view, shadowBuilder));
+            reset(shadowBuilder);
 
-        view.updateDragShadow(shadowBuilder);
-        verify(shadowBuilder, never()).onDrawShadow(any(Canvas.class));
+            view.updateDragShadow(shadowBuilder);
+            verify(shadowBuilder, never()).onDrawShadow(any(Canvas.class));
+        } finally {
+            // Ensure to cancel drag and drop operation so that it does not affect other tests.
+            view.cancelDragAndDrop();
+        }
     }
 
     @Test
@@ -4157,7 +4357,7 @@
         View view = mActivity.findViewById(R.id.fit_windows);
         assertTrue(view.isAttachedToWindow());
 
-        View.DragShadowBuilder shadowBuilder = mock(View.DragShadowBuilder.class);
+        View.DragShadowBuilder shadowBuilder = createDragShadowBuidler();
         view.updateDragShadow(shadowBuilder);
         verify(shadowBuilder, never()).onDrawShadow(any(Canvas.class));
     }
@@ -4340,6 +4540,67 @@
         event.recycle();
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleXNaN() {
+        View view = new View(mContext);
+        view.setScaleX(Float.NaN);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleXPositiveInfinity() {
+        View view = new View(mContext);
+        view.setScaleX(Float.POSITIVE_INFINITY);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleXNegativeInfinity() {
+        View view = new View(mContext);
+        view.setScaleX(Float.NEGATIVE_INFINITY);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleYNaN() {
+        View view = new View(mContext);
+        view.setScaleY(Float.NaN);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleYPositiveInfinity() {
+        View view = new View(mContext);
+        view.setScaleY(Float.POSITIVE_INFINITY);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testScaleYNegativeInfinity() {
+        View view = new View(mContext);
+        view.setScaleY(Float.NEGATIVE_INFINITY);
+    }
+
+    @Test
+    public void testSetGetOutlineShadowColor() {
+        ViewGroup group = (ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.view_outlineshadowcolor, null);
+        View defaultShadow = group.findViewById(R.id.default_shadow);
+        assertEquals(Color.BLACK, defaultShadow.getOutlineSpotShadowColor());
+        assertEquals(Color.BLACK, defaultShadow.getOutlineAmbientShadowColor());
+        defaultShadow.setOutlineSpotShadowColor(Color.YELLOW);
+        defaultShadow.setOutlineAmbientShadowColor(Color.GREEN);
+        assertEquals(Color.YELLOW, defaultShadow.getOutlineSpotShadowColor());
+        assertEquals(Color.GREEN, defaultShadow.getOutlineAmbientShadowColor());
+
+        View redAmbientShadow = group.findViewById(R.id.red_shadow);
+        assertEquals(Color.RED, redAmbientShadow.getOutlineAmbientShadowColor());
+        assertEquals(Color.BLACK, redAmbientShadow.getOutlineSpotShadowColor());
+
+        View blueSpotShadow = group.findViewById(R.id.blue_shadow);
+        assertEquals(Color.BLUE, blueSpotShadow.getOutlineSpotShadowColor());
+        assertEquals(Color.BLACK, blueSpotShadow.getOutlineAmbientShadowColor());
+
+        View greenShadow = group.findViewById(R.id.green_shadow);
+        assertEquals(Color.GREEN, greenShadow.getOutlineSpotShadowColor());
+        assertEquals(Color.GREEN, greenShadow.getOutlineAmbientShadowColor());
+    }
+
     private static class MockDrawable extends Drawable {
         private boolean mCalledSetTint = false;
 
@@ -4691,6 +4952,29 @@
         public View firstChild;
     }
 
+    private static class MockFallbackListener implements View.OnKeyFallbackListener {
+        public View mLastView = null;
+        public boolean mGotUp = false;
+        public boolean mReturnVal = false;
+
+        @Override
+        public boolean onKeyFallback(View v, KeyEvent event) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mLastView = v;
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                mGotUp = true;
+            }
+            return mReturnVal;
+        }
+        public void reset() {
+            mLastView = null;
+            mGotUp = false;
+        }
+        public boolean fired() {
+            return mLastView != null && mGotUp;
+        }
+    }
+
     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/ViewTestCtsActivity.java b/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
index 9766fa2..9117925 100644
--- a/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
@@ -18,52 +18,13 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
 import android.view.cts.R;
 
 public class ViewTestCtsActivity extends Activity {
-    private boolean mHasWindowFocus = false;
-    private final Object mHasWindowFocusLock = new Object();
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setContentView(R.layout.view_layout);
     }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        if (!hasFocus) {
-            Log.w("ViewTestCtsActivity", "ViewTestCtsActivity lost window focus");
-        }
-        synchronized(mHasWindowFocusLock) {
-            mHasWindowFocus = hasFocus;
-            mHasWindowFocusLock.notify();
-        }
-    }
-
-    /**
-     * Blocks the calling thread until the {@link ViewTestCtsActivity} has window focus or the
-     * specified duration (in milliseconds) has passed.
-     */
-    public boolean waitForWindowFocus(long durationMillis) {
-        long elapsedMillis = SystemClock.elapsedRealtime();
-        synchronized(mHasWindowFocusLock) {
-            mHasWindowFocus = hasWindowFocus();
-            while (!mHasWindowFocus && durationMillis > 0) {
-                long newElapsedMillis = SystemClock.elapsedRealtime();
-                durationMillis -= (newElapsedMillis - elapsedMillis);
-                elapsedMillis = newElapsedMillis;
-                if (durationMillis > 0) {
-                    try {
-                        mHasWindowFocusLock.wait(durationMillis);
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return mHasWindowFocus;
-        }
-    }
 }
diff --git a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
index 21fdd89..14d2d5b 100644
--- a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
+++ b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
@@ -30,7 +30,6 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -51,6 +50,8 @@
     @UiThreadTest
     @Test
     public void testFocusHandling() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.setInTouchMode(false);
         Activity activity = mActivityRule.getActivity();
 
         View v1 = activity.findViewById(R.id.view1);
@@ -151,7 +152,7 @@
         v2.setVisibility(View.VISIBLE);
         v3.setVisibility(View.VISIBLE);
         v4.setVisibility(View.VISIBLE);
-        assertEquals(true, v1.isFocused());
+        assertTrue(v1.isFocused());
         assertFalse(v2.isFocused());
         assertFalse(v3.isFocused());
         assertFalse(v4.isFocused());
@@ -227,6 +228,176 @@
 
     @UiThreadTest
     @Test
+    public void testEnabledHandling() {
+        Activity activity = mActivityRule.getActivity();
+
+        View v1 = activity.findViewById(R.id.view1);
+        View v2 = activity.findViewById(R.id.view2);
+        View v3 = activity.findViewById(R.id.view3);
+        View v4 = activity.findViewById(R.id.view4);
+
+        for (View v : new View[]{v1, v2, v3, v4}) v.setFocusable(true);
+
+        assertTrue(v1.requestFocus());
+
+        // disabled view should not be focusable
+        assertTrue(v1.hasFocus());
+        v1.setEnabled(false);
+        assertFalse(v1.hasFocus());
+        v1.requestFocus();
+        assertFalse(v1.hasFocus());
+        v1.setEnabled(true);
+        v1.requestFocus();
+        assertTrue(v1.hasFocus());
+
+        // an enabled view should not take focus if not visible OR not enabled
+        v1.setEnabled(false);
+        v1.setVisibility(View.INVISIBLE);
+        assertFalse(v1.hasFocus());
+        v1.setEnabled(true);
+        v1.requestFocus();
+        assertFalse(v1.hasFocus());
+        v1.setEnabled(false);
+        v1.setVisibility(View.VISIBLE);
+        v1.requestFocus();
+        assertFalse(v1.hasFocus());
+        v1.setEnabled(true);
+        v1.requestFocus();
+        assertTrue(v1.hasFocus());
+
+        // test hasFocusable
+        ViewGroup parent = (ViewGroup) v1.getParent();
+        assertTrue(parent.hasFocusable());
+        for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(false);
+        assertFalse(v1.isFocused());
+        assertFalse(v2.isFocused());
+        assertFalse(v3.isFocused());
+        assertFalse(v4.isFocused());
+        assertFalse(parent.hasFocusable());
+
+        // a view enabled while nothing has focus should get focus if not in touch mode.
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
+        for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(true);
+        assertEquals(true, v1.isFocused());
+    }
+
+    @Test
+    public void testSizeHandling() {
+        Activity activity = mActivityRule.getActivity();
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        View v5 = new Button(activity);
+        ViewGroup layout = activity.findViewById(R.id.auto_test_area);
+
+        // Test requestFocus before first layout focuses if non-0 size
+        activity.runOnUiThread(() -> {
+            layout.addView(v5, 30, 30);
+            assertTrue(isZeroSize(v5));
+            assertTrue(v5.requestFocus());
+        });
+        instrumentation.waitForIdleSync();
+        assertFalse(isZeroSize(v5));
+        assertTrue(v5.isFocused());
+
+        // Test resize to 0 defocuses
+        activity.runOnUiThread(() -> {
+            v5.setRight(v5.getLeft());
+            assertEquals(0, v5.getWidth());
+        });
+        instrumentation.waitForIdleSync();
+        assertTrue(isZeroSize(v5));
+        assertFalse(v5.isFocused());
+
+        // Test requestFocus on laid-out 0-size fails
+        activity.runOnUiThread(() -> assertFalse(v5.requestFocus()));
+
+        activity.runOnUiThread(() -> layout.removeAllViews());
+
+        // Test requestFocus before first layout focuses a child if non-0 size
+        LinearLayout ll0 = new LinearLayout(activity);
+        ll0.setFocusable(true);
+        ll0.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        View butInScroll = new Button(activity);
+        activity.runOnUiThread(() -> {
+            ll0.addView(butInScroll, 40, 40);
+            layout.addView(ll0, 100, 100);
+            assertTrue(isZeroSize(butInScroll));
+            assertTrue(ll0.requestFocus());
+        });
+        instrumentation.waitForIdleSync();
+        assertFalse(isZeroSize(butInScroll));
+        assertTrue(butInScroll.isFocused());
+
+        // Test focusableViewAvailable on resize to non-0 size
+        activity.runOnUiThread(() -> {
+            butInScroll.setRight(v5.getLeft());
+        });
+        instrumentation.waitForIdleSync();
+        assertTrue(isZeroSize(butInScroll));
+        assertTrue(ll0.isFocused());
+
+        activity.runOnUiThread(() -> layout.removeAllViews());
+        instrumentation.waitForIdleSync();
+
+        // Test requestFocus before first layout defocuses if still 0 size
+        LinearLayout ll = new LinearLayout(activity);
+        View zeroSizeBut = new Button(activity);
+        activity.runOnUiThread(() -> {
+            ll.addView(zeroSizeBut, 30, 0);
+            layout.addView(ll, 100, 100);
+            assertTrue(zeroSizeBut.requestFocus());
+        });
+        instrumentation.waitForIdleSync();
+        assertTrue(isZeroSize(zeroSizeBut));
+        assertFalse(zeroSizeBut.isFocused());
+
+        activity.runOnUiThread(() -> layout.removeAllViews());
+        instrumentation.waitForIdleSync();
+
+        // Test requestFocus before first layout focuses parent if child is still 0 size
+        LinearLayout ll2 = new LinearLayout(activity);
+        ll2.setFocusable(true);
+        ll2.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        View zeroButInAfter = new Button(activity);
+        activity.runOnUiThread(() -> {
+            ll2.addView(zeroButInAfter, 40, 0);
+            layout.addView(ll2, 100, 100);
+            assertTrue(ll2.requestFocus());
+            assertTrue(zeroButInAfter.isFocused());
+        });
+        instrumentation.waitForIdleSync();
+        assertTrue(isZeroSize(zeroButInAfter));
+        assertFalse(zeroButInAfter.isFocused());
+        assertTrue(ll2.isFocused());
+
+        activity.runOnUiThread(() -> layout.removeAllViews());
+        instrumentation.waitForIdleSync();
+
+        // Test that we don't focus anything above/outside of where we requested focus
+        LinearLayout ll3 = new LinearLayout(activity);
+        Button outside = new Button(activity);
+        LinearLayout sub = new LinearLayout(activity);
+        Button inside = new Button(activity);
+        activity.runOnUiThread(() -> {
+            ll3.addView(outside, 40, 40);
+            sub.addView(inside, 30, 0);
+            ll3.addView(sub, 40, 40);
+            layout.addView(ll3, 100, 100);
+            assertTrue(sub.requestFocus());
+            assertTrue(inside.isFocused());
+        });
+        instrumentation.waitForIdleSync();
+        assertTrue(isZeroSize(inside));
+        assertTrue(outside.isFocusable() && !isZeroSize(outside));
+        assertNull(layout.getRootView().findFocus());
+    }
+
+    private boolean isZeroSize(View view) {
+        return view.getWidth() <= 0 || view.getHeight() <= 0;
+    }
+
+    @UiThreadTest
+    @Test
     public void testFocusAuto() {
         Activity activity = mActivityRule.getActivity();
 
@@ -328,20 +499,6 @@
         assertTrue("single view doesn't hasExplicitFocusable", view.hasExplicitFocusable());
     }
 
-    private View[] getInitialAndFirstFocus(int res) throws Throwable {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.setInTouchMode(false);
-        final Activity activity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(() -> activity.getLayoutInflater().inflate(res,
-                (ViewGroup) activity.findViewById(R.id.auto_test_area)));
-        instrumentation.waitForIdleSync();
-        View root = activity.findViewById(R.id.main_view);
-        View initial = root.findFocus();
-        instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
-        View first = root.findFocus();
-        return new View[]{initial, first};
-    }
-
     @UiThreadTest
     @Test
     public void testFocusAfterDescendantsTransfer() throws Throwable {
diff --git a/tests/tests/view/src/android/view/cts/View_InitialFocusTest.java b/tests/tests/view/src/android/view/cts/View_InitialFocusTest.java
new file mode 100644
index 0000000..45fd8fa
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/View_InitialFocusTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class View_InitialFocusTest {
+    @Rule
+    public ActivityTestRule<FocusHandlingCtsActivity> mActivityRule =
+            new ActivityTestRule<>(FocusHandlingCtsActivity.class, true);
+
+    @Before
+    public void setup() throws Exception {
+        Activity activity = mActivityRule.getActivity();
+        Assume.assumeTrue(
+                activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN));
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.setInTouchMode(true);
+    }
+
+    private View[] getInitialAndFirstFocus(int res) throws Throwable {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Activity activity = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(() -> activity.getLayoutInflater().inflate(res,
+                (ViewGroup) activity.findViewById(R.id.auto_test_area)));
+        instrumentation.waitForIdleSync();
+        View root = activity.findViewById(R.id.main_view);
+        View initial = root.findFocus();
+        instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); // leaves touch-mode
+        View first = root.findFocus();
+        return new View[]{initial, first};
+    }
+
+    @Test
+    public void testNoInitialFocus() throws Throwable {
+        Activity activity = mActivityRule.getActivity();
+        View[] result = getInitialAndFirstFocus(R.layout.focus_handling_focusables);
+        assertNull(result[0]);
+        assertSame(result[1], activity.findViewById(R.id.focusable1));
+    }
+
+    @Test
+    public void testDefaultFocus() throws Throwable {
+        Activity activity = mActivityRule.getActivity();
+        View[] result = getInitialAndFirstFocus(R.layout.focus_handling_default_focus);
+        assertNull(result[0]);
+        assertSame(result[1], activity.findViewById(R.id.focusable2));
+    }
+
+    @Test
+    public void testInitialFocus() throws Throwable {
+        Activity activity = mActivityRule.getActivity();
+        View[] result = getInitialAndFirstFocus(R.layout.focus_handling_initial_focus);
+        assertSame(result[0], activity.findViewById(R.id.focusable3));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testClearFocus() throws Throwable {
+        Activity activity = mActivityRule.getActivity();
+        View v1 = activity.findViewById(R.id.view1);
+        v1.setFocusableInTouchMode(true);
+        assertFalse(v1.isFocused());
+        v1.requestFocus();
+        assertTrue(v1.isFocused());
+        v1.clearFocus();
+        assertNull(activity.getWindow().getDecorView().findFocus());
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/WindowTest.java b/tests/tests/view/src/android/view/cts/WindowTest.java
index 05585f2..41ad595 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -175,11 +175,28 @@
 
     @Test
     public void testFindViewById() {
-        TextView v = (TextView) mWindow.findViewById(R.id.listview_window);
+        TextView v = mWindow.findViewById(R.id.listview_window);
         assertNotNull(v);
         assertEquals(R.id.listview_window, v.getId());
     }
 
+    @Test
+    public void testRequireViewById() {
+        TextView v = mWindow.requireViewById(R.id.listview_window);
+        assertNotNull(v);
+        assertEquals(R.id.listview_window, v.getId());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRequireViewByIdNoId() {
+        TextView v = mWindow.requireViewById(View.NO_ID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRequireViewByIdInvalid() {
+        TextView v = mWindow.requireViewById(R.id.view); // not present in layout
+    }
+
     /**
      * getAttributes: Retrieve the current window attributes associated with this panel.
      *    Return is 1.the existing window attributes object.
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 1ff6dc5..25a3f03 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -16,6 +16,7 @@
 package android.view.cts.surfacevalidator;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.content.Context;
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 71ca678..880cc0d 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -17,6 +17,7 @@
 package android.view.textclassifier.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;
@@ -29,12 +30,19 @@
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
+import android.view.textclassifier.logging.Logger;
+import android.view.textclassifier.logging.Logger.Config;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
@@ -42,7 +50,10 @@
     private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
     private static final int START = 1;
     private static final int END = 3;
-    private static final String TEXT = "text";
+    // This text has lots of things that are probably entities in many cases.
+    private static final String TEXT = "An email address is test@example.com. A phone number"
+            + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
+            + " and there's good stuff at https://www.android.com :)";
 
     private TextClassificationManager mManager;
     private TextClassifier mClassifier;
@@ -56,13 +67,22 @@
     }
 
     @Test
+    public void testSetTextClassifier() {
+        final TextClassifier classifier = mock(TextClassifier.class);
+        mManager.setTextClassifier(classifier);
+        assertEquals(classifier, mManager.getTextClassifier());
+    }
+
+    @Test
     public void testSmartSelection() {
-        assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
+        assertValidResult(mClassifier.suggestSelection(TEXT, START, END,
+                new TextSelection.Options().setDefaultLocales(LOCALES)));
     }
 
     @Test
     public void testClassifyText() {
-        assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
+        assertValidResult(mClassifier.classifyText(TEXT, START, END,
+                new TextClassification.Options().setDefaultLocales(LOCALES)));
     }
 
     @Test
@@ -70,14 +90,16 @@
         mManager.setTextClassifier(TextClassifier.NO_OP);
         mClassifier = mManager.getTextClassifier();
 
-        final TextSelection selection = mClassifier.suggestSelection(TEXT, START, END, LOCALES);
+        final TextSelection selection = mClassifier.suggestSelection(TEXT, START, END,
+                new TextSelection.Options().setDefaultLocales(LOCALES));
         assertValidResult(selection);
         assertEquals(START, selection.getSelectionStartIndex());
         assertEquals(END, selection.getSelectionEndIndex());
         assertEquals(0, selection.getEntityCount());
 
         final TextClassification classification =
-                mClassifier.classifyText(TEXT, START, END, LOCALES);
+                mClassifier.classifyText(TEXT, START, END,
+                        new TextClassification.Options().setDefaultLocales(LOCALES));
         assertValidResult(classification);
         assertNull(classification.getText());
         assertEquals(0, classification.getEntityCount());
@@ -88,14 +110,59 @@
     }
 
     @Test
-    public void testSetTextClassifier() {
-        final TextClassifier classifier = mock(TextClassifier.class);
-        mManager.setTextClassifier(classifier);
-        assertEquals(classifier, mManager.getTextClassifier());
+    public void testGenerateLinks() {
+        assertValidResult(mClassifier.generateLinks(TEXT, null));
+    }
+
+    @Test
+    public void testGetLogger() {
+        final Logger logger = mClassifier.getLogger(new Config(
+                InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null));
+        assertNotNull(logger);
+        assertNotNull(logger.getTokenIterator(LOCALES.get(0)));
+    }
+
+    @Test
+    public void testDisabledLogger() {
+        assertFalse(Logger.DISABLED.isSmartSelection("sig.na.ture"));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_only_hints() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
+                Arrays.asList("some_hint"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(Arrays.asList("foo", "bar"),
+                entityConfig.resolveEntityListModifications(Arrays.asList("foo", "bar")));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_include_exclude() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
+                Arrays.asList("some_hint"),
+                Arrays.asList("a", "b", "c"),
+                Arrays.asList("b", "d", "x"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(new HashSet(Arrays.asList("a", "c", "w")),
+                new HashSet(entityConfig.resolveEntityListModifications(
+                        Arrays.asList("c", "w", "x"))));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_explicit() {
+        TextClassifier.EntityConfig entityConfig =
+                TextClassifier.EntityConfig.createWithEntityList(Arrays.asList("a", "b"));
+        assertEquals(Collections.EMPTY_LIST, entityConfig.getHints());
+        assertEquals(Arrays.asList("a", "b"),
+                entityConfig.resolveEntityListModifications(Arrays.asList("w", "x")));
     }
 
     private static void assertValidResult(TextSelection selection) {
         assertNotNull(selection);
+        assertTrue(selection.getSelectionStartIndex() >= 0);
+        assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
         assertTrue(selection.getEntityCount() >= 0);
         for (int i = 0; i < selection.getEntityCount(); i++) {
             final String entity = selection.getEntity(i);
@@ -104,6 +171,7 @@
             assertTrue(confidenceScore >= 0);
             assertTrue(confidenceScore <= 1);
         }
+        assertNotNull(selection.getSignature());
     }
 
     private static void assertValidResult(TextClassification classification) {
@@ -116,6 +184,23 @@
             assertTrue(confidenceScore >= 0);
             assertTrue(confidenceScore <= 1);
         }
+        assertTrue(classification.getSecondaryActionsCount() >= 0);
+        assertNotNull(classification.getSignature());
+    }
+
+    private static void assertValidResult(TextLinks links) {
+        assertNotNull(links);
+        for (TextLinks.TextLink link : links.getLinks()) {
+            assertTrue(link.getEntityCount() > 0);
+            assertTrue(link.getStart() >= 0);
+            assertTrue(link.getStart() <= link.getEnd());
+            for (int i = 0; i < link.getEntityCount(); i++) {
+                String entityType = link.getEntity(i);
+                assertNotNull(entityType);
+                final float confidenceScore = link.getConfidenceScore(entityType);
+                assertTrue(confidenceScore >= 0);
+                assertTrue(confidenceScore <= 1);
+            }
+        }
     }
 }
-
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
new file mode 100644
index 0000000..8a71c82
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * TextClassifier value objects tests.
+ *
+ * <p>Contains unit tests for value objects passed to/from TextClassifier APIs.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierValueObjectsTest {
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final String SIGNATURE = "sig.na-ture";
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextSelection() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+
+        final TextSelection selection = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setSignature(SIGNATURE)
+                .build();
+
+        assertEquals(START, selection.getSelectionStartIndex());
+        assertEquals(END, selection.getSelectionEndIndex());
+        assertEquals(2, selection.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, selection.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, selection.getEntity(1));
+        assertEquals(addressScore, selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, selection.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+        assertEquals(SIGNATURE, selection.getSignature());
+    }
+
+    @Test
+    public void testTextSelection_differentParams() {
+        final int start = 0;
+        final int end = 1;
+        final float confidenceScore = 0.5f;
+        final String signature = "2hukwu3m3k44f1gb0";
+
+        final TextSelection selection = new TextSelection.Builder(start, end)
+                .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
+                .setSignature(signature)
+                .build();
+
+        assertEquals(start, selection.getSelectionStartIndex());
+        assertEquals(end, selection.getSelectionEndIndex());
+        assertEquals(1, selection.getEntityCount());
+        assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
+        assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
+                ACCEPTED_DELTA);
+        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+        assertEquals(signature, selection.getSignature());
+    }
+
+    @Test
+    public void testTextSelection_defaultValues() {
+        TextSelection selection = new TextSelection.Builder(START, END).build();
+        assertEquals(0, selection.getEntityCount());
+        assertEquals("", selection.getSignature());
+    }
+
+    @Test
+    public void testTextSelection_prunedConfidenceScore() {
+        final float phoneScore = -0.1f;
+        final float prunedPhoneScore = 0f;
+        final float otherScore = 1.5f;
+        final float prunedOtherScore = 1.0f;
+
+        final TextSelection selection = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
+                .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
+                .build();
+
+        assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                ACCEPTED_DELTA);
+        assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
+                ACCEPTED_DELTA);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidStartParams() {
+        new TextSelection.Builder(-1 /* start */, END)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidEndParams() {
+        new TextSelection.Builder(START, 0 /* end */)
+                .build();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testTextSelection_invalidSignature() {
+        new TextSelection.Builder(START, END)
+                .setSignature(null)
+                .build();
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testTextSelection_entityIndexOutOfBounds() {
+        final TextSelection selection = new TextSelection.Builder(START, END).build();
+        final int outOfBoundsIndex = selection.getEntityCount();
+        selection.getEntity(outOfBoundsIndex);
+    }
+
+    @Test
+    public void testTextSelectionOptions() {
+        final TextSelection.Options options = new TextSelection.Options()
+                .setDefaultLocales(LOCALES);
+        assertEquals(LOCALES, options.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextSelectionOptions_nullValues() {
+        final TextSelection.Options options = new TextSelection.Options()
+                .setDefaultLocales(null);
+        assertNull(options.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextSelectionOptions_defaultValues() {
+        final TextSelection.Options options = new TextSelection.Options();
+        assertNull(options.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextClassification() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+        final Intent intent = new Intent();
+        final String label = "label";
+        final Drawable icon = new ColorDrawable(Color.RED);
+        final View.OnClickListener onClick = v -> { };
+        final Intent intent1 = new Intent();
+        final String label1 = "label1";
+        final Drawable icon1 = new ColorDrawable(Color.GREEN);
+        final Intent intent2 = new Intent();
+        final String label2 = "label2";
+        final Drawable icon2 = new ColorDrawable(Color.BLUE);
+
+        final TextClassification classification = new TextClassification.Builder()
+                .setText(TEXT)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setPrimaryAction(intent, label, icon)
+                .setOnClickListener(onClick)
+                .addSecondaryAction(intent1, label1, icon1)
+                .addSecondaryAction(intent2, label2, icon2)
+                .setSignature(SIGNATURE)
+                .build();
+
+        assertEquals(TEXT, classification.getText());
+        assertEquals(2, classification.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+        assertEquals(intent, classification.getIntent());
+        assertEquals(label, classification.getLabel());
+        assertEquals(icon, classification.getIcon());
+        assertEquals(onClick, classification.getOnClickListener());
+
+        assertEquals(2, classification.getSecondaryActionsCount());
+        assertEquals(intent1, classification.getSecondaryIntent(0));
+        assertEquals(label1, classification.getSecondaryLabel(0));
+        assertEquals(icon1, classification.getSecondaryIcon(0));
+        assertEquals(intent2, classification.getSecondaryIntent(1));
+        assertEquals(label2, classification.getSecondaryLabel(1));
+        assertEquals(icon2, classification.getSecondaryIcon(1));
+
+        assertEquals(SIGNATURE, classification.getSignature());
+    }
+
+    @Test
+    public void testTextClassification_defaultValues() {
+        final TextClassification classification = new TextClassification.Builder().build();
+
+        assertEquals(null, classification.getText());
+        assertEquals(0, classification.getEntityCount());
+        assertEquals(null, classification.getIntent());
+        assertEquals(null, classification.getLabel());
+        assertEquals(null, classification.getIcon());
+        assertEquals(null, classification.getOnClickListener());
+        assertEquals(0, classification.getSecondaryActionsCount());
+        assertEquals("", classification.getSignature());
+    }
+
+    @Test
+    public void testTextClassificationOptions() {
+        final TextClassification.Options options = new TextClassification.Options()
+                .setDefaultLocales(LOCALES);
+        assertEquals(LOCALES, options.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextClassificationOptions_nullValues() {
+        final TextClassification.Options options = new TextClassification.Options()
+                .setDefaultLocales(null);
+        assertNull(options.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextClassificationOptions_defaultValues() {
+        final TextClassification.Options options = new TextClassification.Options();
+        assertNull(options.getDefaultLocales());
+    }
+
+    // TODO: Add more tests.
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/logging/cts/LoggerTest.java b/tests/tests/view/src/android/view/textclassifier/logging/cts/LoggerTest.java
new file mode 100644
index 0000000..1ea5dd7
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/logging/cts/LoggerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.view.textclassifier.logging.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.logging.Logger;
+import android.view.textclassifier.logging.Logger.Config;
+import android.view.textclassifier.logging.Logger.WidgetType;
+import android.view.textclassifier.logging.SelectionEvent;
+import android.view.textclassifier.logging.SelectionEvent.EventType;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LoggerTest {
+
+    // TODO: Add more tests.
+
+    private static final @WidgetType String WIDGET_TYPE = Logger.WIDGET_TEXTVIEW;
+    private static final String WIDGET_VERSION = null;
+    private static final int START = 0;
+    private static final int END = 2;
+
+    private Logger mLogger;
+    private Config mConfig;
+    private SelectionEvent mCapturedSelectionEvent;
+
+    @Before
+    public void setup() {
+        mConfig = new Config(
+                InstrumentationRegistry.getTargetContext(), WIDGET_TYPE, WIDGET_VERSION);
+        mLogger = new Logger(mConfig) {
+            @Override
+            public void writeEvent(SelectionEvent event) {
+                mCapturedSelectionEvent = event;
+            }
+        };
+        mCapturedSelectionEvent = null;
+    }
+
+    private void startSelectionSession() {
+        // A selection started event needs to be fired before any non started event will be logged.
+        mLogger.logSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, START);
+    }
+
+    @Test
+    public void testLogger_logManualSelectionStartedEvent() {
+        mLogger.logSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, START);
+        assertThat(mCapturedSelectionEvent,
+                isSelectionEvent(START, START + 1, SelectionEvent.INVOCATION_MANUAL,
+                        SelectionEvent.EVENT_SELECTION_STARTED));
+    }
+
+    @Test
+    public void testLogger_logLinkSelectionStartedEvent() {
+        mLogger.logSelectionStartedEvent(SelectionEvent.INVOCATION_LINK, START);
+        assertThat(mCapturedSelectionEvent,
+                isSelectionEvent(START, START + 1, SelectionEvent.INVOCATION_LINK,
+                        SelectionEvent.EVENT_SELECTION_STARTED));
+    }
+
+    @Test
+    public void testLogger_logSelectionModifiedEvent() {
+        startSelectionSession();
+        mLogger.logSelectionModifiedEvent(START, END);
+        assertThat(mCapturedSelectionEvent,
+                isSelectionEvent(START, END, SelectionEvent.INVOCATION_MANUAL,
+                        SelectionEvent.EVENT_SELECTION_MODIFIED));
+    }
+
+    @Test
+    public void testLogger_logSelectionActionEvent() {
+        startSelectionSession();
+        mLogger.logSelectionActionEvent(START, END, SelectionEvent.ACTION_SMART_SHARE);
+        assertThat(mCapturedSelectionEvent,
+                isSelectionEvent(START, END, SelectionEvent.INVOCATION_MANUAL,
+                        SelectionEvent.ACTION_SMART_SHARE));
+    }
+
+    @Test
+    public void testLoggerConfig() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String widgetVersion = "v1";
+
+        Config config = new Config(context, Logger.WIDGET_CUSTOM_EDITTEXT, widgetVersion);
+
+        assertEquals(context.getPackageName(), config.getPackageName());
+        assertEquals(Logger.WIDGET_CUSTOM_EDITTEXT, config.getWidgetType());
+        assertEquals(widgetVersion, config.getWidgetVersion());
+    }
+
+    private static Matcher<SelectionEvent> isSelectionEvent(
+            final int start, final int end, @SelectionEvent.InvocationMethod int invocationMethod,
+            @EventType int eventType) {
+        return new BaseMatcher<SelectionEvent>() {
+            @Override
+            public boolean matches(Object o) {
+                if (o instanceof SelectionEvent) {
+                    SelectionEvent event = (SelectionEvent) o;
+                    return event.getStart() == start
+                            && event.getEnd() == end
+                            && event.getInvocationMethod() == invocationMethod
+                            && event.getEventType() == eventType;
+                    // TODO: Test more fields.
+                }
+                return false;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendValue(String.format(Locale.US,
+                        "start=%d, end=%d, eventType=%d",
+                        start, end, eventType));
+            }
+        };
+    }
+}
diff --git a/tests/tests/voiceinteraction/Android.mk b/tests/tests/voiceinteraction/Android.mk
index b83f4e9..e0708ff 100644
--- a/tests/tests/voiceinteraction/Android.mk
+++ b/tests/tests/voiceinteraction/Android.mk
@@ -23,6 +23,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := CtsVoiceInteractionCommon ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsVoiceInteractionTestCases
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 6ada1b8..203be4d 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -20,6 +20,7 @@
 import android.app.VoiceInteractor.Prompt;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.service.voice.VoiceInteractionSession;
@@ -46,6 +47,10 @@
     public void onCreate() {
         super.onCreate();
         Intent sessionStarted = new Intent();
+        sessionStarted.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) {
+            sessionStarted.putExtra("error", "Does not have shortcut permission");
+        }
         sessionStarted.setClassName("android.voiceinteraction.cts",
                 "android.voiceinteraction.cts.VoiceInteractionTestReceiver");
         getContext().sendBroadcast(sessionStarted);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
index 07236b2..66c945a 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
@@ -71,7 +71,6 @@
         if (!mHasFeature) {
             return;
         }
-        VoiceInteractionTestReceiver.sServiceStartedLatch.await(5, TimeUnit.SECONDS);
 
         assertTrue("Doesn't support LocalVoiceInteraction",
                 mTestActivity.isLocalVoiceInteractionSupported());
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index 77c9f26..1abd00f 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -87,7 +87,7 @@
     }
 
     public void testAll() throws Exception {
-        VoiceInteractionTestReceiver.sServiceStartedLatch.await(5, TimeUnit.SECONDS);
+        VoiceInteractionTestReceiver.waitSessionStarted(this, 5, TimeUnit.SECONDS);
 
         if (!mHasFeature) {
             Log.i(TAG, "The device doesn't support feature: " + FEATURE_VOICE_RECOGNIZERS);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
index 118f049..7f875c4 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
@@ -21,15 +21,31 @@
 import android.content.Intent;
 import android.util.Log;
 
+import junit.framework.TestCase;
+
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class VoiceInteractionTestReceiver extends BroadcastReceiver {
 
-    public static CountDownLatch sServiceStartedLatch = new CountDownLatch(1);
+    private static CountDownLatch sServiceStartedLatch = new CountDownLatch(1);
+    private static Intent sReceivedIntent;
+
+    public static void waitSessionStarted(TestCase testCase, long timeout, TimeUnit unit)
+            throws InterruptedException {
+        if (!sServiceStartedLatch.await(5, TimeUnit.SECONDS)) {
+            testCase.fail("Timed out waiting for session to start");
+        }
+        String error = sReceivedIntent.getStringExtra("error");
+        if (error != null) {
+            testCase.fail(error);
+        }
+    }
 
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.i("VoiceInteractionTestReceiver", "Got broadcast that MainInteractionService started");
+        sReceivedIntent = intent;
         sServiceStartedLatch.countDown();
     }
 }
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
index dbfb031..7c6cf53 100644
--- a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -135,6 +135,7 @@
 
     private void broadcastResults() {
         Intent intent = new Intent(Utils.BROADCAST_INTENT);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtras(mTotalInfo);
         Log.i(TAG, "broadcasting: " + intent.toString() + ", Bundle = " + mTotalInfo.toString());
         sendOrderedBroadcast(intent, null, new DoneReceiver(),
diff --git a/tests/tests/voicesettings/Android.mk b/tests/tests/voicesettings/Android.mk
index cad36cb..9409073 100644
--- a/tests/tests/voicesettings/Android.mk
+++ b/tests/tests/voicesettings/Android.mk
@@ -23,6 +23,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsVoiceSettingsTestCases
diff --git a/tests/tests/voicesettings/AndroidTest.xml b/tests/tests/voicesettings/AndroidTest.xml
index 421dfb7..dfb50d8 100644
--- a/tests/tests/voicesettings/AndroidTest.xml
+++ b/tests/tests/voicesettings/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Voice Settings test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/webkit/Android.mk b/tests/tests/webkit/Android.mk
index 9cbdd4c..bb70293 100644
--- a/tests/tests/webkit/Android.mk
+++ b/tests/tests/webkit/Android.mk
@@ -21,7 +21,11 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 83775df..21e116d 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -22,7 +22,7 @@
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <application android:maxRecents="1">
+    <application android:maxRecents="1" android:usesCleartextTraffic="true">
         <provider android:name="android.webkit.cts.MockContentProvider"
                   android:exported="true"
                   android:authorities="android.webkit.cts.MockContentProvider" />
@@ -56,7 +56,18 @@
             </intent-filter>
         </activity>
 
-        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" />
+        <service android:name="android.webkit.cts.TestProcessServiceA"
+                 android:process=":testprocessA"
+                 android:exported="false" />
+
+        <service android:name="android.webkit.cts.TestProcessServiceB"
+                 android:process=":testprocessB"
+                 android:exported="false" />
+
+        <!-- Specify a preloaded font list to ensure that this doesn't interfere
+             with the operation of the renderer process (as in b/70968451)
+         -->
+        <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" />
 
     </application>
 
diff --git a/tests/tests/webkit/AndroidTest.xml b/tests/tests/webkit/AndroidTest.xml
index a3801e4..903aef7 100644
--- a/tests/tests/webkit/AndroidTest.xml
+++ b/tests/tests/webkit/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Webkit test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="webview" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/webkit/res/values/preloaded_fonts.xml b/tests/tests/webkit/res/values/preloaded_fonts.xml
new file mode 100644
index 0000000..f771982
--- /dev/null
+++ b/tests/tests/webkit/res/values/preloaded_fonts.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<resources>
+    <!-- Specify a preloaded font list to ensure that this doesn't interfere
+         with the operation of the renderer process (as in b/70968451).
+         The list is intentionally empty, only the resource lookup for the list
+         itself needs to be covered.
+     -->
+    <array name="preloaded_fonts" translatable="false" />
+    <!-- Give the preloaded font list resource an ID that's unlikely to be
+         present in the APK implementing WebView to ensure it will crash if
+         actually looked up in the renderer process.
+     -->
+    <public type="array" name="preloaded_fonts" id="0x7f424242" />
+</resources>
diff --git a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
index ecc3e4f..0fa239d 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -101,12 +101,26 @@
 
     // Post a string message to main frame and make sure it is received.
     public void testSimpleMessageToMainFrame() throws Throwable {
+        verifyPostMessageToOrigin(Uri.parse(BASE_URI));
+    }
+
+    // Post a string message to main frame passing a wildcard as target origin
+    public void testWildcardOriginMatchesAnything() throws Throwable {
+        verifyPostMessageToOrigin(Uri.parse("*"));
+    }
+
+    // Post a string message to main frame passing an empty string as target origin
+    public void testEmptyStringOriginMatchesAnything() throws Throwable {
+        verifyPostMessageToOrigin(Uri.parse(""));
+    }
+
+    private void verifyPostMessageToOrigin(Uri origin) throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
         loadPage(TITLE_FROM_POST_MESSAGE);
         WebMessage message = new WebMessage(WEBVIEW_MESSAGE);
-        mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
+        mOnUiThread.postWebMessage(message, origin);
         waitForTitle(WEBVIEW_MESSAGE);
     }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
new file mode 100644
index 0000000..1854641
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
@@ -0,0 +1,175 @@
+/*
+ * 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.webkit.cts;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+class TestProcessClient extends Assert implements AutoCloseable, ServiceConnection {
+    private Context mContext;
+
+    private static final long CONNECT_TIMEOUT_MS = 5000;
+
+    private Object mLock = new Object();
+    @GuardedBy("mLock")
+    private Messenger mService;
+    @GuardedBy("mLock")
+    private Integer mLastResult;
+    @GuardedBy("mLock")
+    private Throwable mLastException;
+
+    private final Messenger mReplyHandler = new Messenger(new ReplyHandler(Looper.getMainLooper()));
+
+    public static TestProcessClient createProcessA(Context context) throws Throwable {
+        return new TestProcessClient(context, TestProcessServiceA.class);
+    }
+
+    public static TestProcessClient createProcessB(Context context) throws Throwable {
+        return new TestProcessClient(context, TestProcessServiceB.class);
+    }
+
+    /**
+     * Subclass this to implement test code to run on the service side.
+     */
+    static abstract class TestRunnable extends Assert {
+        public abstract void run(Context ctx);
+    }
+
+    static class ProcessFreshChecker extends TestRunnable {
+        private static Object sFreshLock = new Object();
+        @GuardedBy("sFreshLock")
+        private static boolean sFreshProcess = true;
+
+        @Override
+        public void run(Context ctx) {
+            synchronized (sFreshLock) {
+                if (!sFreshProcess) {
+                    fail("Service process was unexpectedly reused");
+                }
+                sFreshProcess = true;
+            }
+        }
+
+    }
+
+    private TestProcessClient(Context context, Class service) throws Throwable {
+        mContext = context;
+        Intent i = new Intent(context, service);
+        context.bindService(i, this, Context.BIND_AUTO_CREATE);
+        synchronized (mLock) {
+            if (mService == null) {
+                mLock.wait(CONNECT_TIMEOUT_MS);
+                if (mService == null) {
+                    fail("Timeout waiting for connection");
+                }
+            }
+        }
+
+        // Check that we're using an actual fresh process.
+        // 1000ms timeout is plenty since the service is already running.
+        run(ProcessFreshChecker.class, 1000);
+    }
+
+    public void run(Class runnableClass, long timeoutMs) throws Throwable {
+        Message m = Message.obtain(null, TestProcessService.MSG_RUN_TEST);
+        m.replyTo = mReplyHandler;
+        m.getData().putString(TestProcessService.TEST_CLASS_KEY, runnableClass.getName());
+        int result;
+        Throwable exception;
+        synchronized (mLock) {
+            mService.send(m);
+            if (mLastResult == null) {
+                mLock.wait(timeoutMs);
+                if (mLastResult == null) {
+                    fail("Timeout waiting for result");
+                }
+            }
+            result = mLastResult;
+            mLastResult = null;
+            exception = mLastException;
+            mLastException = null;
+        }
+        if (result == TestProcessService.REPLY_EXCEPTION) {
+            throw exception;
+        } else if (result != TestProcessService.REPLY_OK) {
+            fail("Unknown result from service: " + result);
+        }
+    }
+
+    public void close() {
+        synchronized (mLock) {
+            if (mService != null) {
+                try {
+                    mService.send(Message.obtain(null, TestProcessService.MSG_EXIT_PROCESS));
+                } catch (RemoteException e) {}
+                mService = null;
+                mContext.unbindService(this);
+            }
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        synchronized (mLock) {
+            mService = new Messenger(service);
+            mLock.notify();
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName className) {
+        synchronized (mLock) {
+            mService = null;
+            mContext.unbindService(this);
+            mLastResult = TestProcessService.REPLY_EXCEPTION;
+            mLastException = new AssertionFailedError("Service disconnected unexpectedly");
+            mLock.notify();
+        }
+    }
+
+    private class ReplyHandler extends Handler {
+        ReplyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mLock) {
+                mLastResult = msg.what;
+                if (msg.what == TestProcessService.REPLY_EXCEPTION) {
+                    mLastException = (Throwable) msg.getData().getSerializable(
+                            TestProcessService.REPLY_EXCEPTION_KEY);
+                }
+                mLock.notify();
+            }
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
new file mode 100644
index 0000000..6ce8c4c
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
@@ -0,0 +1,85 @@
+/*
+ * 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.webkit.cts;
+
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+// Subclasses are the ones that get actually used, so make this abstract
+abstract class TestProcessService extends Service {
+    static final int MSG_RUN_TEST = 0;
+    static final int MSG_EXIT_PROCESS = 1;
+    static final String TEST_CLASS_KEY = "class";
+
+    static final int REPLY_OK = 0;
+    static final int REPLY_EXCEPTION = 1;
+    static final String REPLY_EXCEPTION_KEY = "exception";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    private class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_EXIT_PROCESS) {
+                System.exit(0);
+            }
+
+            try {
+                if (msg.what != MSG_RUN_TEST) {
+                    throw new AssertionFailedError("Unknown service message " + msg.what);
+                }
+
+                String testClassName = msg.getData().getString(TEST_CLASS_KEY);
+                Class testClass = Class.forName(testClassName);
+                TestProcessClient.TestRunnable test =
+                        (TestProcessClient.TestRunnable) testClass.newInstance();
+                test.run(TestProcessService.this);
+            } catch (Throwable t) {
+                try {
+                    Message m = Message.obtain(null, REPLY_EXCEPTION);
+                    m.getData().putSerializable(REPLY_EXCEPTION_KEY, t);
+                    msg.replyTo.send(m);
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+                return;
+            }
+
+            try {
+                msg.replyTo.send(Message.obtain(null, REPLY_OK));
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java
new file mode 100644
index 0000000..14241f0
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java
@@ -0,0 +1,20 @@
+/*
+ * 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.webkit.cts;
+
+// We need two different instances of the same service, so make two subclasses.
+public class TestProcessServiceA extends TestProcessService {}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java
new file mode 100644
index 0000000..d2376f7
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java
@@ -0,0 +1,20 @@
+/*
+ * 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.webkit.cts;
+
+// We need two different instances of the same service, so make two subclasses.
+public class TestProcessServiceB extends TestProcessService {}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java b/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
new file mode 100644
index 0000000..31c1492
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.webkit.cts;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.webkit.TracingConfig;
+import android.webkit.TracingController;
+import android.webkit.WebView;
+import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+
+import com.android.compatibility.common.util.NullWebViewUtils;
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+
+
+public class TracingControllerTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+
+    public static class TracingReceiver extends OutputStream {
+        private int mChunkCount = 0;
+        private boolean mComplete = false;
+        private ByteArrayOutputStream outputStream;
+        private Handler mHandler;
+
+        public TracingReceiver(Handler handler) {
+            outputStream = new ByteArrayOutputStream();
+            mHandler = handler;
+        }
+
+        @Override
+        public void write(byte[] chunk) {
+            validateThread();
+            mChunkCount++;
+            try {
+                outputStream.write(chunk);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void close() {
+            validateThread();
+            mComplete = true;
+        }
+
+        @Override
+        public void flush() {
+            fail("flush should not be called");
+        }
+
+        @Override
+        public void write(int b) {
+            fail("write(int) should not be called");
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) {
+            fail("write(byte[], int, int) should not be called");
+        }
+
+        private void validateThread() {
+            // Ensure the listener is called on the correct thread.
+            if (mHandler == null) {
+                assertEquals(Looper.getMainLooper(), Looper.myLooper());
+            } else {
+                assertEquals(mHandler.getLooper(), Looper.myLooper());
+            }
+        }
+
+        int getNbChunks() { return mChunkCount; }
+        boolean getComplete() { return mComplete; }
+        ByteArrayOutputStream getOutputStream() { return outputStream; }
+    }
+
+    private static final int POLLING_TIMEOUT = 60 * 1000;
+    private WebViewOnUiThread mOnUiThread;
+
+    public TracingControllerTest() throws Exception {
+        super("android.webkit.cts", WebViewCtsActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        WebView webview = getActivity().getWebView();
+        if (webview == null) return;
+        mOnUiThread = new WebViewOnUiThread(this, webview);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
+        }
+        super.tearDown();
+    }
+
+// b/63750258 : Disable tests for landing due to API update,
+// re-enable once the chromium implementation lands and rolls into android master.
+//
+//    // Test that callbacks are invoked and tracing data is returned on the UI thread.
+//    public void testTracingControllerCallbacks() throws Throwable {
+//        if (!NullWebViewUtils.isWebViewAvailable()) {
+//            return;
+//        }
+//        runTracingTestWithCallbacks(null);
+//    }
+//
+//    // Test that callbacks are invoked and tracing data is returned on custom thread.
+//    public void testTracingControllerCallbacksOnCustomThread() throws Throwable {
+//        if (!NullWebViewUtils.isWebViewAvailable()) {
+//            return;
+//        }
+//
+//        final HandlerThread handlerThread = new HandlerThread("TracingControllerTest");
+//        handlerThread.start();
+//        final Handler handler = new Handler(handlerThread.getLooper());
+//        runTracingTestWithCallbacks(handler);
+//        handlerThread.quitSafely();
+//    }
+//
+//    // Test that tracing cannot be started if already tracing.
+//    @UiThreadTest
+//    public void testTracingCannotStartIfAlreadyTracing() throws Exception {
+//        if (!NullWebViewUtils.isWebViewAvailable()) {
+//            return;
+//        }
+//
+//        TracingController tracingController = TracingController.getInstance();
+//        boolean resultStart = tracingController.start(new TracingConfig(TracingConfig.CATEGORIES_WEB_DEVELOPER));
+//        assertTrue(resultStart);
+//        assertTrue(tracingController.isTracing());
+//        assertFalse(tracingController.start(new TracingConfig(TracingConfig.CATEGORIES_RENDERING)));
+//        assertTrue(tracingController.stop());
+//   }
+//
+//    // Test that tracing stop has no effect if tracing has not been started.
+//    @UiThreadTest
+//    public void testTracingStopFalseIfNotTracing() throws Exception {
+//        if (!NullWebViewUtils.isWebViewAvailable()) {
+//            return;
+//        }
+//
+//        TracingController tracingController = TracingController.getInstance();
+//        assertFalse(tracingController.stop());
+//        assertFalse(tracingController.isTracing());
+//    }
+//
+//    // Generic helper function for running tracing using a given handler.
+//    private void runTracingTestWithCallbacks(Handler handler) throws Throwable {
+//        final TracingReceiver tracingReceiver = new TracingReceiver(handler);
+//        runTestOnUiThread(new Runnable() {
+//            @Override
+//            public void run() {
+//                TracingController tracingController = TracingController.getInstance();
+//                assertNotNull(tracingController);
+//                boolean resultStart = tracingController.start(new TracingConfig(TracingConfig.CATEGORIES_WEB_DEVELOPER));
+//                assertTrue(resultStart);
+//                assertTrue(tracingController.isTracing());
+//
+//                mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
+//                boolean resultStop = tracingController.stopAndFlush(tracingReceiver, handler);
+//                assertTrue(resultStop);
+//            }
+//        });
+//
+//        Callable<Boolean> tracingComplete = new Callable<Boolean>() {
+//            @Override
+//            public Boolean call() {
+//                return tracingReceiver.getComplete();
+//            }
+//         };
+//         PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, tracingComplete);
+//         assertTrue(tracingReceiver.getNbChunks() > 0);
+//         assertTrue(tracingReceiver.getOutputStream().size() > 0);
+//         assertTrue(tracingReceiver.getOutputStream().toString().startsWith("{\"traceEvents\":"));
+//    }
+
+}
+
diff --git a/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java b/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
index e8f0cab..34ae10a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
@@ -30,8 +30,9 @@
     private final String VALID_CONTENT_URL = "content://test";
     private final String VALID_DATA_URL = "data://test";
     private final String VALID_ABOUT_URL = "about://test";
-    private final String VALID_FILE_URL = "file://test";
+    private final String VALID_FILE_URL = "file:///test";
     private final String VALID_FTP_URL = "ftp://www.domain.com";
+    private final String FILE_URL_NO_SLASH = "file:test";
 
     public void testIsAssetUrl() {
         assertFalse(URLUtil.isAssetUrl(null));
@@ -70,6 +71,9 @@
         assertFalse(URLUtil.isFileUrl(VALID_ASSET_URL));
         assertFalse(URLUtil.isFileUrl(VALID_PROXY_URL));
         assertTrue(URLUtil.isFileUrl(VALID_FILE_URL));
+        // Because chromium treats this a file URL, we should make sure {@link URLUtil#isFileUrl}
+        // agrees.
+        assertTrue(URLUtil.isFileUrl(FILE_URL_NO_SLASH));
     }
 
     public void testIsHttpsUrl() {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index f10e7fd..6d61e1c 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -1030,7 +1030,6 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        assertFalse(mSettings.getSafeBrowsingEnabled());
         mSettings.setSafeBrowsingEnabled(false);
         assertFalse(mSettings.getSafeBrowsingEnabled());
     }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
new file mode 100644
index 0000000..4fab198
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.webkit.cts;
+
+
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase2;
+import android.webkit.CookieManager;
+import android.webkit.WebView;
+
+import com.android.compatibility.common.util.NullWebViewUtils;
+
+public class WebViewDataDirTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+
+    private static final long REMOTE_TIMEOUT_MS = 5000;
+    private static final String ALTERNATE_DIR_NAME = "test";
+    private static final String COOKIE_URL = "https://www.example.com/";
+    private static final String COOKIE_VALUE = "foo=main";
+    private static final String SET_COOKIE_PARAMS = "; Max-Age=86400";
+
+    public WebViewDataDirTest() throws Exception {
+        super("android.webkit.cts", WebViewCtsActivity.class);
+    }
+
+    static class TestDisableThenUseImpl extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            WebView.disableWebView();
+            try {
+                new WebView(ctx);
+                fail("didn't throw IllegalStateException");
+            } catch (IllegalStateException e) {}
+        }
+    }
+
+    public void testDisableThenUse() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        try (TestProcessClient process = TestProcessClient.createProcessA(getActivity())) {
+            process.run(TestDisableThenUseImpl.class, REMOTE_TIMEOUT_MS);
+        }
+    }
+
+    public void testUseThenDisable() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertNotNull(getActivity().getWebView());
+        try {
+            WebView.disableWebView();
+            fail("didn't throw IllegalStateException");
+        } catch (IllegalStateException e) {}
+    }
+
+    public void testUseThenChangeDir() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertNotNull(getActivity().getWebView());
+        try {
+            WebView.setDataDirectorySuffix(ALTERNATE_DIR_NAME);
+            fail("didn't throw IllegalStateException");
+        } catch (IllegalStateException e) {}
+    }
+
+    static class TestInvalidDirImpl extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            try {
+                WebView.setDataDirectorySuffix("no/path/separators");
+                fail("didn't throw IllegalArgumentException");
+            } catch (IllegalArgumentException e) {}
+        }
+    }
+
+    public void testInvalidDir() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        try (TestProcessClient process = TestProcessClient.createProcessA(getActivity())) {
+            process.run(TestInvalidDirImpl.class, REMOTE_TIMEOUT_MS);
+        }
+    }
+
+    static class TestDefaultDirDisallowed extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            try {
+                new WebView(ctx);
+                fail("didn't throw RuntimeException");
+            } catch (RuntimeException e) {}
+        }
+    }
+
+    public void testSameDirTwoProcesses() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertNotNull(getActivity().getWebView());
+
+        try (TestProcessClient processA = TestProcessClient.createProcessA(getActivity())) {
+            processA.run(TestDefaultDirDisallowed.class, REMOTE_TIMEOUT_MS);
+        }
+    }
+
+    static class TestCookieInAlternateDir extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            WebView.setDataDirectorySuffix(ALTERNATE_DIR_NAME);
+            CookieManager cm = CookieManager.getInstance();
+            String cookie = cm.getCookie(COOKIE_URL);
+            assertNull("cookie leaked to alternate cookie jar", cookie);
+        }
+    }
+
+    public void testCookieJarsSeparate() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        CookieManager cm = CookieManager.getInstance();
+        cm.setCookie(COOKIE_URL, COOKIE_VALUE + SET_COOKIE_PARAMS);
+        cm.flush();
+        String cookie = cm.getCookie(COOKIE_URL);
+        assertEquals("wrong cookie in default cookie jar", COOKIE_VALUE, cookie);
+
+        try (TestProcessClient processA = TestProcessClient.createProcessA(getActivity())) {
+            processA.run(TestCookieInAlternateDir.class, REMOTE_TIMEOUT_MS);
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index 0708568..688fc33 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -898,8 +898,8 @@
     }
 
     private void clearClientCertPreferences() {
-       final AtomicBoolean cleared = new AtomicBoolean(false);
-        mOnUiThread.clearClientCertPreferences(new Runnable() {
+        final AtomicBoolean cleared = new AtomicBoolean(false);
+        WebView.clearClientCertPreferences(new Runnable() {
             @Override
             public void run() {
                 cleared.set(true);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
old mode 100755
new mode 100644
index dd081ff..4bedbb1
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -27,6 +27,7 @@
 import android.graphics.Color;
 import android.graphics.Picture;
 import android.graphics.Rect;
+import android.graphics.pdf.PdfRenderer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -38,6 +39,7 @@
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
 import android.print.PageRange;
 import android.print.PrintAttributes;
 import android.print.PrintDocumentAdapter;
@@ -272,7 +274,7 @@
         assertNotNull(CookieSyncManager.getInstance());
     }
 
-    @UiThreadTest
+    // Static methods should be safe to call on non-UI threads
     public void testFindAddress() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -489,6 +491,7 @@
         assertFalse(mWebView.overlayVerticalScrollbar());
     }
 
+    @Presubmit
     @UiThreadTest
     public void testLoadUrl() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -2562,7 +2565,8 @@
                     // Called on UI thread
                     @Override
                     public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-                        savePrintedPage(adapter, descriptor, result);
+                        PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES};
+                        savePrintedPage(adapter, descriptor, pageRanges, result);
                     }
                 });
         try {
@@ -2580,6 +2584,57 @@
         }
     }
 
+    // Verify Print feature can create a PDF file with correct number of pages.
+    public void testPrintingPagesCount() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        String content = "<html><head></head><body>";
+        for (int i = 0; i < 500; ++i) {
+            content += "<br />abcdefghijk<br />";
+        }
+        content += "</body></html>";
+        mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null);
+        final PrintDocumentAdapter adapter =  mOnUiThread.createPrintDocumentAdapter();
+        printDocumentStart(adapter);
+        PrintAttributes attributes = new PrintAttributes.Builder()
+                .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
+                .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
+                .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
+                .build();
+        final WebViewCtsActivity activity = getActivity();
+        final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
+        final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
+                ParcelFileDescriptor.parseMode("w"));
+        final FutureTask<Boolean> result =
+                new FutureTask<Boolean>(new Callable<Boolean>() {
+                            public Boolean call() {
+                                return true;
+                            }
+                        });
+        printDocumentLayout(adapter, null, attributes,
+                new LayoutResultCallback() {
+                    // Called on UI thread
+                    @Override
+                    public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+                        PageRange[] pageRanges = new PageRange[] {
+                            new PageRange(1, 1), new PageRange(4, 7)
+                        };
+                        savePrintedPage(adapter, descriptor, pageRanges, result);
+                    }
+                });
+        try {
+            result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+            assertTrue(file.length() > 0);
+            PdfRenderer renderer = new PdfRenderer(
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
+            assertEquals(5, renderer.getPageCount());
+        } finally {
+            descriptor.close();
+            file.delete();
+        }
+    }
+
     public void testVisualStateCallbackCalled() throws Exception {
         // Check that the visual state callback is called correctly.
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -2808,8 +2863,9 @@
     }
 
     private void savePrintedPage(final PrintDocumentAdapter adapter,
-            final ParcelFileDescriptor descriptor, final FutureTask<Boolean> result) {
-        adapter.onWrite(new PageRange[] {PageRange.ALL_PAGES}, descriptor,
+            final ParcelFileDescriptor descriptor, final PageRange[] pageRanges,
+            final FutureTask<Boolean> result) {
+        adapter.onWrite(pageRanges, descriptor,
                 new CancellationSignal(),
                 new WriteResultCallback() {
                     @Override
@@ -3016,4 +3072,12 @@
             Assert.fail("The privacy policy URL should be a well-formed URL");
         }
     }
+
+    public void testWebViewClassLoaderReturnsNonNull() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertNotNull(WebView.getWebViewClassLoader());
+    }
 }
diff --git a/tests/tests/widget/Android.mk b/tests/tests/widget/Android.mk
index 53c052b..209f6cb 100644
--- a/tests/tests/widget/Android.mk
+++ b/tests/tests/widget/Android.mk
@@ -22,15 +22,15 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
+    android-support-annotations \
     android-support-test \
     mockito-target-minus-junit4 \
     android-common \
     compatibility-device-util \
     ctstestrunner \
-    platform-test-annotations \
-    legacy-android-test
+    platform-test-annotations
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index f7fa1d4..3300265 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="android.widget.cts">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <application android:label="Android TestCase"
             android:icon="@drawable/size_48x48"
@@ -329,6 +330,16 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.cts.TextClockCtsActivity"
+                  android:label="TextClockCtsActivity"
+                  android:screenOrientation="nosensor"
+                  android:windowSoftInputMode="stateAlwaysHidden">
+            <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.TextViewCtsActivity"
                   android:label="TextViewCtsActivity"
                   android:screenOrientation="nosensor"
@@ -570,6 +581,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.cts.MagnifierCtsActivity"
+                  android:label="MagnifierCtsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <receiver android:name="android.widget.cts.appwidget.MyAppWidgetProvider" >
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index 2eb0b86..a00e5c9 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Widget test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/widget/assets/font_source/ascii_a3em_weight100_upright.ttx b/tests/tests/widget/assets/font_source/ascii_a3em_weight100_upright.ttx
new file mode 100644
index 0000000..e74087a
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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/widget/assets/font_source/ascii_b3em_weight100_italic.ttx b/tests/tests/widget/assets/font_source/ascii_b3em_weight100_italic.ttx
new file mode 100644
index 0000000..4ce17a5
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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/widget/assets/font_source/ascii_c3em_weight200_upright.ttx b/tests/tests/widget/assets/font_source/ascii_c3em_weight200_upright.ttx
new file mode 100644
index 0000000..5c35360
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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="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/widget/assets/font_source/ascii_d3em_weight200_italic.ttx b/tests/tests/widget/assets/font_source/ascii_d3em_weight200_italic.ttx
new file mode 100644
index 0000000..cb0119f
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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="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/widget/assets/font_source/ascii_e3em_weight300_upright.ttx b/tests/tests/widget/assets/font_source/ascii_e3em_weight300_upright.ttx
new file mode 100644
index 0000000..034b2ba
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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="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/widget/assets/font_source/ascii_f3em_weight300_italic.ttx b/tests/tests/widget/assets/font_source/ascii_f3em_weight300_italic.ttx
new file mode 100644
index 0000000..485e5db
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 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="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/widget/assets/font_source/ascii_g3em_weight400_upright.ttx b/tests/tests/widget/assets/font_source/ascii_g3em_weight400_upright.ttx
new file mode 100644
index 0000000..db9a0f4
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:29 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/widget/assets/font_source/ascii_h3em_weight400_italic.ttx b/tests/tests/widget/assets/font_source/ascii_h3em_weight400_italic.ttx
new file mode 100644
index 0000000..ce423a7
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:29 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/widget/assets/font_source/ascii_i3em_weight500_upright.ttx b/tests/tests/widget/assets/font_source/ascii_i3em_weight500_upright.ttx
new file mode 100644
index 0000000..8f13c06
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:29 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/widget/assets/font_source/ascii_j3em_weight500_italic.ttx b/tests/tests/widget/assets/font_source/ascii_j3em_weight500_italic.ttx
new file mode 100644
index 0000000..612d81e
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:29 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/widget/assets/font_source/ascii_k3em_weight600_upright.ttx b/tests/tests/widget/assets/font_source/ascii_k3em_weight600_upright.ttx
new file mode 100644
index 0000000..8d06d3d
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:29 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/widget/assets/font_source/ascii_l3em_weight600_italic.ttx b/tests/tests/widget/assets/font_source/ascii_l3em_weight600_italic.ttx
new file mode 100644
index 0000000..56ab313
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_m3em_weight700_upright.ttx b/tests/tests/widget/assets/font_source/ascii_m3em_weight700_upright.ttx
new file mode 100644
index 0000000..4667562
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_n3em_weight700_italic.ttx b/tests/tests/widget/assets/font_source/ascii_n3em_weight700_italic.ttx
new file mode 100644
index 0000000..ea23b8c
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_o3em_weight800_upright.ttx b/tests/tests/widget/assets/font_source/ascii_o3em_weight800_upright.ttx
new file mode 100644
index 0000000..d1695ed
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_p3em_weight800_italic.ttx b/tests/tests/widget/assets/font_source/ascii_p3em_weight800_italic.ttx
new file mode 100644
index 0000000..2e52014
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_q3em_weight900_upright.ttx b/tests/tests/widget/assets/font_source/ascii_q3em_weight900_upright.ttx
new file mode 100644
index 0000000..7a5f642
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:30 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/widget/assets/font_source/ascii_r3em_weight900_italic.ttx b/tests/tests/widget/assets/font_source/ascii_r3em_weight900_italic.ttx
new file mode 100644
index 0000000..ebe4974
--- /dev/null
+++ b/tests/tests/widget/assets/font_source/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 22 10:04:31 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/widget/res/font/ascii_a3em_weight100_upright.ttf b/tests/tests/widget/res/font/ascii_a3em_weight100_upright.ttf
new file mode 100644
index 0000000..17cf0c7
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_a3em_weight100_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_b3em_weight100_italic.ttf b/tests/tests/widget/res/font/ascii_b3em_weight100_italic.ttf
new file mode 100644
index 0000000..36ed3ce
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_b3em_weight100_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_c3em_weight200_upright.ttf b/tests/tests/widget/res/font/ascii_c3em_weight200_upright.ttf
new file mode 100644
index 0000000..ef4aaf7
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_c3em_weight200_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_d3em_weight200_italic.ttf b/tests/tests/widget/res/font/ascii_d3em_weight200_italic.ttf
new file mode 100644
index 0000000..761060f
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_d3em_weight200_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_e3em_weight300_upright.ttf b/tests/tests/widget/res/font/ascii_e3em_weight300_upright.ttf
new file mode 100644
index 0000000..645ae28
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_e3em_weight300_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_f3em_weight300_italic.ttf b/tests/tests/widget/res/font/ascii_f3em_weight300_italic.ttf
new file mode 100644
index 0000000..e716981
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_f3em_weight300_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_g3em_weight400_upright.ttf b/tests/tests/widget/res/font/ascii_g3em_weight400_upright.ttf
new file mode 100644
index 0000000..97e25a8
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_g3em_weight400_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_h3em_weight400_italic.ttf b/tests/tests/widget/res/font/ascii_h3em_weight400_italic.ttf
new file mode 100644
index 0000000..c4d3113
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_h3em_weight400_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_i3em_weight500_upright.ttf b/tests/tests/widget/res/font/ascii_i3em_weight500_upright.ttf
new file mode 100644
index 0000000..e595c8e
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_i3em_weight500_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_j3em_weight500_italic.ttf b/tests/tests/widget/res/font/ascii_j3em_weight500_italic.ttf
new file mode 100644
index 0000000..119532f
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_j3em_weight500_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_k3em_weight600_upright.ttf b/tests/tests/widget/res/font/ascii_k3em_weight600_upright.ttf
new file mode 100644
index 0000000..94e4d66
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_k3em_weight600_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_l3em_weight600_italic.ttf b/tests/tests/widget/res/font/ascii_l3em_weight600_italic.ttf
new file mode 100644
index 0000000..1d9fcd0
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_l3em_weight600_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_m3em_weight700_upright.ttf b/tests/tests/widget/res/font/ascii_m3em_weight700_upright.ttf
new file mode 100644
index 0000000..e46b591
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_m3em_weight700_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_n3em_weight700_italic.ttf b/tests/tests/widget/res/font/ascii_n3em_weight700_italic.ttf
new file mode 100644
index 0000000..6fcfea6
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_n3em_weight700_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_o3em_weight800_upright.ttf b/tests/tests/widget/res/font/ascii_o3em_weight800_upright.ttf
new file mode 100644
index 0000000..5d9acd2
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_o3em_weight800_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_p3em_weight800_italic.ttf b/tests/tests/widget/res/font/ascii_p3em_weight800_italic.ttf
new file mode 100644
index 0000000..06f2080
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_p3em_weight800_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_q3em_weight900_upright.ttf b/tests/tests/widget/res/font/ascii_q3em_weight900_upright.ttf
new file mode 100644
index 0000000..dcc3f45
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_q3em_weight900_upright.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/ascii_r3em_weight900_italic.ttf b/tests/tests/widget/res/font/ascii_r3em_weight900_italic.ttf
new file mode 100644
index 0000000..50b0672
--- /dev/null
+++ b/tests/tests/widget/res/font/ascii_r3em_weight900_italic.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/fullfamily.xml b/tests/tests/widget/res/font/fullfamily.xml
new file mode 100644
index 0000000..b00bc87
--- /dev/null
+++ b/tests/tests/widget/res/font/fullfamily.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:font="@font/ascii_a3em_weight100_upright" />
+    <font android:font="@font/ascii_b3em_weight100_italic" />
+    <font android:font="@font/ascii_c3em_weight200_upright" />
+    <font android:font="@font/ascii_d3em_weight200_italic" />
+    <font android:font="@font/ascii_e3em_weight300_upright" />
+    <font android:font="@font/ascii_f3em_weight300_italic" />
+    <font android:font="@font/ascii_g3em_weight400_upright" />
+    <font android:font="@font/ascii_h3em_weight400_italic" />
+    <font android:font="@font/ascii_i3em_weight500_upright" />
+    <font android:font="@font/ascii_j3em_weight500_italic" />
+    <font android:font="@font/ascii_k3em_weight600_upright" />
+    <font android:font="@font/ascii_l3em_weight600_italic" />
+    <font android:font="@font/ascii_m3em_weight700_upright" />
+    <font android:font="@font/ascii_n3em_weight700_italic" />
+    <font android:font="@font/ascii_o3em_weight800_upright" />
+    <font android:font="@font/ascii_p3em_weight800_italic" />
+    <font android:font="@font/ascii_q3em_weight900_upright" />
+    <font android:font="@font/ascii_r3em_weight900_italic" />
+</font-family>
diff --git a/tests/tests/widget/res/layout/autocompletetextview_layout.xml b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
index 793dfb0..32eefb7 100644
--- a/tests/tests/widget/res/layout/autocompletetextview_layout.xml
+++ b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
@@ -18,6 +18,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
+    <requestFocus />
 
     <TextView android:id="@+id/autocompletetv_title"
         android:layout_width="wrap_content"
diff --git a/tests/tests/widget/res/layout/linearlayout_zero_weight_horizontal.xml b/tests/tests/widget/res/layout/linearlayout_zero_weight_horizontal.xml
new file mode 100644
index 0000000..9178cb7
--- /dev/null
+++ b/tests/tests/widget/res/layout/linearlayout_zero_weight_horizontal.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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">
+
+    <LinearLayout
+        android:id="@+id/container1"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <LinearLayout
+            android:id="@+id/view1"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/holo_green_light">
+            <View
+                android:layout_width="30dp"
+                android:layout_height="10dp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/view2"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/holo_orange_light">
+            <View
+                android:layout_width="30dp"
+                android:layout_height="10dp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/view3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <View
+                android:layout_width="100dp"
+                android:layout_height="10dp" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/tests/tests/widget/res/layout/linearlayout_zero_weight_vertical.xml b/tests/tests/widget/res/layout/linearlayout_zero_weight_vertical.xml
new file mode 100644
index 0000000..9e50d72
--- /dev/null
+++ b/tests/tests/widget/res/layout/linearlayout_zero_weight_vertical.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_height="match_parent"
+    android:layout_width="match_parent">
+
+    <LinearLayout
+        android:id="@+id/container1"
+        android:layout_height="100dp"
+        android:layout_width="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/view1"
+            android:layout_height="0dp"
+            android:layout_width="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/holo_green_light">
+            <View
+                android:layout_height="30dp"
+                android:layout_width="10dp"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/view2"
+            android:layout_height="0dp"
+            android:layout_width="wrap_content"
+            android:layout_weight="1"
+            android:background="@android:color/holo_orange_light">
+            <View
+                android:layout_height="30dp"
+                android:layout_width="10dp"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/view3"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content">
+            <View
+                android:layout_height="100dp"
+                android:layout_width="10dp"/>
+        </LinearLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/tests/tests/widget/res/layout/list_popup_window.xml b/tests/tests/widget/res/layout/list_popup_window.xml
index bcf5893..fd9dcdf 100644
--- a/tests/tests/widget/res/layout/list_popup_window.xml
+++ b/tests/tests/widget/res/layout/list_popup_window.xml
@@ -19,6 +19,7 @@
     android:id="@+id/main_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <android.widget.cts.MockViewForListPopupWindow
         android:id="@+id/anchor_upper_left"
diff --git a/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml
new file mode 100644
index 0000000..f736e48
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml
@@ -0,0 +1,23 @@
+<?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_four_quadrants_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_four_quadrants_layout.xml
new file mode 100644
index 0000000..0240498
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_activity_four_quadrants_layout.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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_four_quadrants_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1.0"
+        android:orientation="horizontal" >
+        <FrameLayout
+            android:id="@+id/magnifier_activity_four_quadrants_layout_quadrant_1"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1.0"
+            android:background="@android:color/holo_blue_bright" />
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1.0"
+            android:background="@android:color/holo_green_light" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1.0"
+        android:orientation="horizontal" >
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1.0"
+            android:background="@android:color/holo_orange_light" />
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1.0"
+            android:background="@android:color/holo_red_light" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textclock_layout.xml b/tests/tests/widget/res/layout/textclock_layout.xml
new file mode 100644
index 0000000..a1acce8
--- /dev/null
+++ b/tests/tests/widget/res/layout/textclock_layout.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<TextClock
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/textclock"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.xml b/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.xml
new file mode 100644
index 0000000..684d821
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/layout_textviewtest"
+        android:orientation="vertical">
+
+    <TextView android:id="@+id/textview_true"
+              android:fallbackLineSpacing="true"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/textview_false"
+              android:fallbackLineSpacing="false"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/textview_default"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/textview_style_true"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              style="@style/TextAppearance.FallbackLineSpacingTrue"/>
+
+    <TextView android:id="@+id/textview_style_false"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              style="@style/TextAppearance.FallbackLineSpacingFalse"/>
+
+    <TextView android:id="@+id/textview_style_default"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              style="@style/TextAppearance"/>
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index d311d75..86882de 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -46,12 +46,14 @@
             <TextView
                 android:id="@+id/textview_password"
                 android:password="true"
+                android:minWidth="1dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
 
             <TextView
                 android:id="@+id/textview_singleLine"
                 android:singleLine="true"
+                android:minWidth="1dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
 
@@ -59,12 +61,14 @@
                 android:id="@+id/textview_text"
                 android:text="@string/text_view_hello"
                 android:breakStrategy="simple"
+                android:minWidth="1dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
 
             <TextView
                 android:id="@+id/textview_text_two_lines"
                 android:text="@string/text_view_hello_two_lines"
+                android:minWidth="1dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
 
@@ -383,6 +387,66 @@
                 android:text="@string/sample_text"
                 android:justificationMode="inter_word" />
 
+            <TextView
+                android:id="@+id/textview_textappearance_attrs1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                style="@null"
+                android:textAppearance="@style/TextAppearance.Xml1" />
+
+            <TextView
+                android:id="@+id/textview_textappearance_attrs2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                style="@null"
+                android:textAppearance="@style/TextAppearance.Xml2" />
+
+            <TextView
+                android:id="@+id/textview_textappearance_attrs3"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:textAppearance="@style/TextAppearance.Xml2"
+                style="@style/TextAppearance.Xml3" />
+
+            <TextView
+                android:id="@+id/textview_textappearance_attrs4"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                style="@style/TextAppearance.Xml3" />
+
+            <TextView
+                android:id="@+id/textview_textappearance_attrs_allcaps_password"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                style="@style/AllCapsPassword" />
+
+            <TextView
+                android:id="@+id/textview_textappearance_attrs_serif_fontfamily"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:typeface="serif"
+                android:textAppearance="@style/TextAppearance.Xml2" />
+
+            <TextView
+                android:id="@+id/textview_baseline"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:textSize="20dp"
+                android:paddingTop="10dp"
+                android:paddingBottom="10dp"
+                android:firstBaselineToTopHeight="@dimen/textview_firstBaselineToTopHeight"
+                android:lastBaselineToBottomHeight="@dimen/textview_lastBaselineToBottomHeight"
+                android:lineSpacingExtra="1dp"
+                android:lineSpacingMultiplier="0.5"
+                android:lineHeight="@dimen/textview_lineHeight" />
+
         </LinearLayout>
 
 </ScrollView>
diff --git a/tests/tests/widget/res/layout/textview_weight_test_layout.xml b/tests/tests/widget/res/layout/textview_weight_test_layout.xml
new file mode 100644
index 0000000..7464ec4
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_weight_test_layout.xml
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight100_upright"
+        android:textFontWeight="100" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight100_italic"
+        android:textFontWeight="100"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight100_upright"
+        android:textAppearance="@style/TextAppearance.Weight100Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight100_italic"
+        android:textAppearance="@style/TextAppearance.Weight100Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight200_upright"
+        android:textFontWeight="200" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight200_italic"
+        android:textFontWeight="200"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight200_upright"
+        android:textAppearance="@style/TextAppearance.Weight200Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight200_italic"
+        android:textAppearance="@style/TextAppearance.Weight200Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight300_upright"
+        android:textFontWeight="300" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight300_italic"
+        android:textFontWeight="300"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight300_upright"
+        android:textAppearance="@style/TextAppearance.Weight300Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight300_italic"
+        android:textAppearance="@style/TextAppearance.Weight300Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight400_upright"
+        android:textFontWeight="400" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight400_italic"
+        android:textFontWeight="400"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight400_upright"
+        android:textAppearance="@style/TextAppearance.Weight400Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight400_italic"
+        android:textAppearance="@style/TextAppearance.Weight400Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight500_upright"
+        android:textFontWeight="500" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight500_italic"
+        android:textFontWeight="500"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight500_upright"
+        android:textAppearance="@style/TextAppearance.Weight500Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight500_italic"
+        android:textAppearance="@style/TextAppearance.Weight500Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight600_upright"
+        android:textFontWeight="600" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight600_italic"
+        android:textFontWeight="600"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight600_upright"
+        android:textAppearance="@style/TextAppearance.Weight600Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight600_italic"
+        android:textAppearance="@style/TextAppearance.Weight600Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight700_upright"
+        android:textFontWeight="700" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight700_italic"
+        android:textFontWeight="700"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight700_upright"
+        android:textAppearance="@style/TextAppearance.Weight700Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight700_italic"
+        android:textAppearance="@style/TextAppearance.Weight700Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight800_upright"
+        android:textFontWeight="800" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight800_italic"
+        android:textFontWeight="800"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight800_upright"
+        android:textAppearance="@style/TextAppearance.Weight800Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight800_italic"
+        android:textAppearance="@style/TextAppearance.Weight800Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight900_upright"
+        android:textFontWeight="900" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight900_italic"
+        android:textFontWeight="900"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight900_upright"
+        android:textAppearance="@style/TextAppearance.Weight900Upright" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight900_italic"
+        android:textAppearance="@style/TextAppearance.Weight900Italic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_normal"
+        android:textStyle="normal" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_normal"
+        android:textAppearance="@style/TextAppearance.Normal" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_bold"
+        android:textStyle="bold" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_bold"
+        android:textAppearance="@style/TextAppearance.Bold" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_italic"
+        android:textStyle="italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_italic"
+        android:textAppearance="@style/TextAppearance.Italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_bold_italic"
+        android:textStyle="bold|italic" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_bold_italic"
+        android:textAppearance="@style/TextAppearance.BoldItalic" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textView_weight100_bold"
+        android:textFontWeight="100"
+        android:textStyle="bold" />
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/fullfamily"
+        android:id="@+id/textAppearance_weight100_bold"
+        android:textAppearance="@style/TextAppearance.Weight100Bold" />
+</LinearLayout>
diff --git a/tests/tests/widget/res/menu/popup_menu.xml b/tests/tests/widget/res/menu/popup_menu.xml
index 29daad0..a2653b4 100644
--- a/tests/tests/widget/res/menu/popup_menu.xml
+++ b/tests/tests/widget/res/menu/popup_menu.xml
@@ -18,12 +18,14 @@
           android:title="@string/popup_menu_highlight"
           android:contentDescription="@string/popup_menu_highlight_description"
           android:tooltipText="@string/popup_menu_highlight_tooltip" />
-    <item android:id="@+id/action_edit"
-          android:title="@string/popup_menu_edit"
-          android:contentDescription="@string/popup_menu_edit_description"
-          android:tooltipText="@string/popup_menu_edit_tooltip" />
-    <item android:id="@+id/action_delete"
-          android:title="@string/popup_menu_delete" />
+    <group android:id="@+id/group_test2">
+        <item android:id="@+id/action_edit"
+              android:title="@string/popup_menu_edit"
+              android:contentDescription="@string/popup_menu_edit_description"
+              android:tooltipText="@string/popup_menu_edit_tooltip" />
+        <item android:id="@+id/action_delete"
+              android:title="@string/popup_menu_delete" />
+    </group>
     <item android:id="@+id/action_ignore"
           android:title="@string/popup_menu_ignore" />
     <item android:id="@+id/action_share"
diff --git a/tests/tests/widget/res/values/dimens.xml b/tests/tests/widget/res/values/dimens.xml
index 4ae70d8..685e2c1 100644
--- a/tests/tests/widget/res/values/dimens.xml
+++ b/tests/tests/widget/res/values/dimens.xml
@@ -45,6 +45,10 @@
     <dimen name="textview_padding_bottom">4dip</dimen>
     <dimen name="textview_drawable_padding">2dip</dimen>
 
+    <dimen name="textview_firstBaselineToTopHeight">100dp</dimen>
+    <dimen name="textview_lastBaselineToBottomHeight">30dp</dimen>
+    <dimen name="textview_lineHeight">60dp</dimen>
+
     <dimen name="radiogroup_margin">4dip</dimen>
     <dimen name="listrow_height">40dp</dimen>
 
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index a47f169..08e96fe 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -109,6 +109,154 @@
         <item name="android:textStyle">normal</item>
     </style>
 
+    <style name="TextAppearance.Xml1">
+        <item name="android:textSize">22px</item>
+        <item name="android:typeface">sans</item>
+        <item name="android:textStyle">italic</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:letterSpacing">2.4</item>
+        <item name="android:fontFeatureSettings">smcp</item>
+    </style>
+
+    <style name="TextAppearance.Xml2">
+        <item name="android:fontFamily">monospace</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:shadowColor">@drawable/red</item>
+        <item name="android:shadowDx">10.3</item>
+        <item name="android:shadowDy">0.5</item>
+        <item name="android:shadowRadius">3.3</item>
+        <item name="android:elegantTextHeight">true</item>
+    </style>
+
+    <style name="TextAppearance.Xml3">
+        <item name="android:textSize">32px</item>
+        <item name="android:fontFamily">serif</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:letterSpacing">2.6</item>
+        <item name="android:shadowColor">@drawable/blue</item>
+        <item name="android:shadowDx">1.3</item>
+        <item name="android:shadowDy">10.5</item>
+        <item name="android:shadowRadius">5.3</item>
+        <item name="android:elegantTextHeight">false</item>
+    </style>
+
+
+    <style name="TextAppearance.FallbackLineSpacingFalse">
+        <item name="android:fallbackLineSpacing">false</item>
+    </style>
+
+    <style name="TextAppearance.FallbackLineSpacingTrue">
+        <item name="android:fallbackLineSpacing">true</item>
+    </style>
+
+    <style name="TextAppearance.Weight100Upright">
+        <item name="android:textFontWeight">100</item>
+    </style>
+
+    <style name="TextAppearance.Weight200Upright">
+        <item name="android:textFontWeight">200</item>
+    </style>
+
+    <style name="TextAppearance.Weight300Upright">
+        <item name="android:textFontWeight">300</item>
+    </style>
+
+    <style name="TextAppearance.Weight400Upright">
+        <item name="android:textFontWeight">400</item>
+    </style>
+
+    <style name="TextAppearance.Weight500Upright">
+        <item name="android:textFontWeight">500</item>
+    </style>
+
+    <style name="TextAppearance.Weight600Upright">
+        <item name="android:textFontWeight">600</item>
+    </style>
+
+    <style name="TextAppearance.Weight700Upright">
+        <item name="android:textFontWeight">700</item>
+    </style>
+
+    <style name="TextAppearance.Weight800Upright">
+        <item name="android:textFontWeight">800</item>
+    </style>
+
+    <style name="TextAppearance.Weight900Upright">
+        <item name="android:textFontWeight">900</item>
+    </style>
+
+    <style name="TextAppearance.Weight100Italic">
+        <item name="android:textFontWeight">100</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight200Italic">
+        <item name="android:textFontWeight">200</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight300Italic">
+        <item name="android:textFontWeight">300</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight400Italic">
+        <item name="android:textFontWeight">400</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight500Italic">
+        <item name="android:textFontWeight">500</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight600Italic">
+        <item name="android:textFontWeight">600</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight700Italic">
+        <item name="android:textFontWeight">700</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight800Italic">
+        <item name="android:textFontWeight">800</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight900Italic">
+        <item name="android:textFontWeight">900</item>
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.Normal">
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="TextAppearance.Bold">
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.Italic">
+        <item name="android:textStyle">italic</item>
+    </style>
+
+    <style name="TextAppearance.BoldItalic">
+        <item name="android:textStyle">bold|italic</item>
+    </style>
+
+    <style name="TextAppearance.Weight100Bold">
+        <item name="android:textFontWeight">100</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="AllCapsPassword">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:password">true</item>
+    </style>
+
     <style name="TestEnum1">
         <item name="testEnum">val1</item>
     </style>
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index 51e6102..7bbc08a 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -41,6 +41,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -60,11 +61,13 @@
 import android.util.Xml;
 import android.view.ActionMode;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
 import android.widget.ListView;
@@ -413,6 +416,53 @@
     }
 
     @Test
+    public void testSelectorOnScreen() throws Throwable {
+        // leave touch-mode
+        mInstrumentation.setInTouchMode(false);
+        setAdapter();
+        mInstrumentation.waitForIdleSync();
+        // Entering touch mode hides selector
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
+            // make sure we've left touchmode (including message sending. instrumentation just sets
+            // a variable without any broadcast).
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+            mActivityRule.runOnUiThread(() -> {
+                mListView.requestFocus();
+                mListView.setSelectionFromTop(1, 0);
+            });
+            mInstrumentation.waitForIdleSync();
+            assertEquals(1, mListView.getSelectedItemPosition());
+            final int[] pt = new int[2];
+            mListView.getLocationOnScreen(pt);
+            CtsTouchUtils.emulateDragGesture(mInstrumentation, pt[0] + 2, pt[1] + 2, 0, 10);
+            mInstrumentation.waitForIdleSync();
+            assertEquals(AdapterView.INVALID_POSITION, mListView.getSelectedItemPosition());
+            // leave touch-mode
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+        }
+
+        // Scroll off-screen hides selector, but shows up again when on-screen
+        mActivityRule.runOnUiThread(() -> {
+            mListView.requestFocus();
+            mListView.setSelectionFromTop(1, 0);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals(1, mListView.getSelectedItemPosition());
+        int selViewHeight = mListView.getSelectedView().getHeight();
+        final int[] pt = new int[2];
+        mListView.getLocationOnScreen(pt);
+        pt[0] += mListView.getWidth() / 2;
+        pt[1] += selViewHeight / 2;
+        mActivityRule.runOnUiThread(() -> mListView.scrollListBy(selViewHeight * 2));
+        mInstrumentation.waitForIdleSync();
+        assertEquals(1, mListView.getSelectedItemPosition());
+        assertFalse(mListView.shouldDrawSelector());
+        mActivityRule.runOnUiThread(() -> mListView.scrollListBy(-(selViewHeight * 4) / 3));
+        assertEquals(1, mListView.getSelectedItemPosition());
+        assertTrue(mListView.shouldDrawSelector());
+    }
+
+    @Test
     public void testSetScrollIndicators() throws Throwable {
         final Activity activity = mActivityRule.getActivity();
         TextView tv1 = (TextView) activity.findViewById(R.id.headerview1);
diff --git a/tests/tests/widget/src/android/widget/cts/CalendarViewTest.java b/tests/tests/widget/src/android/widget/cts/CalendarViewTest.java
index e66b1d9..0395db0 100644
--- a/tests/tests/widget/src/android/widget/cts/CalendarViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CalendarViewTest.java
@@ -26,9 +26,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
-import android.annotation.ColorInt;
 import android.app.Instrumentation;
 import android.graphics.Rect;
+import android.support.annotation.ColorInt;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 9cb9903..32e54f1 100644
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -30,6 +30,7 @@
 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.TextUtils;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.MovementMethod;
@@ -360,4 +361,45 @@
             return super.getDefaultMovementMethod();
         }
     }
+
+    @Test
+    public void testGetTextNonEditable() {
+        // This subclass calls getText before the object is fully constructed. This should not cause
+        // a null pointer exception.
+        GetTextEditText editText = new GetTextEditText(mActivity);
+    }
+
+    private class GetTextEditText extends EditText {
+
+        GetTextEditText(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setText(CharSequence text, BufferType type) {
+            Editable currentText = getText();
+            super.setText(text, type);
+        }
+    }
+
+    @Test
+    public void testGetTextBeforeConstructor() {
+        // This subclass calls getText before the TextView constructor. This should not cause
+        // a null pointer exception.
+        GetTextEditText2 editText = new GetTextEditText2(mActivity);
+    }
+
+    private class GetTextEditText2 extends EditText {
+
+        GetTextEditText2(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setOverScrollMode(int overScrollMode) {
+            // This method is called by the View constructor before the TextView/EditText
+            // constructors.
+            Editable text = getText();
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 95e3581..189a0c9 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -608,6 +608,7 @@
         final AttachDetachAwareView theView = new AttachDetachAwareView(mActivity);
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mGridView, () -> {
             mGridView.setAdapter(new DummyAdapter(1000, theView));
+            mGridView.requestFocus();
         });
         assertEquals("test sanity", 1, theView.mOnAttachCount);
         assertEquals("test sanity", 0, theView.mOnDetachCount);
diff --git a/tests/tests/widget/src/android/widget/cts/LinearLayoutTest.java b/tests/tests/widget/src/android/widget/cts/LinearLayoutTest.java
index 35841a1..b7dbd8e 100644
--- a/tests/tests/widget/src/android/widget/cts/LinearLayoutTest.java
+++ b/tests/tests/widget/src/android/widget/cts/LinearLayoutTest.java
@@ -23,8 +23,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.annotation.ColorInt;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
@@ -32,6 +30,8 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -1217,6 +1217,30 @@
                 dividerSize, Color.RED, dividerPadding);
     }
 
+    @Test
+    public void testZeroWeightDistributionHorizontal() throws Throwable {
+        // Ensure that weight is correctly distributed when there is no excess space.
+        final View parent = mActivity.findViewById(android.R.id.content);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, parent,
+                () -> mActivity.setContentView(R.layout.linearlayout_zero_weight_horizontal));
+
+        assertEquals(0, mActivity.findViewById(R.id.view1).getWidth());
+        assertEquals(0, mActivity.findViewById(R.id.view2).getWidth());
+        assertEquals(parent.getWidth(), mActivity.findViewById(R.id.view3).getWidth());
+    }
+
+    @Test
+    public void testZeroWeightDistributionVertical() throws Throwable {
+        // Ensure that weight is correctly distributed when there is no excess space.
+        final View parent = mActivity.findViewById(android.R.id.content);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, parent,
+                () -> mActivity.setContentView(R.layout.linearlayout_zero_weight_vertical));
+
+        assertEquals(0, mActivity.findViewById(R.id.view1).getWidth());
+        assertEquals(0, mActivity.findViewById(R.id.view2).getWidth());
+        assertEquals(parent.getWidth(), mActivity.findViewById(R.id.view3).getWidth());
+    }
+
     private class MockListView extends ListView {
         private final static int DEFAULT_CHILD_BASE_LINE = 1;
 
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
new file mode 100644
index 0000000..55d9f81
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A minimal application for {@link MagnifierTest}.
+ */
+public class MagnifierCtsActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.magnifier_activity_basic_layout);
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
new file mode 100644
index 0000000..5c7c47e
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.PointF;
+import android.graphics.Rect;
+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.View;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.Magnifier;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WidgetTestUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link Magnifier}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MagnifierTest {
+    private static final String TIME_LIMIT_EXCEEDED =
+            "Completing the magnifier operation took too long";
+
+    private Activity mActivity;
+    private LinearLayout mLayout;
+    private Magnifier mMagnifier;
+
+    @Rule
+    public ActivityTestRule<MagnifierCtsActivity> mActivityRule =
+            new ActivityTestRule<>(MagnifierCtsActivity.class);
+
+    @Before
+    public void setup() {
+        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.
+        assumeTrue(isScreenBigEnough());
+    }
+
+    private boolean isScreenBigEnough() {
+        // Get the size of the screen in dp.
+        final DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
+        final float dpScreenWidth = displayMetrics.widthPixels / displayMetrics.density;
+        final float dpScreenHeight = displayMetrics.heightPixels / displayMetrics.density;
+        // Get the size of the magnifier window in dp.
+        final PointF dpMagnifier = Magnifier.getMagnifierDefaultSize();
+
+        return dpScreenWidth >= dpMagnifier.x * 1.1 && dpScreenHeight >= dpMagnifier.y * 1.1;
+    }
+
+    @Test
+    public void testConstructor() {
+        new Magnifier(new View(mActivity));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testConstructor_NPE() {
+        new Magnifier(null);
+    }
+
+    @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);
+    }
+
+    @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();
+    }
+
+    @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();
+    }
+
+    @Test
+    public void testWindowContent() 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));
+
+        assertFourQuadrants(mMagnifier.getContent());
+    }
+
+    @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());
+    }
+
+    @Test
+    public void testWindowContent_modifiesAfterUpdate() 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));
+
+        final Bitmap initialBitmap = mMagnifier.getContent()
+                .copy(mMagnifier.getContent().getConfig(), true);
+        assertFourQuadrants(initialBitmap);
+
+        // Make the one quadrant white.
+        final View quadrant1 =
+                mActivity.findViewById(R.id.magnifier_activity_four_quadrants_layout_quadrant_1);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, quadrant1, () -> {
+            quadrant1.setBackground(null);
+        });
+
+        // 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));
+
+        final Bitmap newBitmap = mMagnifier.getContent();
+        assertFourQuadrants(newBitmap);
+        assertFalse(newBitmap.sameAs(initialBitmap));
+    }
+
+    /**
+     * Sets the activity to contain four equal quadrants coloured differently and
+     * instantiates a magnifier. This method should not be called on the UI thread.
+     */
+    private void prepareFourQuadrantsScenario() throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_four_quadrants_layout);
+            mLayout = mActivity.findViewById(R.id.magnifier_activity_four_quadrants_layout);
+            mMagnifier = new Magnifier(mLayout);
+        }, false);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mLayout, null);
+    }
+
+    /**
+     * Asserts that the current bitmap contains exactly four different colors,
+     * and that they are (almost) equally distributed.
+     *
+     * @param bitmap the bitmap to be checked
+     */
+    private void assertFourQuadrants(final Bitmap bitmap) {
+        final int totalPixels = bitmap.getWidth() * bitmap.getHeight();
+        final Map<Integer, Integer> colorCount = new HashMap<>();
+        for (int x = 0; x < bitmap.getWidth(); ++x) {
+            for (int y = 0; y < bitmap.getHeight(); ++y) {
+                final int currentColor = bitmap.getPixel(x, y);
+                colorCount.put(currentColor, colorCount.getOrDefault(currentColor, 0) + 1);
+            }
+        }
+        assertEquals(4, colorCount.size());
+        for (Integer color : colorCount.keySet()) {
+            final int currentCount = colorCount.get(color);
+            final float proportion = 1.0f * Math.abs(4 * currentCount - totalPixels) / totalPixels;
+            assertTrue(proportion <= 0.2f);
+        }
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/MockTextView.java b/tests/tests/widget/src/android/widget/cts/MockTextView.java
index ffdcfde..5e29fb0 100644
--- a/tests/tests/widget/src/android/widget/cts/MockTextView.java
+++ b/tests/tests/widget/src/android/widget/cts/MockTextView.java
@@ -16,9 +16,9 @@
 
 package android.widget.cts;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 import android.text.method.MovementMethod;
 import android.util.AttributeSet;
 import android.widget.TextView;
diff --git a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
index 6640cc4..7392c99 100644
--- a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
@@ -32,6 +32,7 @@
 import android.content.res.Configuration;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -47,6 +48,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 
+@FlakyTest
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class NumberPickerTest {
diff --git a/tests/tests/widget/src/android/widget/cts/OWNERS b/tests/tests/widget/src/android/widget/cts/OWNERS
new file mode 100644
index 0000000..f8095fe
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -0,0 +1,7 @@
+per-file TextView*.java = siyamed@google.com
+per-file TextView*.java = nona@google.com
+per-file TextView*.java = clarabayarri@google.com
+
+per-file EditText*.java = siyamed@google.com
+per-file EditText*.java = nona@google.com
+per-file EditText*.java = clarabayarri@google.com
diff --git a/tests/tests/widget/src/android/widget/cts/PointerIconTest.java b/tests/tests/widget/src/android/widget/cts/PointerIconTest.java
index fe985b2..13b81b0 100644
--- a/tests/tests/widget/src/android/widget/cts/PointerIconTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PointerIconTest.java
@@ -59,8 +59,10 @@
     }
 
     private void assertPointerIcon(String message, PointerIcon expectedIcon, View target) {
-        final int[] topPos = mTopView.getLocationOnScreen();
-        final int[] targetPos = target.getLocationOnScreen();
+        final int[] topPos = new int[2];
+        mTopView.getLocationOnScreen(topPos);
+        final int[] targetPos = new int[2];
+        target.getLocationOnScreen(targetPos);
         final int x = targetPos[0] + target.getWidth() / 2 - topPos[0];
         final int y = targetPos[1] + target.getHeight() / 2 - topPos[1];
         final MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
diff --git a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
index 5ce73a8..3a85f47 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
@@ -40,6 +40,7 @@
 import android.view.SubMenu;
 import android.view.View;
 import android.widget.EditText;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.PopupMenu;
 
@@ -273,7 +274,7 @@
 
     @Test
     public void testItemViewAttributes() throws Throwable {
-        mBuilder = new Builder().withDismissListener();
+        mBuilder = new Builder().withDismissListener().withAnchorId(R.id.anchor_upper_left);
         WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, mBuilder::show, true);
 
         Menu menu = mPopupMenu.getMenu();
@@ -303,6 +304,38 @@
         }
     }
 
+    @Test
+    public void testGroupDividerEnabledAPI() throws Throwable {
+        testGroupDivider(false);
+        testGroupDivider(true);
+    }
+
+    private void testGroupDivider(boolean groupDividerEnabled) throws Throwable {
+        mBuilder = new Builder().withGroupDivider(groupDividerEnabled)
+            .withAnchorId(R.id.anchor_upper_left);
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, mBuilder::show, true);
+
+        Menu menu = mPopupMenu.getMenu();
+        ListView menuItemList = mPopupMenu.getMenuListView();
+
+        for (int i = 0; i < menuItemList.getChildCount(); i++) {
+            final int currGroupId = menu.getItem(i).getGroupId();
+            final int prevGroupId =
+                    i - 1 >= 0 ? menu.getItem(i - 1).getGroupId() : currGroupId;
+            View itemView = menuItemList.getChildAt(i);
+            ImageView groupDivider = itemView.findViewById(com.android.internal.R.id.group_divider);
+
+            assertNotNull(groupDivider);
+            if (!groupDividerEnabled || currGroupId == prevGroupId) {
+                assertEquals(groupDivider.getVisibility(), View.GONE);
+            } else {
+                assertEquals(groupDivider.getVisibility(), View.VISIBLE);
+            }
+        }
+
+        teardown();
+    }
+
     /**
      * Inner helper class to configure an instance of {@link PopupMenu} for the specific test.
      * The main reason for its existence is that once a popup menu is shown with the show() method,
@@ -315,6 +348,7 @@
         private boolean mHasMenuItemClickListener;
         private boolean mInflateWithInflater;
 
+        private int mAnchorId = R.id.anchor_middle_left;
         private int mPopupMenuContent = R.menu.popup_menu;
 
         private boolean mUseCustomPopupResource;
@@ -328,6 +362,8 @@
 
         private View mAnchor;
 
+        private boolean mGroupDividerEnabled = false;
+
         public Builder withMenuItemClickListener() {
             mHasMenuItemClickListener = true;
             return this;
@@ -360,8 +396,18 @@
             return this;
         }
 
+        public Builder withGroupDivider(boolean groupDividerEnabled) {
+            mGroupDividerEnabled = groupDividerEnabled;
+            return this;
+        }
+
+        public Builder withAnchorId(int anchorId) {
+            mAnchorId = anchorId;
+            return this;
+        }
+
         private void configure() {
-            mAnchor = mActivity.findViewById(R.id.anchor_middle_left);
+            mAnchor = mActivity.findViewById(mAnchorId);
             if (!mUseCustomGravity && !mUseCustomPopupResource) {
                 mPopupMenu = new PopupMenu(mActivity, mAnchor);
             } else if (!mUseCustomPopupResource) {
@@ -390,6 +436,10 @@
                 mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
                 mPopupMenu.setOnDismissListener(mOnDismissListener);
             }
+
+            if (mGroupDividerEnabled) {
+                mPopupMenu.getMenu().setGroupDividerEnabled(true);
+            }
         }
 
         public void show() {
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 330a92a..01fcccd 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -40,6 +40,7 @@
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -68,6 +69,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@FlakyTest
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PopupWindowTest {
@@ -1330,6 +1332,9 @@
             mActivity.runOnUiThread(() ->
                     mActivity.setRequestedOrientation(orientation));
             mActivity.waitForConfigurationChanged();
+            // Wait for main thread to be idle to make sure layout and draw have been performed
+            // before continuing.
+            mInstrumentation.waitForIdleSync();
 
             View parentWindowView = mActivity.getWindow().getDecorView();
             int parentWidth = parentWindowView.getMeasuredWidth();
@@ -1398,6 +1403,9 @@
                 mPopupWindow.getContentView().getRootView(),
                 () -> container.scrollBy(deltaX, deltaY),
                 false  /* force layout */);
+        // Since the first layout might have been caused by the original scroll event (and not by
+        // the anchor change), we need to wait until all traversals are done.
+        mInstrumentation.waitForIdleSync();
         assertPopupLocation(originalLocation, deltaX, deltaY);
 
         // Detach the anchor, the popup should stay in the same location.
@@ -1414,6 +1422,7 @@
                 mActivity.getWindow().getDecorView(),
                 () -> container.scrollBy(deltaX, deltaY),
                 true  /* force layout */);
+        mInstrumentation.waitForIdleSync();
         assertPopupLocation(originalLocation, deltaX, deltaY);
 
         // Re-attach the anchor, the popup should snap back to the new anchor location.
@@ -1530,6 +1539,10 @@
                 () -> container.scrollBy(deltaX, deltaY),
                 false  /* force layout */);
 
+        // Since the first layout might have been caused by the original scroll event (and not by
+        // the anchor change), we need to wait until all traversals are done.
+        mInstrumentation.waitForIdleSync();
+
         final int[] newPopupLocation = mPopupWindow.getContentView().getLocationOnScreen();
         assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]);
         assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]);
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index f8b9994..8798c2d 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -52,7 +52,6 @@
 import android.widget.Button;
 import android.widget.Chronometer;
 import android.widget.DatePicker;
-import android.widget.DateTimeView;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.GridLayout;
@@ -412,7 +411,6 @@
         assertTrue(mRemoteViews.onLoadClass(AnalogClock.class));
         assertTrue(mRemoteViews.onLoadClass(Button.class));
         assertTrue(mRemoteViews.onLoadClass(Chronometer.class));
-        assertTrue(mRemoteViews.onLoadClass(DateTimeView.class));
         assertTrue(mRemoteViews.onLoadClass(FrameLayout.class));
         assertTrue(mRemoteViews.onLoadClass(GridLayout.class));
         assertTrue(mRemoteViews.onLoadClass(GridView.class));
diff --git a/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java b/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java
new file mode 100644
index 0000000..43e3582
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextClock;
+
+/**
+ * A minimal application for {@link TextClock} test.
+ */
+public class TextClockCtsActivity extends Activity {
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.textclock_layout);
+    }
+}
+
diff --git a/tests/tests/widget/src/android/widget/cts/TextClockTest.java b/tests/tests/widget/src/android/widget/cts/TextClockTest.java
new file mode 100644
index 0000000..acb6796
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextClockTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.format.DateFormat;
+import android.util.MutableBoolean;
+import android.widget.TextClock;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link TextClock}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TextClockTest {
+    private Activity mActivity;
+    private TextClock mTextClock;
+    private boolean mStartedAs24;
+
+    @Rule
+    public ActivityTestRule<TextClockCtsActivity> mActivityRule =
+            new ActivityTestRule<>(TextClockCtsActivity.class);
+
+    @Before
+    public void setup() throws Throwable {
+        mActivity = mActivityRule.getActivity();
+        mTextClock = mActivity.findViewById(R.id.textclock);
+        mStartedAs24 = DateFormat.is24HourFormat(mActivity);
+    }
+
+    public void teardown() throws Throwable {
+        int base = mStartedAs24 ? 24 : 12;
+        Settings.System.putInt(mActivity.getContentResolver(), Settings.System.TIME_12_24, base);
+    }
+
+    @Test
+    public void testUpdate12_24() throws Throwable {
+        grantWriteSettingsPermission();
+
+        mActivityRule.runOnUiThread(() -> {
+            mTextClock.setFormat12Hour("h");
+            mTextClock.setFormat24Hour("H");
+            mTextClock.disableClockTick();
+        });
+
+        final ContentResolver resolver = mActivity.getContentResolver();
+        Calendar mNow = Calendar.getInstance();
+        mNow.setTimeInMillis(System.currentTimeMillis()); // just like TextClock uses
+
+        // make sure the clock is showing some time > 12pm and not near midnight
+        for (String id : TimeZone.getAvailableIDs()) {
+            final TimeZone timeZone = TimeZone.getTimeZone(id);
+            mNow.setTimeZone(timeZone);
+            int hour = mNow.get(Calendar.HOUR_OF_DAY);
+            if (hour < 22 && hour > 12) {
+                mActivityRule.runOnUiThread(() -> {
+                    mTextClock.setTimeZone(id);
+                });
+                break;
+            }
+        }
+
+        final CountDownLatch change12 = registerForChanges(Settings.System.TIME_12_24);
+        mActivityRule.runOnUiThread(() -> {
+            Settings.System.putInt(resolver, Settings.System.TIME_12_24, 12);
+        });
+        assertTrue(change12.await(1, TimeUnit.SECONDS));
+
+        // Must poll here because there are no timing guarantees for ContentObserver notification
+        PollingCheck.waitFor(() -> {
+            final MutableBoolean ok = new MutableBoolean(false);
+            try {
+                mActivityRule.runOnUiThread(() -> {
+                    int hour = Integer.parseInt(mTextClock.getText().toString());
+                    ok.value = hour >= 1 && hour < 12;
+                });
+            } catch (Throwable t) {
+                throw new RuntimeException(t.getMessage());
+            }
+            return ok.value;
+        });
+
+        final CountDownLatch change24 = registerForChanges(Settings.System.TIME_12_24);
+        mActivityRule.runOnUiThread(() -> {
+            Settings.System.putInt(resolver, Settings.System.TIME_12_24, 24);
+        });
+        assertTrue(change24.await(1, TimeUnit.SECONDS));
+
+        // Must poll here because there are no timing guarantees for ContentObserver notification
+        PollingCheck.waitFor(() -> {
+            final MutableBoolean ok = new MutableBoolean(false);
+            try {
+                mActivityRule.runOnUiThread(() -> {
+                    int hour = Integer.parseInt(mTextClock.getText().toString());
+                    ok.value = hour > 12 && hour < 24;
+                });
+                return ok.value;
+            } catch (Throwable t) {
+                throw new RuntimeException(t.getMessage());
+            }
+        });
+    }
+
+    @Test
+    public void testNoChange() throws Throwable {
+        grantWriteSettingsPermission();
+        mActivityRule.runOnUiThread(() -> {
+            mTextClock.disableClockTick();
+        });
+        final ContentResolver resolver = mActivity.getContentResolver();
+
+        // Now test that it isn't updated when a non-12/24 hour setting is set
+        mActivityRule.runOnUiThread(() -> {
+            mTextClock.setText("Nothing");
+        });
+
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals("Nothing", mTextClock.getText().toString());
+        });
+
+        final CountDownLatch otherChange = registerForChanges(Settings.System.TEXT_AUTO_CAPS);
+        mActivityRule.runOnUiThread(() -> {
+            int oldAutoCaps = Settings.System.getInt(resolver, Settings.System.TEXT_AUTO_CAPS,
+                    1);
+            try {
+                int newAutoCaps = oldAutoCaps == 0 ? 1 : 0;
+                Settings.System.putInt(resolver, Settings.System.TEXT_AUTO_CAPS, newAutoCaps);
+            } finally {
+                Settings.System.putInt(resolver, Settings.System.TEXT_AUTO_CAPS, oldAutoCaps);
+            }
+        });
+
+        assertTrue(otherChange.await(1, TimeUnit.SECONDS));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals("Nothing", mTextClock.getText().toString());
+        });
+    }
+
+    private CountDownLatch registerForChanges(String setting) throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        mActivityRule.runOnUiThread(() -> {
+            final ContentResolver resolver = mActivity.getContentResolver();
+            Uri uri = Settings.System.getUriFor(setting);
+            resolver.registerContentObserver(uri, true,
+                    new ContentObserver(new Handler()) {
+                        @Override
+                        public void onChange(boolean selfChange) {
+                            countDownAndRemove();
+                        }
+
+                        @Override
+                        public void onChange(boolean selfChange, Uri uri, int userId) {
+                            countDownAndRemove();
+                        }
+
+                        private void countDownAndRemove() {
+                            latch.countDown();
+                            resolver.unregisterContentObserver(this);
+                        }
+                    });
+        });
+        return latch;
+    }
+
+    private void grantWriteSettingsPermission() throws IOException {
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "appops set " + mActivity.getPackageName() + " "
+                        + AppOpsManager.OPSTR_WRITE_SETTINGS + " allow");
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewFontWeightTest.java b/tests/tests/widget/src/android/widget/cts/TextViewFontWeightTest.java
new file mode 100644
index 0000000..11ed15d
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewFontWeightTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.content.Context;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test for font weight in TextView
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewFontWeightTest {
+    private TextView getTextView(int id) {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final ViewGroup container =
+                (ViewGroup) inflater.inflate(R.layout.textview_weight_test_layout, null);
+        return (TextView) container.findViewById(id);
+    }
+
+    private static class FontStyle {
+        FontStyle(int weight, boolean italic) {
+            mWeight = weight;
+            mItalic = italic;
+        }
+
+        public int getWeight() {
+            return mWeight;
+        }
+
+        public boolean isItalic() {
+            return mItalic;
+        }
+
+        private int mWeight;
+        private boolean mItalic;
+    };
+
+    private static final Map<Character, FontStyle> CHAR_FONT_MAP;
+    static {
+        // This mapping needs to be synced with res/font/fullfamily.xml
+        final HashMap<Character, FontStyle> map = new HashMap<>();
+        map.put('a', new FontStyle(100, false));
+        map.put('b', new FontStyle(100, true));
+        map.put('c', new FontStyle(200, false));
+        map.put('d', new FontStyle(200, true));
+        map.put('e', new FontStyle(300, false));
+        map.put('f', new FontStyle(300, true));
+        map.put('g', new FontStyle(400, false));
+        map.put('h', new FontStyle(400, true));
+        map.put('i', new FontStyle(500, false));
+        map.put('j', new FontStyle(500, true));
+        map.put('k', new FontStyle(600, false));
+        map.put('l', new FontStyle(600, true));
+        map.put('m', new FontStyle(700, false));
+        map.put('n', new FontStyle(700, true));
+        map.put('o', new FontStyle(800, false));
+        map.put('p', new FontStyle(800, true));
+        map.put('q', new FontStyle(900, false));
+        map.put('r', new FontStyle(900, true));
+        CHAR_FONT_MAP = Collections.unmodifiableMap(map);
+    }
+
+    private static void assertFontSelected(TextView tv, FontStyle style) {
+        // In this tests, the following special font is used for testing typeface.
+        // All fonts support 'a' to 'z' characters and all character has 1em advance except for one
+        // character. For example, 'ascii_a3em_weight100_upright.ttf' supports 'a' to 'z' characters
+        // and 'a' has 3em advance and others has 1em advance. Also, the metadata has width=100 and
+        // slant=upright information.
+        Typeface typeface = tv.getTypeface();
+        assertNotNull(typeface);
+        assertEquals(style.getWeight(), typeface.getWeight());
+        assertEquals(style.isItalic(), typeface.isItalic());
+
+        // Check if the correct underlying font is selected.
+        char threeEmChar = 0;
+        for (char c = 'a'; c <= 'z'; c++) {
+            Paint paint = new Paint();
+            paint.setTextSize(10.0f);  // Make 1em=10px
+            paint.setTypeface(typeface);
+
+            final float width = paint.measureText(new char[] { c }, 0 /* index */, 1 /* count */);
+            if (width == 30.0f) {
+                if (threeEmChar != 0) {
+                    throw new IllegalStateException(
+                        "There are multiple 3em characters. Incorrect test set up?");
+                }
+                threeEmChar = c;
+            }
+        }
+
+        FontStyle fontStyle = CHAR_FONT_MAP.get(threeEmChar);
+        assertNotNull(fontStyle);
+        assertEquals(style.getWeight(), fontStyle.getWeight());
+        assertEquals(style.isItalic(), fontStyle.isItalic());
+    }
+
+    @Test
+    public void testWeight() {
+        assertFontSelected(getTextView(R.id.textView_weight100_upright), new FontStyle(100, false));
+        assertFontSelected(getTextView(R.id.textView_weight100_italic), new FontStyle(100, true));
+        assertFontSelected(getTextView(R.id.textView_weight200_upright), new FontStyle(200, false));
+        assertFontSelected(getTextView(R.id.textView_weight200_italic), new FontStyle(200, true));
+        assertFontSelected(getTextView(R.id.textView_weight300_upright), new FontStyle(300, false));
+        assertFontSelected(getTextView(R.id.textView_weight300_italic), new FontStyle(300, true));
+        assertFontSelected(getTextView(R.id.textView_weight400_upright), new FontStyle(400, false));
+        assertFontSelected(getTextView(R.id.textView_weight400_italic), new FontStyle(400, true));
+        assertFontSelected(getTextView(R.id.textView_weight500_upright), new FontStyle(500, false));
+        assertFontSelected(getTextView(R.id.textView_weight500_italic), new FontStyle(500, true));
+        assertFontSelected(getTextView(R.id.textView_weight600_upright), new FontStyle(600, false));
+        assertFontSelected(getTextView(R.id.textView_weight600_italic), new FontStyle(600, true));
+        assertFontSelected(getTextView(R.id.textView_weight700_upright), new FontStyle(700, false));
+        assertFontSelected(getTextView(R.id.textView_weight700_italic), new FontStyle(700, true));
+        assertFontSelected(getTextView(R.id.textView_weight800_upright), new FontStyle(800, false));
+        assertFontSelected(getTextView(R.id.textView_weight800_italic), new FontStyle(800, true));
+        assertFontSelected(getTextView(R.id.textView_weight900_upright), new FontStyle(900, false));
+        assertFontSelected(getTextView(R.id.textView_weight900_italic), new FontStyle(900, true));
+    }
+
+    @Test
+    public void testTextAppearance() {
+        assertFontSelected(getTextView(R.id.textAppearance_weight100_upright),
+                new FontStyle(100, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight100_italic),
+                new FontStyle(100, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight200_upright),
+                new FontStyle(200, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight200_italic),
+                new FontStyle(200, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight300_upright),
+                new FontStyle(300, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight300_italic),
+                new FontStyle(300, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight400_upright),
+                new FontStyle(400, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight400_italic),
+                new FontStyle(400, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight500_upright),
+                new FontStyle(500, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight500_italic),
+                new FontStyle(500, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight600_upright),
+                new FontStyle(600, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight600_italic),
+                new FontStyle(600, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight700_upright),
+                new FontStyle(700, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight700_italic),
+                new FontStyle(700, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight800_upright),
+                new FontStyle(800, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight800_italic),
+                new FontStyle(800, true));
+        assertFontSelected(getTextView(R.id.textAppearance_weight900_upright),
+                new FontStyle(900, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight900_italic),
+                new FontStyle(900, true));
+    }
+
+    @Test
+    public void testStyle() {
+        assertFontSelected(getTextView(R.id.textView_normal), new FontStyle(400, false));
+        assertFontSelected(getTextView(R.id.textView_bold), new FontStyle(700, false));
+        assertFontSelected(getTextView(R.id.textView_italic), new FontStyle(400, true));
+        assertFontSelected(getTextView(R.id.textView_bold_italic), new FontStyle(700, true));
+        assertFontSelected(getTextView(R.id.textAppearance_normal), new FontStyle(400, false));
+        assertFontSelected(getTextView(R.id.textAppearance_bold), new FontStyle(700, false));
+        assertFontSelected(getTextView(R.id.textAppearance_italic), new FontStyle(400, true));
+        assertFontSelected(getTextView(R.id.textAppearance_bold_italic), new FontStyle(700, true));
+    }
+
+    @Test
+    public void testWeightStyleResolve() {
+        // If both weight and style=bold is specified, ignore the boldness and use weight.
+        assertFontSelected(getTextView(R.id.textView_weight100_bold), new FontStyle(100, false));
+        assertFontSelected(getTextView(R.id.textAppearance_weight100_bold),
+                new FontStyle(100, false));
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 367ed40..e0fe71f 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -43,8 +43,6 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.annotation.IntDef;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
@@ -57,6 +55,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
@@ -75,6 +74,8 @@
 import android.os.Parcelable;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -85,6 +86,7 @@
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -139,7 +141,6 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextSelection;
 import android.widget.EditText;
@@ -190,15 +191,9 @@
     private static final TextClassifier FAKE_TEXT_CLASSIFIER = new TextClassifier() {
         @Override
         public TextSelection suggestSelection(
-                CharSequence text, int start, int end, LocaleList locales) {
+                CharSequence text, int start, int end, TextSelection.Options options) {
             return new TextSelection.Builder(SMARTSELECT_START, SMARTSELECT_END).build();
         }
-
-        @Override
-        public TextClassification classifyText(
-                CharSequence text, int start, int end, LocaleList locales) {
-            return new TextClassification.Builder().build();
-        }
     };
     private static final int CLICK_TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 50;
 
@@ -1640,6 +1635,16 @@
         }
     }
 
+    @UiThreadTest
+    @Test
+    public void testSetText_PrecomputedText() {
+        final TextView tv = findTextView(R.id.textview_text);
+        final PrecomputedText measured = PrecomputedText.create(
+                "This is an example text.", tv.getTextMetricsParams());
+        tv.setText(measured);
+        assertEquals(measured.toString(), tv.getText().toString());
+    }
+
     @Test
     public void testSetTextUpdatesHeightAfterRemovingImageSpan() throws Throwable {
         // Height calculation had problems when TextView had width: match_parent
@@ -4381,6 +4386,149 @@
 
     @UiThreadTest
     @Test
+    public void testBaselineAttributes() {
+        mTextView = findTextView(R.id.textview_baseline);
+
+        final int firstBaselineToTopHeight = mTextView.getResources()
+                .getDimensionPixelSize(R.dimen.textview_firstBaselineToTopHeight);
+        final int lastBaselineToBottomHeight = mTextView.getResources()
+                .getDimensionPixelSize(R.dimen.textview_lastBaselineToBottomHeight);
+        final int lineHeight = mTextView.getResources()
+                .getDimensionPixelSize(R.dimen.textview_lineHeight);
+
+        assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
+        assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
+        assertEquals(lineHeight, mTextView.getLineHeight());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetFirstBaselineToTopHeight() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText("This is some random text");
+        final int padding = 100;
+        mTextView.setPadding(padding, padding, padding, padding);
+
+        final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
+        final int fontMetricsTop = Math.max(
+                Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));
+
+        int firstBaselineToTopHeight = fontMetricsTop + 10;
+        mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
+        assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
+        assertNotEquals(padding, mTextView.getPaddingTop());
+
+        firstBaselineToTopHeight = fontMetricsTop + 40;
+        mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
+        assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
+
+        mTextView.setPadding(padding, padding, padding, padding);
+        assertEquals(padding, mTextView.getPaddingTop());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetFirstBaselineToTopHeight_tooSmall() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText("This is some random text");
+        final int padding = 100;
+        mTextView.setPadding(padding, padding, padding, padding);
+
+        final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
+        final int fontMetricsTop = Math.min(
+                Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));
+
+        int firstBaselineToTopHeight = fontMetricsTop - 1;
+        mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
+        assertNotEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
+        assertEquals(padding, mTextView.getPaddingTop());
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetFirstBaselineToTopHeight_negative() {
+        new TextView(mActivity).setFirstBaselineToTopHeight(-1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetLastBaselineToBottomHeight() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText("This is some random text");
+        final int padding = 100;
+        mTextView.setPadding(padding, padding, padding, padding);
+
+        final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
+        final int fontMetricsBottom = Math.max(
+                Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));
+
+        int lastBaselineToBottomHeight = fontMetricsBottom + 20;
+        mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
+        assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
+        assertNotEquals(padding, mTextView.getPaddingBottom());
+
+        lastBaselineToBottomHeight = fontMetricsBottom + 30;
+        mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
+        assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
+
+        mTextView.setPadding(padding, padding, padding, padding);
+        assertEquals(padding, mTextView.getPaddingBottom());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetLastBaselineToBottomHeight_tooSmall() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText("This is some random text");
+        final int padding = 100;
+        mTextView.setPadding(padding, padding, padding, padding);
+
+        final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
+        final int fontMetricsBottom = Math.min(
+                Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));
+
+        int lastBaselineToBottomHeight = fontMetricsBottom - 1;
+        mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
+        assertNotEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
+        assertEquals(padding, mTextView.getPaddingBottom());
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetLastBaselineToBottomHeight_negative() {
+        new TextView(mActivity).setLastBaselineToBottomHeight(-1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetLineHeight() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText("This is some random text");
+        final float lineSpacingExtra = 50;
+        final float lineSpacingMultiplier = 0.2f;
+        mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
+
+        mTextView.setLineHeight(100);
+        assertEquals(100, mTextView.getLineHeight());
+        assertNotEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
+        assertNotEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
+
+        mTextView.setLineHeight(200);
+        assertEquals(200, mTextView.getLineHeight());
+
+        mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
+        assertEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
+        assertEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetLineHeight_negative() {
+        new TextView(mActivity).setLineHeight(-1);
+    }
+
+    @UiThreadTest
+    @Test
     public void testDeprecatedSetTextAppearance() {
         mTextView = new TextView(mActivity);
 
@@ -4445,6 +4593,75 @@
         assertEquals(null, mTextView.getTypeface());
     }
 
+    @Test
+    public void testXmlTextAppearance() {
+        mTextView = findTextView(R.id.textview_textappearance_attrs1);
+        assertEquals(22f, mTextView.getTextSize(), 0.01f);
+        Typeface italicSans = Typeface.create(Typeface.SANS_SERIF, Typeface.ITALIC);
+        assertEquals(italicSans, mTextView.getTypeface());
+        assertEquals(Typeface.ITALIC, mTextView.getTypeface().getStyle());
+        assertTrue(mTextView.isAllCaps());
+        assertEquals(2.4f, mTextView.getLetterSpacing(), 0.01f);
+        assertEquals("smcp", mTextView.getFontFeatureSettings());
+
+        mTextView = findTextView(R.id.textview_textappearance_attrs2);
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+        assertEquals(mActivity.getResources().getColor(R.drawable.red),
+                mTextView.getShadowColor());
+        assertEquals(10.3f, mTextView.getShadowDx(), 0.01f);
+        assertEquals(0.5f, mTextView.getShadowDy(), 0.01f);
+        assertEquals(3.3f, mTextView.getShadowRadius(), 0.01f);
+        assertTrue(mTextView.isElegantTextHeight());
+
+        // This TextView has both a TextAppearance and a style, so the style should override.
+        mTextView = findTextView(R.id.textview_textappearance_attrs3);
+        assertEquals(32f, mTextView.getTextSize(), 0.01f);
+        Typeface boldSerif = Typeface.create(Typeface.SERIF, Typeface.BOLD);
+        assertEquals(boldSerif, mTextView.getTypeface());
+        assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
+        assertFalse(mTextView.isAllCaps());
+        assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
+        assertEquals(mActivity.getResources().getColor(R.drawable.blue),
+                mTextView.getShadowColor());
+        assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
+        assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
+        assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
+        assertFalse(mTextView.isElegantTextHeight());
+
+        // This TextView has no TextAppearance and has a style, so the style should be applied.
+        mTextView = findTextView(R.id.textview_textappearance_attrs4);
+        assertEquals(32f, mTextView.getTextSize(), 0.01f);
+        assertEquals(boldSerif, mTextView.getTypeface());
+        assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
+        assertFalse(mTextView.isAllCaps());
+        assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
+        assertEquals(mActivity.getResources().getColor(R.drawable.blue),
+                mTextView.getShadowColor());
+        assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
+        assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
+        assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
+        assertFalse(mTextView.isElegantTextHeight());
+
+        // Note: text, link and hint colors can't be tested due to the default style overriding
+        // values b/63923542
+    }
+
+    @Test
+    public void testXmlTypefaceFontFamilyHierarchy() {
+        // This view has typeface=serif set on the view directly and a fontFamily on the appearance.
+        // In this case, the attr set directly on the view should take precedence.
+        mTextView = findTextView(R.id.textview_textappearance_attrs_serif_fontfamily);
+
+        assertEquals(Typeface.SERIF, mTextView.getTypeface());
+    }
+
+    @Test
+    public void testAttributeReading_allCapsAndPassword() {
+        // This TextView has all caps & password, therefore all caps should be ignored.
+        mTextView = findTextView(R.id.textview_textappearance_attrs_allcaps_password);
+        assertFalse(mTextView.isAllCaps());
+    }
+
     @UiThreadTest
     @Test
     public void testAccessCompoundDrawableTint() {
@@ -7743,6 +7960,92 @@
         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
     }
 
+    @UiThreadTest
+    @Test
+    public void testFallbackLineSpacing_readsFromLayoutXml() {
+        mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+        mTextView = findTextView(R.id.textview_true);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView = findTextView(R.id.textview_default);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView = findTextView(R.id.textview_false);
+        assertFalse(mTextView.isFallbackLineSpacing());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFallbackLineSpacing_set_get() {
+        mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+        mTextView = findTextView(R.id.textview_true);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView.setFallbackLineSpacing(false);
+        assertFalse(mTextView.isFallbackLineSpacing());
+
+        mTextView.setFallbackLineSpacing(true);
+        assertTrue(mTextView.isFallbackLineSpacing());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFallbackLineSpacing_readsFromStyleXml() {
+        mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+        mTextView = findTextView(R.id.textview_style_true);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView = findTextView(R.id.textview_style_default);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView = findTextView(R.id.textview_style_false);
+        assertFalse(mTextView.isFallbackLineSpacing());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFallbackLineSpacing_textAppearance_set_get() {
+        mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+        mTextView = findTextView(R.id.textview_default);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingFalse);
+        assertFalse(mTextView.isFallbackLineSpacing());
+
+        mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingTrue);
+        assertTrue(mTextView.isFallbackLineSpacing());
+
+        mTextView.setFallbackLineSpacing(false);
+        mTextView.setTextAppearance(R.style.TextAppearance);
+        assertFalse(mTextView.isFallbackLineSpacing());
+
+        mTextView.setFallbackLineSpacing(true);
+        mTextView.setTextAppearance(R.style.TextAppearance);
+        assertTrue(mTextView.isFallbackLineSpacing());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextLayoutParam() {
+        mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+        mTextView = findTextView(R.id.textview_default);
+        PrecomputedText.Params param = mTextView.getTextMetricsParams();
+
+        assertEquals(mTextView.getBreakStrategy(), param.getBreakStrategy());
+        assertEquals(mTextView.getHyphenationFrequency(), param.getHyphenationFrequency());
+
+        assertTrue(param.equals(mTextView.getTextMetricsParams()));
+
+        mTextView.setBreakStrategy(
+                mTextView.getBreakStrategy() == Layout.BREAK_STRATEGY_SIMPLE
+                ?  Layout.BREAK_STRATEGY_BALANCED : Layout.BREAK_STRATEGY_SIMPLE);
+
+        assertFalse(param.equals(mTextView.getTextMetricsParams()));
+
+        mTextView.setTextMetricsParams(param);
+        assertTrue(param.equals(mTextView.getTextMetricsParams()));
+    }
+
     private void initializeTextForSmartSelection(CharSequence text) throws Throwable {
         assertTrue(text.length() >= SMARTSELECT_END);
         mActivityRule.runOnUiThread(() -> {
diff --git a/tests/tests/widget/src/android/widget/cts/util/ListScenario.java b/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
index c4d4199..1d63c40 100644
--- a/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
+++ b/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
@@ -16,13 +16,6 @@
 
 package android.widget.cts.util;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import android.app.Activity;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -36,6 +29,13 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * Utility base class for creating various List scenarios.  Configurable by the number
  * of items, how tall each item should be (in relation to the screen height), and
@@ -390,6 +390,7 @@
             mLinearLayout.addView(mListView);
             setContentView(mLinearLayout);
         }
+        mLinearLayout.restoreDefaultFocus();
     }
 
     /**
diff --git a/tests/tests/widget/src/android/widget/cts/util/TestUtils.java b/tests/tests/widget/src/android/widget/cts/util/TestUtils.java
index afa8f7a..80e0dc1 100644
--- a/tests/tests/widget/src/android/widget/cts/util/TestUtils.java
+++ b/tests/tests/widget/src/android/widget/cts/util/TestUtils.java
@@ -19,9 +19,6 @@
 import static org.junit.Assert.assertNull;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
-import android.annotation.ColorInt;
-import android.annotation.DrawableRes;
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
@@ -30,6 +27,9 @@
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
 import android.util.Pair;
 import android.util.SparseBooleanArray;
 import android.view.View;
diff --git a/tests/tests/wrap/nowrap/Android.mk b/tests/tests/wrap/nowrap/Android.mk
index 35c0448..8fd1015 100644
--- a/tests/tests/wrap/nowrap/Android.mk
+++ b/tests/tests/wrap/nowrap/Android.mk
@@ -23,8 +23,8 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	android-support-test \
-	legacy-android-test
+	android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/nowrap/AndroidTest.xml b/tests/tests/wrap/nowrap/AndroidTest.xml
index 1ca2369..b80d4ee 100644
--- a/tests/tests/wrap/nowrap/AndroidTest.xml
+++ b/tests/tests/wrap/nowrap/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS No Wrap test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/wrap/wrap_debug/Android.mk b/tests/tests/wrap/wrap_debug/Android.mk
index 77b6d27..c67e191 100644
--- a/tests/tests/wrap/wrap_debug/Android.mk
+++ b/tests/tests/wrap/wrap_debug/Android.mk
@@ -23,8 +23,8 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	android-support-test \
-	legacy-android-test
+	android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/wrap_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug/AndroidTest.xml
index b3468bf..2b9cbe1 100644
--- a/tests/tests/wrap/wrap_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Debug Wrap test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
index bc6240d..b768dcf 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
@@ -23,10 +23,10 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	android-support-test \
-	legacy-android-test
+	android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 LOCAL_SDK_VERSION := current
 
 LOCAL_PREBUILT_JNI_LIBS_arm := wrap.sh
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
index 0c740e4..2faff68 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
@@ -14,7 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Debug Wrap (Malloc Debug) test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
+    <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="CtsWrapWrapDebugMallocDebugTestCases.apk" />
diff --git a/tests/tests/wrap/wrap_nodebug/Android.mk b/tests/tests/wrap/wrap_nodebug/Android.mk
index 1d6e9c0..3317f3c 100644
--- a/tests/tests/wrap/wrap_nodebug/Android.mk
+++ b/tests/tests/wrap/wrap_nodebug/Android.mk
@@ -23,8 +23,8 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	compatibility-device-util \
-	android-support-test \
-	legacy-android-test
+	android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/wrap_nodebug/AndroidTest.xml b/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
index b7a6a11..dc547cf 100644
--- a/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Wrap test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tvprovider/Android.mk b/tests/tvprovider/Android.mk
index 4a13909..5595519 100644
--- a/tests/tvprovider/Android.mk
+++ b/tests/tvprovider/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsTvProviderTestCases
diff --git a/tests/tvprovider/AndroidTest.xml b/tests/tvprovider/AndroidTest.xml
index 9365cbd..c62cec8 100644
--- a/tests/tvprovider/AndroidTest.xml
+++ b/tests/tvprovider/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS TV Provider test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="tv" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/ui/Android.mk b/tests/ui/Android.mk
index 53ae77e..abdc148 100644
--- a/tests/ui/Android.mk
+++ b/tests/ui/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsUiDeviceTestCases
diff --git a/tests/video/Android.mk b/tests/video/Android.mk
index c70cfaf..b077e63 100644
--- a/tests/video/Android.mk
+++ b/tests/video/Android.mk
@@ -25,6 +25,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil compatibility-device-util ctstestrunner
 
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libnativehelper_compat_libc++
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/video/AndroidTest.xml b/tests/video/AndroidTest.xml
index e30a394..3836f98 100644
--- a/tests/video/AndroidTest.xml
+++ b/tests/video/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Video test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/vm/Android.mk b/tests/vm/Android.mk
index d28ec0f..8391815 100755
--- a/tests/vm/Android.mk
+++ b/tests/vm/Android.mk
@@ -36,4 +36,4 @@
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/vm/AndroidTest.xml b/tests/vm/AndroidTest.xml
index c6f2058..4035349 100644
--- a/tests/vm/AndroidTest.xml
+++ b/tests/vm/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS VM test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <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 74aebf1..56a7310 100644
--- a/tests/vr/Android.mk
+++ b/tests/vr/Android.mk
@@ -27,7 +27,9 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsvrextensions_jni libnativehelper_compat_libc++
 
diff --git a/tests/vr/src/android/vr/cts/VrFeaturesTest.java b/tests/vr/src/android/vr/cts/VrFeaturesTest.java
new file mode 100644
index 0000000..17fe23a
--- /dev/null
+++ b/tests/vr/src/android/vr/cts/VrFeaturesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.vr.cts;
+
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.test.ActivityInstrumentationTestCase2;
+
+public class VrFeaturesTest extends ActivityInstrumentationTestCase2<CtsActivity> {
+    private CtsActivity mActivity;
+
+    public VrFeaturesTest() {
+        super(CtsActivity.class);
+    }
+
+    public void testLacksDeprecatedVrModeFeature() {
+        mActivity = getActivity();
+        boolean hasVrMode = mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE);
+        boolean hasVrModeHighPerf = mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
+        if (hasVrMode || hasVrModeHighPerf) {
+            assertTrue("FEATURE_VR_MODE and FEATURE_VR_MODE_HIGH_PERFORMANCE must be used together",
+                    hasVrMode && hasVrModeHighPerf);
+        }
+    }
+}
diff --git a/tools/cts-api-coverage/Android.mk b/tools/cts-api-coverage/Android.mk
index c66f7d4..8e5e0ee 100644
--- a/tools/cts-api-coverage/Android.mk
+++ b/tools/cts-api-coverage/Android.mk
@@ -14,23 +14,52 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# the hat script
+# the cts-api-coverage script
 # ============================================================
 include $(CLEAR_VARS)
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE := cts-api-coverage
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-common-util-devicesidelib
 LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX)
 
 include $(BUILD_PREBUILT)
 
-# the other stuff
+# the ndk-api-report script
 # ============================================================
-subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
-		src \
-	))
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := ndk-api-report
+LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
 
-include $(subdirs)
+include $(BUILD_PREBUILT)
+
+# cts-api-coverage java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-subdir-java-files) \
+    $(call all-proto-files-under, proto)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  compatibility-host-util \
+  dexlib2
+
+LOCAL_MODULE := cts-api-coverage
+
+# 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.
+LOCAL_STATIC_JAVA_LIBRARIES += \
+  tradefed hosttestlib \
+  platformprotos
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/MANIFEST.mf
similarity index 100%
rename from tools/cts-api-coverage/src/MANIFEST.mf
rename to tools/cts-api-coverage/MANIFEST.mf
diff --git a/tools/cts-api-coverage/proto/cts_report.proto b/tools/cts-api-coverage/proto/cts_report.proto
new file mode 100644
index 0000000..afb4338
--- /dev/null
+++ b/tools/cts-api-coverage/proto/cts_report.proto
@@ -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.
+
+// Contains proto definition for storing CTS reports.
+
+syntax = "proto2";
+
+package com.android.cts.apicoverage;
+option java_package = "com.android.cts.apicoverage";
+option java_outer_classname = "CtsReportProto";
+
+// from common_report.proto
+// Information about the build on the phone.  All the fields
+// correspond to values from android.os.Build.
+// Next Id: 20
+message BuildInfo {
+  optional string board = 1;
+  optional string brand = 2;
+  optional string device = 3;
+  optional string display = 4;
+  optional string fingerprint = 5;
+  optional string id = 6;
+  optional string model = 7;
+  optional string product = 8;
+  message Version {
+    optional string release = 1;
+    optional string sdk = 2;
+  }
+  optional Version version = 9;
+  optional string manufacturer = 10;
+  // This field is deprecated in android.os.Build. Use supported_abi instead.
+  optional string abi = 11 [deprecated = true];
+  // This field is deprecated in android.os.Build. Use supported_abi instead.
+  optional string abi2 = 12 [deprecated = true];
+  repeated string supported_abi = 13;
+  repeated string supported_32_bit_abi = 14;
+  repeated string supported_64_bit_abi = 15;
+  // Build.BASE_OS The base OS build the product is based on. See b/23003940
+  optional string base_os = 16;
+  // Build.SECURITY_PATCH The user-visible security patch level. See b/23003940
+  optional string security_patch = 17;
+  // A build fingerprint of the reference device. See go/apfe-reference-build
+  optional string reference_build_fingerprint = 18;
+  // RO Property set for the build.
+  map<string, string> ro_property_map = 19;
+}
+
+// Summary count of the test results.
+message Summary {
+  optional int32 failed = 1;
+  optional int32 not_executed = 2;
+  optional int32 pass = 3;
+  optional int32 timeout = 4;
+  optional int32 warning = 5;
+}
+
+// Information about the device's memory configuration
+message MemoryInfo {
+    // ActivityManager.isLowRamDevice
+    optional bool is_low_ram_device = 1;
+
+    // ActivityManager.getMemoryClass()
+    optional int32 memory_class = 2;
+
+    // ActivityManager.getLargeMemoryClass()
+    optional int32 large_memory_class = 3;
+
+    // MemoryInfo.totalMem
+    optional int64 total_memory = 4;
+}
+
+message CpuInfo {
+  // Runtime.availableProcessors
+  optional int32 available_processors = 1;
+}
+// from common_report.proto ends
+
+// Logical screen density
+// The numbers are in dpi and should match android.util.DisplayMetrics.DENSITY_*
+enum LogicalDensity {
+  LDPI = 120;
+  MDPI = 160;
+  TVDPI = 213;
+  HDPI = 240;
+  DENSITY_260 = 260;
+  DENSITY_280 = 280;
+  DENSITY_300 = 300;
+  XHDPI = 320;
+  DENSITY_340 = 340;
+  DENSITY_360 = 360;
+  // Intermediate density for screens that sit somewhere between DENSITY_XHIGH (320 dpi) and
+  // DENSITY_XXHIGH (480 dpi).
+  DENSITY_400 = 400;
+  DENSITY_420 = 420;
+  XXHDPI = 480;
+  // Intermediate density for screens that sit somewhere between DENSITY_XXHIGH (480 dpi) and
+  // DENSITY_XXXHIGH (640 dpi).
+  DENSITY_560 = 560;
+  XXXHDPI = 640;
+}
+
+// Logical screen size
+// The numbers should match
+// android.content.res.Configuration.SCREENLAYOUT_SIZE_*
+enum LogicalSize {
+  UNDEFINED = 0;
+  SMALL = 1;
+  NORMAL = 2;
+  LARGE = 3;
+  XLARGE = 4;
+}
+
+// Result type of PTS tests defined in:
+// cts/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
+enum ResultType {
+  LOWER_BETTER = 0;
+  HIGHER_BETTER = 1;
+  NEUTRAL = 2;
+  WARNING = 3;
+}
+
+// Result unit of PTS values defined in:
+// cts/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
+enum ResultUnit {
+  NONE = 0;
+  MS = 1;
+  FPS = 2;
+  OPS = 3;
+  KBPS = 4;
+  MBPS = 5;
+  BYTE = 6;
+  COUNT = 7;
+  SCORE = 8;
+}
+
+// A CtsReport message encapsulates the output of a Compatibility Test Suite
+// (CTS) run.
+message CtsReport {
+
+  // Test plan that was run, generally "CTS".
+  optional string test_plan = 1;
+
+  // Version of the CTS tool.
+  optional string version = 2;
+
+  optional int64 start_time = 3;
+  optional int64 end_time = 4;
+
+  // Fields describing the particular device under test.
+  // Next Id: 32
+  message DeviceInfo {
+
+    optional string screen_resolution = 1;
+    optional LogicalDensity logical_screen_density = 17;
+    optional LogicalSize logical_screen_size = 18;
+
+    optional string subscriber_id = 2 [deprecated = true];
+    optional string type = 3 [deprecated = true];
+    optional string device_id = 4 [deprecated = true];
+    optional string imei = 5 [deprecated = true];
+    optional string imsi = 6 [deprecated = true];
+    optional string keypad = 7;
+    repeated string locale = 8;
+    optional string navigation = 9;
+    optional string network = 10 [deprecated = true];
+    optional string touch = 11;
+    optional float x_dpi = 12;
+    optional float y_dpi = 13;
+    optional string opengl_es_version = 19;
+
+    // Use BuildInfo.supported_abi instead
+    optional string build_abi = 20 [deprecated = true];
+    // Use BuildInfo.supported_abi instead
+    optional string build_abi2 = 21 [deprecated = true];
+
+    optional BuildInfo build_info = 14;
+    optional MemoryInfo memory_info = 29;
+    optional CpuInfo cpu_info = 30;
+
+    // Filesystem partitions.
+    optional string partitions = 22;
+
+    repeated string system_library = 23;
+    // Deprecated. These values are found in the extension list
+    repeated string opengl_texture_format = 24 [deprecated = true];
+    // GLES20.GL_EXTENSIONS,  GL10.GL_EXTENSIONS or GLES30.GL_EXTENSIONS
+    repeated string opengl_extension = 25;
+    // gl.glGetString(GL10.GL_VENDOR) or GLES20.glGetString(GLES20.GL_VENDOR)
+    // or GLES30.glGetString(GLES30.GL_VENDOR)
+    optional string opengl_vendor = 26;
+    // gl.glGetString(GL10.GL_RENDERER)
+    // or GLES20.glGetString(GLES20.GL_RENDERER)
+    // or GLES30.glGetString(GLES30.GL_RENDERER)
+    optional string opengl_renderer = 27;
+
+    // Hardware features that may be available on the device.
+    // This includes features such as camera, gps and compass.
+    message Feature {
+      optional string name = 1;
+      optional string type = 2;
+      optional bool available = 3;
+      optional int32 version = 4;
+    }
+    repeated Feature feature = 15;
+
+    // Running processes.
+    message Process {
+      optional string name = 1;
+      optional int32 uid = 2;
+    }
+    repeated Process process = 16;
+
+    // Configuration.smallestScreenWidthDp
+    optional int32 smallest_screen_width_dp = 28;
+
+    // The value reported from UserManager.getMaxSupportedUsers
+    optional int32 max_supported_users = 31;
+  }
+  optional DeviceInfo device_info = 5;
+
+  // Information about the host running the test suite.
+  message HostInfo {
+
+    // Hostname of the machine running the tests.
+    optional string hostname = 1;
+
+    // Operating system running on the host.
+    message Os {
+      optional string arch = 1;
+      optional string name = 2;
+      optional string version = 3;
+    }
+    optional Os os = 2;
+
+    // Information about the JRE used to run the tests.
+    message JavaEnv {
+      optional string name = 1;
+      optional string version = 2;
+    }
+    optional JavaEnv java_env = 3;
+
+    // CTS version and parameters during runtime.
+    message Cts {
+      optional string version = 1;
+
+      message Parameter {
+        optional string name = 1;
+        optional int32 value = 2;
+      }
+      repeated Parameter parameter = 2;
+    }
+    optional Cts cts = 4;
+  }
+  optional HostInfo host_info = 6;
+
+  optional Summary summary = 7;
+
+  // Group of test suites within a specific java package.
+  message TestPackage {
+
+    // Java package name.
+    optional string deprecated_app_package_name = 1 [deprecated = true];
+
+    // Unique name describing the test package within the java package.
+    optional string name = 2;
+    optional string deprecated_digest = 3 [deprecated = true];
+    optional bool deprecated_signature_check = 4 [deprecated = true];
+
+    // Group of test cases.
+    message TestSuite {
+
+      // Unique name describing the test suite within the test package.
+      optional string name = 1;
+
+      // Group of individual tests.
+      message TestCase {
+
+        // Unique name describing the test case within the test suite.
+        optional string name = 1;
+        optional string priority = 2;
+
+        // Smallest test unit, which ideally tests only one feature or function.
+        message Test {
+
+          // Unique name describing the test within the test case.
+          optional string name = 1;
+
+          // Result of the test run.
+          optional string result = 2;
+
+          // Bug id for known issues.
+          optional string deprecated_known_failure = 3 [deprecated = true];
+
+          // Time this test was started.
+          optional int64 deprecated_start_time = 4 [deprecated = true];
+
+          // Time this test completed.
+          optional int64 deprecated_end_time = 5 [deprecated = true];
+
+          // Captures an exception thrown during the test.
+          message FailedScene {
+            optional string exception_message = 1;
+            optional string stack_trace = 2;
+          }
+          repeated FailedScene failed_scene = 6;
+
+          // Summary of the PTS test.
+          message Summary {
+            optional string message = 1;
+            optional ResultType score_type = 2;
+            optional ResultUnit unit = 3;
+            optional double value = 4;
+          }
+          optional Summary summary = 7;
+
+          // Details of the PTS test.
+          message Details {
+
+            // Set of values captured when running the PTS test.
+            message ValueArray {
+              optional string source = 1;
+              optional string message = 2;
+              optional ResultType score_type = 3;
+              optional ResultUnit unit = 4;
+              repeated double value = 5;
+            }
+            repeated ValueArray value_array = 1;
+          }
+          optional Details details = 8;
+        }
+        repeated Test test = 3;
+      }
+      repeated TestCase test_case = 2;
+    }
+    repeated TestSuite test_suite = 5;
+
+    // abi specifies the ABI the test ran under like "armeabi".
+    optional string abi = 6;
+  }
+  repeated TestPackage test_package = 8;
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/proto/testsuite.proto b/tools/cts-api-coverage/proto/testsuite.proto
new file mode 100644
index 0000000..d7c64de
--- /dev/null
+++ b/tools/cts-api-coverage/proto/testsuite.proto
@@ -0,0 +1,175 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_apicoverage;
+// [END declaration]
+
+// [START java_declaration]
+option java_package = "com.android.cts.apicoverage";
+option java_outer_classname = "TestSuiteProto";
+// [END java_declaration]
+
+// [START messages]
+message Option {
+    string name = 1;
+    string key = 2;
+    string value =3;
+}
+
+message ConfigMetadata {
+    string module_name = 1;
+    string component = 2;
+    repeated Option options = 3;
+
+    message TargetPreparer {
+        string test_class = 1;
+        repeated Option options = 2;
+    }
+    repeated TargetPreparer target_preparers = 4;
+
+    message TestClass {
+        string test_class = 1;
+        string package = 2;
+        repeated Option options = 3;
+    }
+    repeated TestClass test_classes = 5;
+}
+
+message Annotation {
+    int32 visibility = 1;
+    string type = 2;
+
+    message Element {
+        string name = 1;
+        string value = 2;
+    }
+    repeated Element elements = 3;
+}
+
+message TestSuite {
+    string name = 1;
+
+    message Package {
+        string name = 1;
+
+        enum Type {
+            ANDROIDJUNIT = 0;
+            JAVAHOST = 1;
+            GTEST = 2;
+            DEQP = 3;
+            LIBCORE = 4;
+            DALVIK = 5;
+        }
+        Type type = 2;
+
+        message Class {
+            string name = 1;
+            string type = 2;
+            string super_class = 3;
+            string interface = 4;
+
+            enum ClassType {
+                UNKNOWN = 0;
+                JUNIT3 = 1;
+                JUNIT4 = 2;
+                PARAMETERIZED = 3;
+                JAVAHOST = 4;
+            }
+
+            ClassType class_type = 5;
+            repeated Annotation annotations = 6;
+
+            message Method {
+                string defining_class = 1;
+                string name = 2;
+                string parameters = 3;
+                string return_type = 4;
+                int32 access_flags = 5;
+                repeated Annotation annotations = 6;
+            }
+            repeated Method methods = 7;
+
+            message Field {
+                string defining_class = 1;
+                string name = 2;
+                string type = 3;
+                int32 access_flags = 4;
+                string initial_value = 5;
+                repeated Annotation annotations = 6;
+            }
+            repeated Field fields = 8;
+            string apk = 9;
+        }
+        repeated Class classes = 3;
+        string op_codes = 4;
+    }
+    repeated Package packages = 2;
+}
+
+// target File Metadata for e.g. config, apk, jar, exe, so
+message FileMetadata {
+    string description = 1;
+    ConfigMetadata config_metadata = 2;
+}
+
+// An entry in a Test Suire Release messages: cts, etc.
+message Entry {
+    // Entry ID
+    string id = 1;
+    // Name
+    string name = 2;
+
+    enum EntryType {
+        FOLDER = 0;
+        FILE = 1;
+        CONFIG = 2;
+        JAR = 3;
+        APK = 4;
+        EXE = 5;
+        SO = 6;
+    }
+
+    // Type
+    EntryType type = 3;
+    // Size
+    int64 size = 4;
+    // Content ID
+    string content_id = 5;
+    // Parent entry ID
+    string parent_id = 6;
+    // Relative path
+    string relative_path = 7;
+
+    FileMetadata file_metadata = 8;
+}
+
+// Test Suite Release: cts, etc.
+message TestSuiteContent {
+    // Entry ID
+    string id = 1;
+    // Name
+    string name = 2;
+    // Version
+    string version = 3;
+    // Build ID
+    string bid = 4;
+    // Content ID
+    string content_id = 5;
+    // File Entries
+    repeated Entry file_entries = 6;
+    repeated string known_failures = 7;
+}
+// [END messages]
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/res/api-coverage.xsl
similarity index 100%
rename from tools/cts-api-coverage/src/res/api-coverage.xsl
rename to tools/cts-api-coverage/res/api-coverage.xsl
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
deleted file mode 100644
index fcf7ad4..0000000
--- a/tools/cts-api-coverage/src/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-
-# cts-api-coverage java library
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_JAVA_RESOURCE_DIRS := res 
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-  compatibility-host-util \
-  dexlib2
-
-LOCAL_MODULE := cts-api-coverage
-
-# 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.
-LOCAL_STATIC_JAVA_LIBRARIES += \
-        platformprotos
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ComparisonReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ComparisonReport.java
new file mode 100644
index 0000000..12b9de6
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ComparisonReport.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+import com.android.cts.apicoverage.TestSuiteProto.*;
+import com.android.cts.apicoverage.CtsReportProto.*;
+
+import org.xml.sax.SAXException;
+
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in a
+ * browser when used with the api-coverage.css and api-coverage.xsl files.
+ */
+class ComparisonReport {
+    public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
+    // [module].[class]#[method]
+    public static final String TESTCASE_NAME_FORMAT = "%s.%s#%s";
+
+    private static void printUsage() {
+        System.out.println("Usage: ReportChecker [OPTION]...");
+        System.out.println();
+        System.out.println("Generates a comparsion report about Test Suite Content and Report.");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -o FILE                output file of missing test case list");
+        System.out.println("  -e FILE                output file of extra test case list ");
+        System.out.println("  -r FILE                test report PB file");
+        System.out.println("  -t FILE                test suite content PB file");
+
+        System.out.println();
+        System.exit(1);
+    }
+
+    private static boolean isKnownFailure(String tsName, List<String> knownFailures) {
+        for (String kf : knownFailures) {
+            if (tsName.startsWith(kf)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void writeComparsionReport(
+            TestSuite ts,
+            CtsReport tsReport,
+            String outputFileName,
+            String outputExtraTestFileName,
+            List<String> knownFailures,
+            String extraTestSubPlanFileName)
+            throws IOException {
+
+        HashMap<String, String> testCaseMap = new HashMap<String, String>();
+        HashMap<String, String> extraTestCaseMap = new HashMap<String, String>();
+        // Add all test case into a HashMap
+        for (TestSuite.Package pkg : ts.getPackagesList()) {
+            for (TestSuite.Package.Class cls : pkg.getClassesList()) {
+                for (TestSuite.Package.Class.Method mtd : cls.getMethodsList()) {
+                    String testCaseName =
+                            String.format(
+                                    TESTCASE_NAME_FORMAT,
+                                    pkg.getName(),
+                                    cls.getName(),
+                                    mtd.getName());
+                    // Filter out known failures
+                    if (!isKnownFailure(testCaseName, knownFailures)) {
+                        String testApkType = String.format("%s,%s", cls.getApk(), pkg.getType());
+                        testCaseMap.put(testCaseName, testApkType);
+                        extraTestCaseMap.put(testCaseName, testApkType);
+                        System.out.printf("add: %s: %s\n", testCaseName, testApkType);
+                    } else {
+                        System.out.printf("KF: %s\n", testCaseName);
+                    }
+                }
+            }
+        }
+        System.out.printf(
+                "Before: testCaseMap entries: %d & extraTestCaseMap: %d\n",
+                testCaseMap.size(), extraTestCaseMap.size());
+
+        //List missing Test Cases
+        FileWriter fWriter = new FileWriter(outputFileName);
+        PrintWriter pWriter = new PrintWriter(fWriter);
+
+        pWriter.printf("no,Module,TestCase,abi\n");
+        int i = 1;
+        for (CtsReport.TestPackage tPkg : tsReport.getTestPackageList()) {
+            for (CtsReport.TestPackage.TestSuite module : tPkg.getTestSuiteList()) {
+                for (CtsReport.TestPackage.TestSuite.TestCase testCase : module.getTestCaseList()) {
+                    for (CtsReport.TestPackage.TestSuite.TestCase.Test test :
+                            testCase.getTestList()) {
+                        String testCaseName =
+                                String.format(
+                                        TESTCASE_NAME_FORMAT,
+                                        tPkg.getName(),
+                                        testCase.getName(),
+                                        test.getName());
+
+                        System.out.printf(
+                                "rm: %s, %s\n",
+                                testCaseName, extraTestCaseMap.remove(testCaseName));
+                        if (!testCaseMap.containsKey(testCaseName)) {
+                            pWriter.printf(
+                                    "%d,%s,%s,%s\n",
+                                    i++, module.getName(), testCaseName, tPkg.getAbi());
+                        }
+                    }
+                }
+            }
+        }
+        pWriter.close();
+
+        //List extra Test Cases
+        fWriter = new FileWriter(outputExtraTestFileName);
+        pWriter = new PrintWriter(fWriter);
+        pWriter.printf("no,TestCase,Apk,Type\n");
+        int j = 1;
+        for (Map.Entry<String, String> test : extraTestCaseMap.entrySet()) {
+            pWriter.printf("%d,%s,%s\n", j++, test.getKey(), test.getValue());
+        }
+        pWriter.close();
+        System.out.printf(
+                "After: testCaseMap entries: %d & extraTestCaseMap: %d\n",
+                testCaseMap.size(), extraTestCaseMap.size());
+                
+        if (null != extraTestSubPlanFileName) {
+            //Create extra Test Cases subplan
+            fWriter = new FileWriter(extraTestSubPlanFileName);
+            pWriter = new PrintWriter(fWriter);
+            pWriter.println("<?xml version=\'1.0\' encoding=\'UTF-8\' standalone=\'no\' ?>");
+            pWriter.println("<SubPlan version=\'2.0\'>");
+            for (Map.Entry<String, String> test : extraTestCaseMap.entrySet()) {
+                pWriter.printf(
+                        "  <Entry include=\"%s\"/>\n", test.getKey().replaceFirst("\\.", " "));
+            }
+            pWriter.println("</SubPlan>");
+            pWriter.close();
+        }
+
+    }
+
+    public static void main(String[] args) throws IOException, SAXException, Exception {
+        String outputFileName = "./missingTestCase.csv";
+        String testReportFileName = "./extraTestCase.csv";
+        String testSuiteFileName = null;
+        String testSuiteContentFileName = null;
+        String outputExtraTestFileName = null;
+        String extraTestSubPlanFileName = null;
+        int numTestModule = 0;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    outputFileName = getExpectedArg(args, ++i);
+                } else if ("-r".equals(args[i])) {
+                    testReportFileName = getExpectedArg(args, ++i);
+                } else if ("-e".equals(args[i])) {
+                    outputExtraTestFileName = getExpectedArg(args, ++i);
+                } else if ("-t".equals(args[i])) {
+                    testSuiteFileName = getExpectedArg(args, ++i);
+                } else if ("-s".equals(args[i])) {
+                    testSuiteContentFileName = getExpectedArg(args, ++i);
+                } else if ("-p".equals(args[i])) {
+                    extraTestSubPlanFileName = getExpectedArg(args, ++i);
+                } else {
+                    printUsage();
+                }
+            } else {
+                printUsage();
+            }
+        }
+
+        if (null == testReportFileName || null == testSuiteContentFileName) {
+            printUsage();
+        }
+
+        TestSuiteContent tsContent =
+                TestSuiteContent.parseFrom(new FileInputStream(testSuiteContentFileName));
+        TestSuite ts = TestSuite.parseFrom(new FileInputStream(testSuiteFileName));
+        CtsReport tsReport = CtsReport.parseFrom(new FileInputStream(testReportFileName));
+
+        writeComparsionReport(
+                ts,
+                tsReport,
+                outputFileName,
+                outputExtraTestFileName,
+                tsContent.getKnownFailuresList(),
+                extraTestSubPlanFileName);
+        System.err.printf("%s", tsContent.getKnownFailuresList());
+    }
+
+    /** 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)
+        }
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportHandler.java
new file mode 100644
index 0000000..d9f1156
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportHandler.java
@@ -0,0 +1,98 @@
+/*
+ * 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.apicoverage;
+
+import com.android.cts.apicoverage.CtsReportProto.*;
+
+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 CtsReportHandler extends DefaultHandler {
+    private static final String MODULE_TAG = "Module";
+    private static final String TEST_CASE_TAG = "TestCase";
+    private static final String TEST_TAG = "Test";
+    private static final String NAME_TAG = "name";
+    private static final String RESULT_TAG = "result";
+    private static final String ABI_TAG = "abi";
+    private static final String DONE_TAG = "done";
+    private static final String PASS_TAG = "pass";
+    private static final String FAILED_TAG = "failed";
+    private static final String RUNTIME_TAG = "runtime";
+
+    private CtsReport.Builder mCtsReportBld;
+    private CtsReport.TestPackage.Builder mTestPackageBld;
+    private CtsReport.TestPackage.TestSuite.Builder mTestSuiteBld;
+    private CtsReport.TestPackage.TestSuite.TestCase.Builder mTestCaseBld;
+
+    CtsReportHandler(String configFileName) {
+        mCtsReportBld = CtsReport.newBuilder();
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String name, Attributes attributes)
+            throws SAXException {
+        super.startElement(uri, localName, name, attributes);
+
+        try {
+            if (MODULE_TAG.equalsIgnoreCase(localName)) {
+                mTestPackageBld = CtsReport.TestPackage.newBuilder();
+                mTestSuiteBld = CtsReport.TestPackage.TestSuite.newBuilder();
+                mTestSuiteBld.setName(attributes.getValue(NAME_TAG));
+                mTestPackageBld.setName(attributes.getValue(NAME_TAG));
+                // ABI is option fro a Report
+                if (null != attributes.getValue(NAME_TAG)) {
+                    mTestPackageBld.setAbi(attributes.getValue(ABI_TAG));
+                }
+            } else if (TEST_CASE_TAG.equalsIgnoreCase(localName)) {
+                mTestCaseBld = CtsReport.TestPackage.TestSuite.TestCase.newBuilder();
+                mTestCaseBld.setName(attributes.getValue(NAME_TAG));
+            } else if (TEST_TAG.equalsIgnoreCase(localName)) {
+                CtsReport.TestPackage.TestSuite.TestCase.Test.Builder testBld;
+                testBld = CtsReport.TestPackage.TestSuite.TestCase.Test.newBuilder();
+                testBld.setName(attributes.getValue(NAME_TAG));
+                testBld.setResult(attributes.getValue(RESULT_TAG));
+                mTestCaseBld.addTest(testBld);
+            }
+        } catch (Exception ex) {
+            System.err.println(localName + " " + name);
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String name) throws SAXException {
+        super.endElement(uri, localName, name);
+
+        if (MODULE_TAG.equalsIgnoreCase(localName)) {
+            mTestPackageBld.addTestSuite(mTestSuiteBld);
+            mCtsReportBld.addTestPackage(mTestPackageBld);
+            mTestPackageBld = null;
+            mTestSuiteBld = null;
+        } else if (TEST_CASE_TAG.equalsIgnoreCase(localName)) {
+            mTestSuiteBld.addTestCase(mTestCaseBld);
+            mTestCaseBld = null;
+        }
+    }
+
+    public CtsReport getCtsReport() {
+        return mCtsReportBld.build();
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportParser.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportParser.java
new file mode 100644
index 0000000..e5397f0
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportParser.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+import com.android.cts.apicoverage.CtsReportProto.*;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+/**
+ * Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in a
+ * browser when used with the api-coverage.css and api-coverage.xsl files.
+ */
+class CtsReportParser {
+    public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
+
+    private static void printUsage() {
+        System.out.println("Usage: CtsReportParser [OPTION]... [APK]...");
+        System.out.println();
+        System.out.println("Generates a report about what Android NDK methods.");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -o FILE                output file or standard out if not given");
+        System.out.println("  -i PATH                path to the Test_Result.xml");
+        System.out.println("  -s FILE                summary output file");
+
+        System.out.println();
+        System.exit(1);
+    }
+
+    private static CtsReport parseCtsReport(String testResultPath)
+            throws Exception {
+        XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+        CtsReportHandler ctsReportXmlHandler =
+                new CtsReportHandler(testResultPath);
+        xmlReader.setContentHandler(ctsReportXmlHandler);
+        FileReader fileReader = null;
+        try {
+            fileReader = new FileReader(testResultPath);
+            xmlReader.parse(new InputSource(fileReader));
+        } finally {
+            if (fileReader != null) {
+                fileReader.close();
+            }
+        }
+        return ctsReportXmlHandler.getCtsReport();
+    }
+
+    private static void printCtsReport(CtsReport ctsReport) {
+        //Header
+        System.out.println("Module,Class,Test,no,Abi,Result");
+        int i = 1;
+        for (CtsReport.TestPackage tPkg : ctsReport.getTestPackageList()) {
+            for (CtsReport.TestPackage.TestSuite tSuite : tPkg.getTestSuiteList()) {
+                for (CtsReport.TestPackage.TestSuite.TestCase testCase : tSuite.getTestCaseList()) {
+                    for (CtsReport.TestPackage.TestSuite.TestCase.Test test : testCase.getTestList()) {
+                        System.out.printf(
+                                "%s,%s,%s,%d,%s,%s\n",
+                                tPkg.getName(),
+                                testCase.getName(),
+                                test.getName(),
+                                i++,
+                                tPkg.getAbi(),
+                                test.getResult());
+                    }
+                }
+            }
+        }
+    }
+
+    static void printCtsReportSummary(CtsReport ctsReport, String fName)
+        throws IOException{
+
+        FileWriter fWriter = new FileWriter(fName);
+        PrintWriter pWriter = new PrintWriter(fWriter);
+
+        //Header
+        pWriter.print("Module,Test#,no,abi\n");
+
+        int moduleCnt = 0;
+        for (CtsReport.TestPackage tPkg : ctsReport.getTestPackageList()) {
+            int testCaseCnt = 0;
+            for (CtsReport.TestPackage.TestSuite tSuite : tPkg.getTestSuiteList()) {
+                for (CtsReport.TestPackage.TestSuite.TestCase testCase : tSuite.getTestCaseList()) {
+                    for (CtsReport.TestPackage.TestSuite.TestCase.Test test : testCase.getTestList()) {
+                        testCaseCnt++;
+                    }
+                }
+            }
+            pWriter.printf(
+                    "%s,%d,%d,%s\n", tPkg.getName(), testCaseCnt, moduleCnt++, tPkg.getAbi());
+        }
+
+        pWriter.close();
+    }
+
+    public static void main(String[] args)
+            throws IOException, SAXException, Exception {
+        String testResultPath = "./test_result.xml";
+        String outputFileName = "./test_result.pb";
+        String outputSummaryFilePath = "./reportSummary.csv";
+        int numTestModule = 0;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    outputFileName = getExpectedArg(args, ++i);
+                } else if ("-i".equals(args[i])) {
+                    testResultPath = getExpectedArg(args, ++i);
+                } else if ("-s".equals(args[i])) {
+                    outputSummaryFilePath = getExpectedArg(args, ++i);
+                } else {
+                    printUsage();
+                }
+            } else {
+                printUsage();
+            }
+        }
+
+        // Read message from the file and print them out
+        CtsReport ctsReport = parseCtsReport(testResultPath);
+
+        printCtsReport(ctsReport);
+        printCtsReportSummary(ctsReport, outputSummaryFilePath);
+
+
+        // Write test case list message to disk.
+        FileOutputStream output = new FileOutputStream(outputFileName);
+        try {
+          ctsReport.writeTo(output);
+        } finally {
+          output.close();
+        }
+    }
+
+    /** 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)
+        }
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
index a1a6923..3c88938 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
@@ -128,7 +128,7 @@
 
             for (File testConfigFile : testConfigFiles) {
                 XMLReader xmlReader = XMLReaderFactory.createXMLReader();
-                TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler();
+                TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler(file.getName());
                 xmlReader.setContentHandler(testModuleXmlHandler);
                 FileReader fileReader = null;
 
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestCaseReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestCaseReport.java
new file mode 100644
index 0000000..7dea68e
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestCaseReport.java
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+
+import com.android.cts.apicoverage.TestSuiteProto.*;
+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.junit.runners.Suite.SuiteClasses;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Modifier;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+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.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+class TestCaseReport {
+    // 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 PARAMETERS_TAG =
+            "Lorg/junit/runners/Parameterized$Parameters";
+    private static final String ANDROID_JUNIT4_TEST_TAG =
+            "AndroidJUnit4.class";
+    private static final String PARAMETERIZED_TAG =
+            "Parameterized.class";
+
+    // 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 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 static void printUsage() {
+        System.out.println("Usage: test-suite-content-report [OPTION]...");
+        System.out.println();
+        System.out.println("Generates test test list protocal buffer message.");
+        System.out.println();
+        System.out.println(
+                "$ANDROID_HOST_OUT/bin/test-suite-content-report "
+                        + "-i out/host/linux-x86/cts/android-cts/testcases "
+                        + "-c ./cts-content.pb"
+                        + "-o ./cts-list.pb");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -i PATH                path to the Test Suite Folder");
+        System.out.println("  -c FILE                input file of Test Content Protocal Buffer");
+        System.out.println("  -o FILE                output file of Test Case List Protocal Buffer");
+        System.out.println();
+        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 print Usage will call exit(1)
+        }
+    }
+
+    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 TestSuite.Package.Class.ClassType chkTestType(ClassDef classDef) {
+        // Only care about Public Class
+        if ((classDef.getAccessFlags() & AccessFlags.PUBLIC.getValue()) == 0) {
+            return TestSuite.Package.Class.ClassType.UNKNOWN;
+        }
+
+        for (Annotation annotation : classDef.getAnnotations()) {
+            if (annotation.getType().equals(DEPRECATED_ANNOTATION_TAG)) {
+                return TestSuite.Package.Class.ClassType.UNKNOWN;
+            }
+            if (annotation.getType().equals(RUN_WITH_ANNOTATION_TAG)) {
+                for (AnnotationElement annotationEle : annotation.getElements()) {
+                    String aName = annotationEle.getName();
+                    if (aName.equals(ANDROID_JUNIT4_TEST_TAG)) {
+                        return TestSuite.Package.Class.ClassType.JUNIT4;
+                    } else if (aName.equals(PARAMETERIZED_TAG)) {
+                        return TestSuite.Package.Class.ClassType.PARAMETERIZED;
+                    }
+                }
+                return TestSuite.Package.Class.ClassType.JUNIT4;
+            }
+        }
+
+        if (classDef.getType().endsWith(TEST_TAG) || classDef.getType().endsWith(TESTS_TAG)) {
+            return TestSuite.Package.Class.ClassType.JUNIT3;
+        } else {
+            return TestSuite.Package.Class.ClassType.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;
+    }
+
+    // Get test case list from an APK
+    private static TestSuite.Package.Builder parseApkTestCase(List<String> apkList,
+            List<String> classList, String tsPath, int api)
+            throws Exception {
+
+        TestSuite.Package.Builder tsPkgBuilder = TestSuite.Package.newBuilder();
+        for (String apkName : apkList) {
+            DexFile dexFile = null;
+            String apkPath = Paths.get(tsPath, apkName).toString();
+            try {
+                dexFile = DexFileFactory.loadDexFile(apkPath, Opcodes.forApi(api));
+            } catch (IOException | DexFileFactory.DexFileNotFoundException ex) {
+                System.err.println("Unable to load dex file: " + apkPath);
+                // ex.printStackTrace();
+                continue;
+            }
+
+            tsPkgBuilder.setName(apkName);
+            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);
+                }
+
+                TestSuite.Package.Class.ClassType cType;
+                cType = chkTestType(classDef);
+                if (TestSuite.Package.Class.ClassType.JUNIT3 == cType) {
+                    TestSuite.Package.Class.Builder tClassBuilder =
+                            TestSuite.Package.Class.newBuilder();
+                    tClassBuilder.setClassType(cType);
+                    tClassBuilder.setApk(apkName);
+                    tClassBuilder.setName(className);
+
+                    for (Method method : classDef.getMethods()) {
+                        if ((method.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0) {
+                            String mName = method.getName();
+                            if (hasAnnotationSuffix(
+                                    method.getAnnotations(), SUPPRESS_ANNOTATION_TAG)) {
+                                System.err.printf("%s#%s with Suppress:\n", className, mName);
+                                System.err.println(method.getAnnotations());
+                            } else if (mName.startsWith(TEST_PREFIX_TAG)) {
+                                TestSuite.Package.Class.Method.Builder methodBuilder =
+                                        TestSuite.Package.Class.Method.newBuilder();
+                                methodBuilder.setName(mName);
+                                tClassBuilder.addMethods(methodBuilder);
+                            }
+                        }
+                    }
+                    tsPkgBuilder.addClasses(tClassBuilder);
+                } else if (TestSuite.Package.Class.ClassType.JUNIT4 == cType) {
+                    TestSuite.Package.Class.Builder tClassBuilder =
+                            TestSuite.Package.Class.newBuilder();
+                    tClassBuilder.setClassType(cType);
+                    tClassBuilder.setApk(apkName);
+                    tClassBuilder.setName(className);
+
+                    for (Method method : classDef.getMethods()) {
+                        if (hasAnnotation(method.getAnnotations(), TEST_ANNOTATION_TAG)) {
+                            String mName = method.getName();
+                            TestSuite.Package.Class.Method.Builder methodBuilder =
+                                    TestSuite.Package.Class.Method.newBuilder();
+                            methodBuilder.setName(mName);
+                            tClassBuilder.addMethods(methodBuilder);
+                        }
+                    }
+                    tsPkgBuilder.addClasses(tClassBuilder);
+                } else if (TestSuite.Package.Class.ClassType.PARAMETERIZED == cType) {
+                    TestSuite.Package.Class.Builder tClassBuilder =
+                            TestSuite.Package.Class.newBuilder();
+                    tClassBuilder.setClassType(cType);
+                    tClassBuilder.setApk(apkName);
+                    tClassBuilder.setName(className);
+
+                    for (Method method : classDef.getMethods()) {
+                        if (hasAnnotation(method.getAnnotations(), TEST_ANNOTATION_TAG)) {
+                            String mName = method.getName();
+                            TestSuite.Package.Class.Method.Builder methodBuilder =
+                                    TestSuite.Package.Class.Method.newBuilder();
+                            methodBuilder.setName(mName);
+                            tClassBuilder.addMethods(methodBuilder);
+                        }
+                    }
+                    tsPkgBuilder.addClasses(tClassBuilder);
+                }
+            }
+        }
+        return tsPkgBuilder;
+    }
+
+    private static Collection<Class<?>> getJarTestClasses(File jarTestFile, String tfPath)
+            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!/", tfPath))
+            };
+            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 static TestSuite.Package.Builder parseJarTestCase(
+            List<String> jarList, String tsPath, String tfPath) throws Exception {
+
+        TestSuite.Package.Builder tsPkgBuilder = TestSuite.Package.newBuilder();
+        for (String jarName : jarList) {
+            tsPkgBuilder.setName(jarName);
+            Collection<Class<?>> classes =
+                    getJarTestClasses(Paths.get(tsPath, jarName).toFile(), tfPath);
+            for (Class<?> c : classes) {
+                TestSuite.Package.Class.Builder tClassBuilder =
+                        TestSuite.Package.Class.newBuilder();
+                tClassBuilder.setClassType(TestSuite.Package.Class.ClassType.JAVAHOST);
+                tClassBuilder.setApk(jarName);
+                tClassBuilder.setName(c.getName());
+
+                System.err.printf("class: %s\n", 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)) {
+                            System.err.printf("test: %s\n", m.getName());
+                            TestSuite.Package.Class.Method.Builder methodBuilder =
+                                    TestSuite.Package.Class.Method.newBuilder();
+                            methodBuilder.setName(m.getName());
+                            tClassBuilder.addMethods(methodBuilder);
+                        }
+                    }
+                }
+                tsPkgBuilder.addClasses(tClassBuilder);
+            }
+        }
+        return tsPkgBuilder;
+    }
+
+    // Iterates though all test suite content and prints them.
+    static TestSuite.Builder listTestCases(TestSuiteContent tsContent, String tsPath, String tfPath)
+            throws Exception {
+        TestSuite.Builder tsBuilder = TestSuite.newBuilder();
+
+        int i = 1;
+        for (Entry entry: tsContent.getFileEntriesList()) {
+            if (Entry.EntryType.CONFIG == entry.getType()) {
+                TestSuite.Package.Type pType = null;
+                ConfigMetadata config = entry.getFileMetadata().getConfigMetadata();
+
+                // getting package/class list from Test Module Configuration
+                ArrayList<String> testClassList = new ArrayList<String> ();
+                ArrayList<String> hostTestJarList = new ArrayList<String>();
+                List<Option> optList;
+                List<ConfigMetadata.TestClass> testClassesList = config.getTestClassesList();
+                for (ConfigMetadata.TestClass tClass : testClassesList) {
+                    boolean isHostTest = false;
+                    boolean isAndroidJunitTest = false;
+                    optList = tClass.getOptionsList();
+                    if (HOST_TEST_CLASS_TAG.equals(tClass.getTestClass())) {
+                        isHostTest = true;
+                        pType = TestSuite.Package.Type.JAVAHOST;
+                    } else if (ANDROID_JUNIT_TEST_TAG.equals(tClass.getTestClass())) {
+                        isAndroidJunitTest = true;
+                    } else if (GTEST_TAG.equals(tClass.getTestClass())) {
+                        pType = TestSuite.Package.Type.GTEST;
+                    } else if (DEQP_TEST_TAG.equals(tClass.getTestClass())) {
+                        pType = TestSuite.Package.Type.DEQP;
+                    } else if (LIBCORE_TEST_TAG.equals(tClass.getTestClass())) {
+                        pType = TestSuite.Package.Type.LIBCORE;
+                    } else if (DALVIK_TEST_TAG.equals(tClass.getTestClass())) {
+                        // cts/tests/jdwp/AndroidTest.xml
+                        pType = TestSuite.Package.Type.DALVIK;
+                    } else {
+                        System.err.printf(
+                                "Unknown Test Type: %s %s\n",
+                                entry.getName(), tClass.getTestClass());
+                    }
+
+                    for (Option opt : optList) {
+                        if (isAndroidJunitTest && PACKAGE_TAG.equalsIgnoreCase(opt.getName())) {
+                            testClassList.add(opt.getValue());
+                        } else if (isHostTest && JAR_TAG.equalsIgnoreCase(opt.getName())) {
+                            hostTestJarList.add(opt.getValue());
+                        }
+                    }
+                }
+
+                // getting apk list from Test Module Configuration
+                ArrayList<String> testApkList = new ArrayList<String> ();
+                List<ConfigMetadata.TargetPreparer> tPrepList = config.getTargetPreparersList();
+                for (ConfigMetadata.TargetPreparer tPrep : tPrepList) {
+                    optList = tPrep.getOptionsList();
+                    for (Option opt : optList) {
+                        if (TEST_FILE_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+                            testApkList.add(opt.getValue());
+                        }
+                    }
+                }
+
+                TestSuite.Package.Builder tsPkgBuilder;
+
+                if (pType == TestSuite.Package.Type.JAVAHOST) {
+                    tsPkgBuilder = parseJarTestCase(hostTestJarList, tsPath, tfPath);
+                } else {
+                    tsPkgBuilder = parseApkTestCase(testApkList, testClassList, tsPath, 27);
+                }
+
+                tsPkgBuilder.setName(entry.getName().replaceAll(CONFIG_REGEX, ""));
+                if (null != pType) {
+                    tsPkgBuilder.setType(pType);
+                }
+
+                tsBuilder.addPackages(tsPkgBuilder);
+            }
+        }
+        return tsBuilder;
+    }
+
+    private static boolean isKnownFailure(String tsName, List<String> knownFailures) {
+        for (String kf : knownFailures) {
+            if (tsName.startsWith(kf)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void printTestSuite(TestSuite ts, List<String> knownFailures) {
+        //Header
+        System.out.println("Module,Class,Test,no,Apk,Type");
+        int i = 1;
+        for (TestSuite.Package pkg : ts.getPackagesList()) {
+            for (TestSuite.Package.Class cls : pkg.getClassesList()) {
+                for (TestSuite.Package.Class.Method mtd : cls.getMethodsList()) {
+                    String testCaseName =
+                            String.format(
+                                    TESTCASE_NAME_FORMAT,
+                                    pkg.getName(),
+                                    cls.getName(),
+                                    mtd.getName());
+                    // Filter out known failures
+                    if (!isKnownFailure(testCaseName, knownFailures)) {
+                        System.out.printf(
+                                "%s,%s,%s,%d,%s,%s\n",
+                                pkg.getName(),
+                                cls.getName(),
+                                mtd.getName(),
+                                i++,
+                                cls.getApk(),
+                                cls.getClassType());
+                    }
+                }
+            }
+        }
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void printTestSuiteSummary(TestSuite ts, String fName, List<String> knownFailures)
+            throws IOException {
+        FileWriter fWriter = new FileWriter(fName);
+        PrintWriter pWriter = new PrintWriter(fWriter);
+
+        //Header
+        pWriter.print("Module,Test#,no,Type,Class#\n");
+
+        int i = 0;
+        for (TestSuite.Package pkg : ts.getPackagesList()) {
+            int classCnt = 0;
+            int methodCnt = 0;
+            for (TestSuite.Package.Class cls : pkg.getClassesList()) {
+                for (TestSuite.Package.Class.Method mtd : cls.getMethodsList()) {
+                    String testCaseName =
+                            String.format(
+                                    TESTCASE_NAME_FORMAT,
+                                    pkg.getName(),
+                                    cls.getName(),
+                                    mtd.getName());
+                    // Filter out known failures
+                    if (!isKnownFailure(testCaseName, knownFailures)) {
+                        methodCnt++;
+                    }
+                }
+                classCnt++;
+            }
+            pWriter.printf(
+                    "%s,%s,%d,%s,%d\n", pkg.getName(), methodCnt, i++, pkg.getType(), classCnt);
+        }
+
+        pWriter.close();
+    }
+
+    public static void main(String[] args)
+            throws IOException, NoSuchAlgorithmException, Exception {
+        String tsContentFilePath = "./tsContentMessage.pb";
+        String outputTestCaseListFilePath = "./tsTestCaseList.pb";
+        String outputSummaryFilePath = "./tsSummary.csv";
+        String tsPath = "";
+        String tradFedJarPath = "";
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    outputTestCaseListFilePath = getExpectedArg(args, ++i);
+                } else if ("-c".equals(args[i])) {
+                    tsContentFilePath = getExpectedArg(args, ++i);
+                } else if ("-s".equals(args[i])) {
+                    outputSummaryFilePath = getExpectedArg(args, ++i);
+                } else if ("-t".equals(args[i])) {
+                    tradFedJarPath = getExpectedArg(args, ++i);
+                } else if ("-i".equals(args[i])) {
+                    tsPath = getExpectedArg(args, ++i);
+                    File file = new File(tsPath);
+                    // Only acception a folder
+                    if (!file.isDirectory()) {
+                        printUsage();
+                    }
+                } else {
+                    printUsage();
+                }
+            }
+        }
+
+        // Read message from the file and print them out
+        TestSuiteContent tsContent =
+                TestSuiteContent.parseFrom(new FileInputStream(tsContentFilePath));
+
+        TestSuite ts = listTestCases(tsContent, tsPath, tradFedJarPath).build();
+
+        // Write test case list message to disk.
+        FileOutputStream output = new FileOutputStream(outputTestCaseListFilePath);
+        try {
+          ts.writeTo(output);
+        } finally {
+          output.close();
+        }
+
+        // Read message from the file and print them out
+        TestSuite ts1 = TestSuite.parseFrom(new FileInputStream(outputTestCaseListFilePath));
+        printTestSuite(ts1, tsContent.getKnownFailuresList());
+        printTestSuiteSummary(ts1, outputSummaryFilePath, tsContent.getKnownFailuresList());
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
index 85423b7..8a17153 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.apicoverage;
 
+import com.android.cts.apicoverage.TestSuiteProto.*;
+
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -25,25 +27,65 @@
  * TestModule.xml.
  */
 class TestModuleConfigHandler extends DefaultHandler {
-    private String mTestClassName;
-    private String mModuleName;
-    private Boolean inTestEle = false;
+    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";
+
+    private FileMetadata.Builder mFileMetadata;
+    private ConfigMetadata.Builder mConfigMetadata;
+    private ConfigMetadata.TestClass.Builder mTestCase;
+    private ConfigMetadata.TargetPreparer.Builder mTargetPreparer;
+    private String mModuleName = null;
+
+    TestModuleConfigHandler(String configFileName) {
+        mFileMetadata = FileMetadata.newBuilder();
+        mConfigMetadata = ConfigMetadata.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);
 
-        if ("test".equalsIgnoreCase(localName)) {
-            mTestClassName = attributes.getValue("class");
-            inTestEle = true;
-        } else if ("option".equalsIgnoreCase(localName)) {
-            if (inTestEle) {
-                String optName = attributes.getValue("name");
-                if ("module-name".equalsIgnoreCase(optName)) {
-                    mModuleName = attributes.getValue("value");
+        if (CONFIGURATION_TAG.equalsIgnoreCase(localName)) {
+            if (null != attributes.getValue(DESCRIPTION_TAG)) {
+                mFileMetadata.setDescription(attributes.getValue(DESCRIPTION_TAG));
+            } else {
+                mFileMetadata.setDescription("WARNING: no description.");
+            }
+        } else if (TEST_TAG.equalsIgnoreCase(localName)) {
+            mTestCase = ConfigMetadata.TestClass.newBuilder();
+            mTestCase.setTestClass(attributes.getValue(CLASS_TAG));
+        } else if (TARGET_PREPARER_TAG.equalsIgnoreCase(localName)) {
+            mTargetPreparer = ConfigMetadata.TargetPreparer.newBuilder();
+            mTargetPreparer.setTestClass(attributes.getValue(CLASS_TAG));
+        } else if (OPTION_TAG.equalsIgnoreCase(localName)) {
+            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);
+                if (GTEST_CLASS_TAG.equalsIgnoreCase(option.getName())) {
+                    mModuleName = option.getValue();
                 }
-                //System.out.println(String.format("%s: %s, %s, %s", localName, name, optName, attributes.getValue("value")));
+            } else if (null != mTargetPreparer) {
+                mTargetPreparer.addOptions(option);
             }
         }
     }
@@ -51,8 +93,14 @@
     @Override
     public void endElement(String uri, String localName, String name) throws SAXException {
         super.endElement(uri, localName, name);
-        if ("test".equalsIgnoreCase(localName)) {
-            inTestEle = false;
+        if (TEST_TAG.equalsIgnoreCase(localName)) {
+            mConfigMetadata.addTestClasses(mTestCase);
+            mTestCase = null;
+        } else if (TARGET_PREPARER_TAG.equalsIgnoreCase(localName)) {
+            mConfigMetadata.addTargetPreparers(mTargetPreparer);
+            mTargetPreparer = null;
+        } else if (CONFIGURATION_TAG.equalsIgnoreCase(localName)) {
+            mFileMetadata.setConfigMetadata(mConfigMetadata);
         }
     }
 
@@ -61,6 +109,11 @@
     }
 
     public String getTestClassName() {
-        return mTestClassName;
+        //return the 1st Test Class
+        return mFileMetadata.getConfigMetadata().getTestClassesList().get(0).getTestClass();
+    }
+
+    public FileMetadata getFileMetadata() {
+        return mFileMetadata.build();
     }
 }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
new file mode 100644
index 0000000..b0e5b2a
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+
+import com.android.cts.apicoverage.TestSuiteProto.*;
+
+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.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.MessageDigest;
+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;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+class TestSuiteContentReport {
+    // 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";
+
+    // Target File Extensions
+    private static final String CONFIG_EXT_TAG = ".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";
+    private static final String TEST_SUITE_HARNESS = "-tradefed.jar";
+    private static final String KNOWN_FAILURES_XML_FILE = "-known-failures.xml";
+
+    private static final String OPTION_TAG = "option";
+    private static final String NAME_TAG = "name";
+    private static final String EXCLUDE_FILTER_TAG = "compatibility:exclude-filter";
+    private static final String VALUE_TAG = "value";
+
+
+    private static void printUsage() {
+        System.out.println("Usage: test-suite-content-report [OPTION]...");
+        System.out.println();
+        System.out.println("Generates test suite content protocal buffer message.");
+        System.out.println();
+        System.out.println(
+                "$ANDROID_HOST_OUT/bin/test-suite-content-report "
+                        + "-i out/host/linux-x86/cts/android-cts "
+                        + "-o ./cts-content.pb"
+                        + "-t ./cts-list.pb");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -i PATH                path to the Test Suite Folder");
+        System.out.println("  -o FILE                output file of Test Content Protocal Buffer");
+        System.out.println("  -t FILE                output file of Test Case List Protocal Buffer");
+        System.out.println();
+        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 TestSuiteContent parseTestSuiteFolder(String testSuitePath)
+            throws IOException, NoSuchAlgorithmException {
+
+        TestSuiteContent.Builder testSuiteContent = TestSuiteContent.newBuilder();
+        testSuiteContent.addFileEntries(parseFolder(testSuiteContent, testSuitePath, testSuitePath));
+        return testSuiteContent.build();
+    }
+
+    // Parse a file
+    private static FileMetadata parseFileMetadata(Entry.Builder fEntry, File file)
+            throws Exception {
+        if (file.getName().endsWith(CONFIG_EXT_TAG)) {
+            fEntry.setType(Entry.EntryType.CONFIG);
+            return parseConfigFile(file);
+        } else if (file.getName().endsWith(APK_EXT_TAG)) {
+            fEntry.setType(Entry.EntryType.APK);
+        } else if (file.getName().endsWith(JAR_EXT_TAG)) {
+            fEntry.setType(Entry.EntryType.JAR);
+        } else if (file.getName().endsWith(SO_EXT_TAG)) {
+            fEntry.setType(Entry.EntryType.SO);
+        } else {
+            // Just file in general
+            fEntry.setType(Entry.EntryType.FILE);
+        }
+        return null;
+    }
+
+    private static FileMetadata parseConfigFile(File file)
+            throws Exception {
+        XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+        TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler(file.getName());
+        xmlReader.setContentHandler(testModuleXmlHandler);
+        FileReader fileReader = null;
+        try {
+            fileReader = new FileReader(file);
+            xmlReader.parse(new InputSource(fileReader));
+            return testModuleXmlHandler.getFileMetadata();
+        } finally {
+            if (null != fileReader) {
+                fileReader.close();
+            }
+        }
+    }
+
+    private static class KnownFailuresXmlHandler extends DefaultHandler {
+        private TestSuiteContent.Builder mTsBld = null;
+
+        KnownFailuresXmlHandler(TestSuiteContent.Builder tsBld) {
+            mTsBld = tsBld;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            super.startElement(uri, localName, name, attributes);
+
+            System.err.printf(
+                    "ele %s: %s: %s \n",
+                    localName, attributes.getValue(NAME_TAG), attributes.getValue(VALUE_TAG));
+            if (EXCLUDE_FILTER_TAG.equals(attributes.getValue(NAME_TAG))) {
+                String kfFilter = attributes.getValue(VALUE_TAG).replace(' ', '.');
+                mTsBld.addKnownFailures(kfFilter);
+            }
+        }
+    }
+
+    private static void parseKnownFailures(TestSuiteContent.Builder tsBld, File file)
+            throws Exception {
+
+        ZipFile zip = new ZipFile(file);
+        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(tsBld);
+                    saxParser.parse(xmlStream, kfXmlHandler);
+                    xmlStream.close();
+                }
+            }
+        } finally {
+            zip.close();
+        }
+    }
+
+    // Parse a folder to add all entries
+    private static Entry.Builder parseFolder(TestSuiteContent.Builder testSuiteContent, String fPath, String rPath)
+            throws IOException, NoSuchAlgorithmException {
+        Entry.Builder folderEntry = Entry.newBuilder();
+
+        File folder = new File(fPath);
+        File rFolder = new File(rPath);
+        Path folderPath = Paths.get(folder.getAbsolutePath());
+        Path rootPath = Paths.get(rFolder.getAbsolutePath());
+        String folderRelativePath = rootPath.relativize(folderPath).toString();
+        String folderId = getId(folderRelativePath);
+        File[] fileList = folder.listFiles();
+        Long folderSize = 0L;
+        List <Entry> entryList = new ArrayList<Entry> ();
+        for (File file : fileList){
+            if (file.isFile()){
+                String fileRelativePath = rootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
+                Entry.Builder fileEntry = Entry.newBuilder();
+                fileEntry.setId(getId(fileRelativePath));
+                fileEntry.setName(file.getName());
+                fileEntry.setSize(file.length());
+                fileEntry.setContentId(getFileContentId(file));
+                fileEntry.setRelativePath(fileRelativePath);
+                fileEntry.setParentId(folderId);
+                try {
+                    FileMetadata fMetadata = parseFileMetadata(fileEntry, file);
+                    if (null != fMetadata) {
+                        fileEntry.setFileMetadata(fMetadata);
+                    }
+                    // get [cts]-known-failures.xml
+                    if (file.getName().endsWith(TEST_SUITE_HARNESS)) {
+                        parseKnownFailures(testSuiteContent, file);
+                    }
+                } catch (Exception ex) {
+                    System.err.println(
+                            String.format("Cannot parse %s",
+                                    file.getAbsolutePath()));
+                    ex.printStackTrace();
+                }
+                testSuiteContent.addFileEntries(fileEntry);
+                entryList.add(fileEntry.build());
+                folderSize += file.length();
+            } else if (file.isDirectory()){
+                Entry.Builder subFolderEntry = parseFolder(testSuiteContent, file.getAbsolutePath(), rPath);
+                subFolderEntry.setParentId(folderId);
+                testSuiteContent.addFileEntries(subFolderEntry);
+                folderSize += subFolderEntry.getSize();
+                entryList.add(subFolderEntry.build());
+            }
+        }
+
+        folderEntry.setId(folderId);
+        folderEntry.setName(folderRelativePath);
+        folderEntry.setSize(folderSize);
+        folderEntry.setType(Entry.EntryType.FOLDER);
+        folderEntry.setContentId(getFolderContentId(folderEntry, entryList));
+        folderEntry.setRelativePath(folderRelativePath);
+        return folderEntry;
+    }
+
+    private static String getFileContentId(File file)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        FileInputStream fis = new FileInputStream(file);
+
+        byte[] dataBytes = new byte[10240];
+
+        int nread = 0;
+        while ((nread = fis.read(dataBytes)) != -1) {
+          md.update(dataBytes, 0, nread);
+        };
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    private static String getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+        for (Entry entry: entryList) {
+            md.update(entry.getContentId().getBytes(StandardCharsets.UTF_8));
+        }
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    private static String getId(String name)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        md.update(name.getBytes(StandardCharsets.UTF_8));
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void printTestSuiteContent(TestSuiteContent tsContent) {
+        //Header
+        System.out.printf("no,type,name,size,relative path,id,content id,parent id,description,test class");
+        // test class header
+        System.out.printf(",%s,%s,%s,%s,%s",
+                RUNTIME_HIT_TAG, PACKAGE_TAG, JAR_NAME_TAG, NATIVE_TEST_DEVICE_PATH_TAG, MODULE_TAG);
+        // target preparer header
+        System.out.printf(",%s,%s\n",
+                TEST_FILE_NAME_TAG, PUSH_TAG);
+
+        int i = 1;
+        for (Entry entry: tsContent.getFileEntriesList()) {
+            System.out.printf("%d,%s,%s,%d,%s,%s,%s,%s",
+                i++, entry.getType(), entry.getName(), entry.getSize(),
+                entry.getRelativePath(), entry.getId(), entry.getContentId(),
+                entry.getParentId());
+
+            if (Entry.EntryType.CONFIG == entry.getType()) {
+                ConfigMetadata config = entry.getFileMetadata().getConfigMetadata();
+                System.out.printf(",%s", entry.getFileMetadata().getDescription());
+                List<Option> optList;
+                List<ConfigMetadata.TestClass> testClassesList = config.getTestClassesList();
+                String rtHit = "";
+                String pkg = "";
+                String jar = "";
+                String ntdPath = "";
+                String module = "";
+
+                for (ConfigMetadata.TestClass tClass : testClassesList) {
+                    System.out.printf(",%s", tClass.getTestClass());
+                    optList = tClass.getOptionsList();
+                    for (Option opt : optList) {
+                        if (RUNTIME_HIT_TAG.equalsIgnoreCase(opt.getName())) {
+                            rtHit = rtHit + opt.getValue() + " ";
+                        } else if (PACKAGE_TAG.equalsIgnoreCase(opt.getName())) {
+                            pkg = pkg + opt.getValue() + " ";
+                        } else if (JAR_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+                            jar = jar + opt.getValue() + " ";
+                        } else if (NATIVE_TEST_DEVICE_PATH_TAG.equalsIgnoreCase(opt.getName())) {
+                            ntdPath = ntdPath + opt.getValue() + " ";
+                        } else if (MODULE_TAG.equalsIgnoreCase(opt.getName())) {
+                            module = module + opt.getValue() + " ";
+                        }
+                    }
+                }
+                System.out.printf(",%s,%s,%s,%s,%s", rtHit.trim(), pkg.trim(),
+                        jar.trim(), module.trim(), ntdPath.trim());
+
+                List<ConfigMetadata.TargetPreparer> tPrepList = config.getTargetPreparersList();
+                String testFile = "";
+                String pushList = "";
+                for (ConfigMetadata.TargetPreparer tPrep : tPrepList) {
+                    optList = tPrep.getOptionsList();
+                    for (Option opt : optList) {
+                        if (TEST_FILE_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+                            testFile = testFile + opt.getValue() + " ";
+                        } else if (PUSH_TAG.equalsIgnoreCase(opt.getName())) {
+                            pushList = pushList + opt.getValue() + " ";
+                        }
+                    }
+                }
+                System.out.printf(",%s,%s", testFile.trim(), pushList.trim());
+            }
+            System.out.printf("\n");
+        }
+
+        System.out.printf("\nKnown Failures\n");
+        for (String kf : tsContent.getKnownFailuresList()) {
+            System.out.printf("%s\n", kf);
+        }
+    }
+
+    public static void main(String[] args)
+            throws IOException, NoSuchAlgorithmException {
+        String outputFilePath = "./tsContentMessage.pb";
+        String outputTestCaseListFilePath = "./tsTestCaseList.pb";
+        String tsPath = "";
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    outputFilePath = getExpectedArg(args, ++i);
+                } else if ("-t".equals(args[i])) {
+                    outputTestCaseListFilePath = getExpectedArg(args, ++i);
+                } else if ("-i".equals(args[i])) {
+                    tsPath = getExpectedArg(args, ++i);
+                    File file = new File(tsPath);
+                    // Only acception a folder
+                    if (!file.isDirectory()) {
+                        printUsage();
+                    }
+                } else {
+                    printUsage();
+                }
+            }
+        }
+
+        TestSuiteContent tsContent = parseTestSuiteFolder(tsPath);
+
+        // Write test suite content message to disk.
+        FileOutputStream output = new FileOutputStream(outputFilePath);
+        try {
+          tsContent.writeTo(output);
+        } finally {
+          output.close();
+        }
+
+        // Read message from the file and print them out
+        TestSuiteContent tsContent1 =
+                TestSuiteContent.parseFrom(new FileInputStream(outputFilePath));
+        printTestSuiteContent(tsContent1);
+    }
+}
diff --git a/tools/cts-device-info/Android.mk b/tools/cts-device-info/Android.mk
index 1ca719f..b574612 100644
--- a/tools/cts-device-info/Android.mk
+++ b/tools/cts-device-info/Android.mk
@@ -18,6 +18,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
 LOCAL_JNI_SHARED_LIBRARIES := libctsdeviceinfo
 
 LOCAL_MULTILIB := both
diff --git a/tools/cts-device-info/jni/Android.mk b/tools/cts-device-info/jni/Android.mk
index 9e514f0..5786c20 100644
--- a/tools/cts-device-info/jni/Android.mk
+++ b/tools/cts-device-info/jni/Android.mk
@@ -30,7 +30,6 @@
 
 LOCAL_CFLAGS := -Wall -Werror
 
-# Would be "current" if that was supported for native code.
-LOCAL_SDK_VERSION := 24
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_SHARED_LIBRARY)
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 41afdb9..cfa369a 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
@@ -468,6 +468,7 @@
         charsKeyNames.add(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES.getName());
         charsKeyNames.add(CameraCharacteristics.LENS_FACING.getName());
+        charsKeyNames.add(CameraCharacteristics.LENS_POSE_REFERENCE.getName());
         charsKeyNames.add(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES.getName());
         charsKeyNames.add(CameraCharacteristics.LENS_INFO_AVAILABLE_FILTER_DENSITIES.getName());
         charsKeyNames.add(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS.getName());
@@ -515,12 +516,15 @@
         charsKeyNames.add(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT.getName());
         charsKeyNames.add(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES.getName());
+        charsKeyNames.add(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS.getName());
         charsKeyNames.add(CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.getName());
+        charsKeyNames.add(CameraCharacteristics.INFO_VERSION.getName());
         charsKeyNames.add(CameraCharacteristics.SYNC_MAX_LATENCY.getName());
         charsKeyNames.add(CameraCharacteristics.REPROCESS_MAX_CAPTURE_STALL.getName());
         charsKeyNames.add(CameraCharacteristics.DEPTH_DEPTH_IS_EXCLUSIVE.getName());
+        charsKeyNames.add(CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE.getName());
 
         return charsKeyNames;
     }
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 fc99d75..2066d14 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
@@ -16,6 +16,7 @@
 
 package com.android.cts.deviceinfo;
 
+import java.util.HashMap;
 import com.android.compatibility.common.deviceinfo.DeviceInfo;
 import com.android.compatibility.common.util.DeviceInfoStore;
 
@@ -72,25 +73,38 @@
  */
 public final class VulkanDeviceInfo extends DeviceInfo {
 
+    private static final String KEY_16BIT_STORAGE_FEATURES = "16bitStorageFeatures";
     private static final String KEY_ALPHA_TO_ONE = "alphaToOne";
     private static final String KEY_API_VERSION = "apiVersion";
     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_DEPTH = "depth";
     private static final String KEY_DEPTH_BIAS_CLAMP = "depthBiasClamp";
     private static final String KEY_DEPTH_BOUNDS = "depthBounds";
     private static final String KEY_DEPTH_CLAMP = "depthClamp";
     private static final String KEY_DESCRIPTION = "description";
+    private static final String KEY_DEVICE_GROUPS = "deviceGroups";
     private static final String KEY_DEVICE_ID = "deviceID";
+    private static final String KEY_DEVICE_LUID = "deviceLUID";
+    private static final String KEY_DEVICE_LUID_VALID = "deviceLUIDValid";
     private static final String KEY_DEVICE_NAME = "deviceName";
+    private static final String KEY_DEVICE_NODE_MASK = "deviceNodeMask";
     private static final String KEY_DEVICE_TYPE = "deviceType";
+    private static final String KEY_DEVICE_UUID = "deviceUUID";
     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_UUID = "driverUUID";
     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";
     private static final String KEY_EXTENSIONS = "extensions";
+    private static final String KEY_EXTERNAL_FENCE_FEATURES = "externalFenceFeatures";
+    private static final String KEY_EXTERNAL_FENCE_PROPERTIES = "externalFenceProperties";
+    private static final String KEY_EXTERNAL_SEMAPHORE_FEATURES = "externalSemaphoreFeatures";
+    private static final String KEY_EXTERNAL_SEMAPHORE_PROPERTIES = "externalSemaphoreProperties";
     private static final String KEY_FEATURES = "features";
     private static final String KEY_FILL_MODE_NON_SOLID = "fillModeNonSolid";
     private static final String KEY_FLAGS = "flags";
@@ -104,6 +118,7 @@
     private static final String KEY_GEOMETRY_SHADER = "geometryShader";
     private static final String KEY_HEAP_INDEX = "heapIndex";
     private static final String KEY_HEIGHT = "height";
+    private static final String KEY_ID_PROPERTIES = "idProperties";
     private static final String KEY_IMAGE_CUBE_ARRAY = "imageCubeArray";
     private static final String KEY_IMPLEMENTATION_VERSION = "implementationVersion";
     private static final String KEY_INDEPENDENT_BLEND = "independentBlend";
@@ -116,6 +131,7 @@
     private static final String KEY_LINE_WIDTH_RANGE = "lineWidthRange";
     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_MAX_BOUND_DESCRIPTOR_SETS = "maxBoundDescriptorSets";
     private static final String KEY_MAX_CLIP_DISTANCES = "maxClipDistances";
     private static final String KEY_MAX_COLOR_ATTACHMENTS = "maxColorAttachments";
@@ -154,6 +170,10 @@
     private static final String KEY_MAX_IMAGE_DIMENSION_CUBE = "maxImageDimensionCube";
     private static final String KEY_MAX_INTERPOLATION_OFFSET = "maxInterpolationOffset";
     private static final String KEY_MAX_MEMORY_ALLOCATION_COUNT = "maxMemoryAllocationCount";
+    private static final String KEY_MAX_MEMORY_ALLOCATION_SIZE = "maxMemoryAllocationSize";
+    private static final String KEY_MAX_MULTIVIEW_INSTANCE_INDEX = "maxMultiviewInstanceIndex";
+    private static final String KEY_MAX_MULTIVIEW_VIEW_COUNT = "maxMultiviewViewCount";
+    private static final String KEY_MAX_PER_SET_DESCRIPTORS = "maxPerSetDescriptors";
     private static final String KEY_MAX_PER_STAGE_DESCRIPTOR_INPUT_ATTACHMENTS = "maxPerStageDescriptorInputAttachments";
     private static final String KEY_MAX_PER_STAGE_DESCRIPTOR_SAMPLED_IMAGES = "maxPerStageDescriptorSampledImages";
     private static final String KEY_MAX_PER_STAGE_DESCRIPTOR_SAMPLERS = "maxPerStageDescriptorSamplers";
@@ -202,6 +222,11 @@
     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";
+    private static final String KEY_MULTIVIEW = "multiview";
+    private static final String KEY_MULTIVIEW_FEATURES = "multiviewFeatures";
+    private static final String KEY_MULTIVIEW_GEOMETRY_SHADER = "multiviewGeometryShader";
+    private static final String KEY_MULTIVIEW_PROPERTIES = "multiviewProperties";
+    private static final String KEY_MULTIVIEW_TESSELLATION_SHADER = "multiviewTessellationShader";
     private static final String KEY_NON_COHERENT_ATOM_SIZE = "nonCoherentAtomSize";
     private static final String KEY_OCCLUSION_QUERY_PRECISE = "occlusionQueryPrecise";
     private static final String KEY_OPTIMAL_BUFFER_COPY_OFFSET_ALIGNMENT = "optimalBufferCopyOffsetAlignment";
@@ -209,10 +234,15 @@
     private static final String KEY_OPTIMAL_TILING_FEATURES = "optimalTilingFeatures";
     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";
+    private static final String KEY_POINT_CLIPPING_PROPERTIES = "pointClippingProperties";
     private static final String KEY_POINT_SIZE_GRANULARITY = "pointSizeGranularity";
     private static final String KEY_POINT_SIZE_RANGE = "pointSizeRange";
     private static final String KEY_PROPERTIES = "properties";
     private static final String KEY_PROPERTY_FLAGS = "propertyFlags";
+    private static final String KEY_PROTECTED_MEMORY = "protectedMemory";
+    private static final String KEY_PROTECTED_MEMORY_FEATURES = "protectedMemoryFeatures";
+    private static final String KEY_QUAD_OPERATIONS_IN_ALL_STAGES = "quadOperationsInAllStages";
     private static final String KEY_QUEUE_COUNT = "queueCount";
     private static final String KEY_QUEUE_FLAGS = "queueFlags";
     private static final String KEY_QUEUES = "queues";
@@ -228,8 +258,12 @@
     private static final String KEY_SAMPLED_IMAGE_INTEGER_SAMPLE_COUNTS = "sampledImageIntegerSampleCounts";
     private static final String KEY_SAMPLED_IMAGE_STENCIL_SAMPLE_COUNTS = "sampledImageStencilSampleCounts";
     private static final String KEY_SAMPLER_ANISOTROPY = "samplerAnisotropy";
+    private static final String KEY_SAMPLER_YCBCR_CONVERSION = "samplerYcbcrConversion";
+    private static final String KEY_SAMPLER_YCBCR_CONVERSION_FEATURES = "samplerYcbcrConversionFeatures";
     private static final String KEY_SHADER_CLIP_DISTANCE = "shaderClipDistance";
     private static final String KEY_SHADER_CULL_DISTANCE = "shaderCullDistance";
+    private static final String KEY_SHADER_DRAW_PARAMETER_FEATURES = "shaderDrawParameterFeatures";
+    private static final String KEY_SHADER_DRAW_PARAMETERS = "shaderDrawParameters";
     private static final String KEY_SHADER_FLOAT64 = "shaderFloat64";
     private static final String KEY_SHADER_IMAGE_GATHER_EXTENDED = "shaderImageGatherExtended";
     private static final String KEY_SHADER_INT16 = "shaderInt16";
@@ -259,11 +293,19 @@
     private static final String KEY_SPARSE_RESIDENCY_IMAGE_3D = "sparseResidencyImage3D";
     private static final String KEY_SPEC_VERSION = "specVersion";
     private static final String KEY_STANDARD_SAMPLE_LOCATIONS = "standardSampleLocations";
+    private static final String KEY_STORAGE_BUFFER_16BIT_ACCESS = "storageBuffer16BitAccess";
     private static final String KEY_STORAGE_IMAGE_SAMPLE_COUNTS = "storageImageSampleCounts";
+    private static final String KEY_STORAGE_INPUT_OUTPUT_16 = "storageInputOutput16";
+    private static final String KEY_STORAGE_PUSH_CONSTANT_16 = "storagePushConstant16";
     private static final String KEY_STRICT_LINES = "strictLines";
     private static final String KEY_SUB_PIXEL_INTERPOLATION_OFFSET_BITS = "subPixelInterpolationOffsetBits";
     private static final String KEY_SUB_PIXEL_PRECISION_BITS = "subPixelPrecisionBits";
     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_SUBSET_ALLOCATION = "subsetAllocation";
+    private static final String KEY_SUPPORTED_OPERATIONS = "supportedOperations";
+    private static final String KEY_SUPPORTED_STAGES = "supportedStages";
     private static final String KEY_TESSELLATION_SHADER = "tessellationShader";
     private static final String KEY_TEXTURE_COMPRESSION_ASTC_LDR = "textureCompressionASTC_LDR";
     private static final String KEY_TEXTURE_COMPRESSION_BC = "textureCompressionBC";
@@ -271,22 +313,36 @@
     private static final String KEY_TIMESTAMP_COMPUTE_AND_GRAPHICS = "timestampComputeAndGraphics";
     private static final String KEY_TIMESTAMP_PERIOD = "timestampPeriod";
     private static final String KEY_TIMESTAMP_VALID_BITS = "timestampValidBits";
+    private static final String KEY_UNIFORM_AND_STORAGE_BUFFER_16BIT_ACCESS = "uniformAndStorageBuffer16BitAccess";
     private static final String KEY_VARIABLE_MULTISAMPLE_RATE = "variableMultisampleRate";
+    private static final String KEY_VARIABLE_POINTER_FEATURES = "variablePointerFeatures";
+    private static final String KEY_VARIABLE_POINTER_FEATURES_KHR = "variablePointerFeaturesKHR";
+    private static final String KEY_VARIABLE_POINTERS = "variablePointers";
+    private static final String KEY_VARIABLE_POINTERS_STORAGE_BUFFER = "variablePointersStorageBuffer";
     private static final String KEY_VENDOR_ID = "vendorID";
     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_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 HashMap<String, Integer> extensionNameToEnum;
+
     static {
         System.loadLibrary("ctsdeviceinfo");
+        extensionNameToEnum = new HashMap<>();
+        extensionNameToEnum.put(KEY_VK_KHR_VARIABLE_POINTERS, ENUM_VK_KHR_VARIABLE_POINTERS);
     }
 
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
         try {
             JSONObject instance = new JSONObject(nativeGetVkJSON());
+            emitDeviceGroups(store, instance);
             emitLayers(store, instance);
             emitExtensions(store, instance);
             emitDevices(store, instance);
@@ -296,6 +352,22 @@
         }
     }
 
+    private static void emitDeviceGroups(DeviceInfoStore store, JSONObject parent)
+            throws Exception {
+        JSONArray deviceGroups = parent.getJSONArray(KEY_DEVICE_GROUPS);
+        store.startArray(convertName(KEY_DEVICE_GROUPS));
+        for (int deviceGroupIdx = 0; deviceGroupIdx < deviceGroups.length(); deviceGroupIdx++) {
+            JSONObject deviceGroup = deviceGroups.getJSONObject(deviceGroupIdx);
+            store.startGroup();
+            {
+                emitLongArray(store, deviceGroup, KEY_DEVICES);
+                emitBoolean(store, deviceGroup, KEY_SUBSET_ALLOCATION);
+            }
+            store.endGroup();
+        }
+        store.endArray();
+    }
+
     private static void emitDevices(DeviceInfoStore store, JSONObject parent)
             throws Exception {
         JSONArray devices = parent.getJSONArray(KEY_DEVICES);
@@ -583,6 +655,132 @@
                     store.endGroup();
                 }
                 store.endArray();
+
+                if (properties.getLong(KEY_API_VERSION) >= VK_API_VERSION_1_1) {
+                    JSONObject subgroupProperties = device.getJSONObject(KEY_SUBGROUP_PROPERTIES);
+                    store.startGroup(convertName(KEY_SUBGROUP_PROPERTIES));
+                    {
+                        emitLong(store, subgroupProperties, KEY_SUBGROUP_SIZE);
+                        emitLong(store, subgroupProperties, KEY_SUPPORTED_STAGES);
+                        emitLong(store, subgroupProperties, KEY_SUPPORTED_OPERATIONS);
+                        emitBoolean(store, subgroupProperties, KEY_QUAD_OPERATIONS_IN_ALL_STAGES);
+                    }
+                    store.endGroup();
+
+                    JSONObject pointClippingProperties = device.getJSONObject(KEY_POINT_CLIPPING_PROPERTIES);
+                    store.startGroup(convertName(KEY_POINT_CLIPPING_PROPERTIES));
+                    {
+                        emitLong(store, pointClippingProperties, KEY_POINT_CLIPPING_BEHAVIOR);
+                    }
+                    store.endGroup();
+
+                    JSONObject multiviewProperties = device.getJSONObject(KEY_MULTIVIEW_PROPERTIES);
+                    store.startGroup(convertName(KEY_MULTIVIEW_PROPERTIES));
+                    {
+                        emitLong(store, multiviewProperties, KEY_MAX_MULTIVIEW_VIEW_COUNT);
+                        emitLong(store, multiviewProperties, KEY_MAX_MULTIVIEW_INSTANCE_INDEX);
+                    }
+                    store.endGroup();
+
+                    JSONObject idProperties = device.getJSONObject(KEY_ID_PROPERTIES);
+                    store.startGroup(convertName(KEY_ID_PROPERTIES));
+                    {
+                        emitLongArray(store, idProperties, KEY_DEVICE_UUID);
+                        emitLongArray(store, idProperties, KEY_DRIVER_UUID);
+                        emitLongArray(store, idProperties, KEY_DEVICE_LUID);
+                        emitLong(store, idProperties, KEY_DEVICE_NODE_MASK);
+                        emitBoolean(store, idProperties, KEY_DEVICE_LUID_VALID);
+                    }
+                    store.endGroup();
+
+                    JSONObject maintenance3Properties = device.getJSONObject(KEY_MAINTENANCE_3_PROPERTIES);
+                    store.startGroup(convertName(KEY_MAINTENANCE_3_PROPERTIES));
+                    {
+                        emitLong(store, maintenance3Properties, KEY_MAX_PER_SET_DESCRIPTORS);
+                        emitString(store, maintenance3Properties, KEY_MAX_MEMORY_ALLOCATION_SIZE);
+                    }
+                    store.endGroup();
+
+                    JSONObject bit16StorageFeatures = device.getJSONObject(KEY_16BIT_STORAGE_FEATURES);
+                    store.startGroup(convertName(KEY_16BIT_STORAGE_FEATURES));
+                    {
+                        emitBoolean(store, bit16StorageFeatures, KEY_STORAGE_BUFFER_16BIT_ACCESS);
+                        emitBoolean(store, bit16StorageFeatures, KEY_UNIFORM_AND_STORAGE_BUFFER_16BIT_ACCESS);
+                        emitBoolean(store, bit16StorageFeatures, KEY_STORAGE_PUSH_CONSTANT_16);
+                        emitBoolean(store, bit16StorageFeatures, KEY_STORAGE_INPUT_OUTPUT_16);
+                    }
+                    store.endGroup();
+
+                    JSONObject multiviewFeatures = device.getJSONObject(KEY_MULTIVIEW_FEATURES);
+                    store.startGroup(convertName(KEY_MULTIVIEW_FEATURES));
+                    {
+                        emitBoolean(store, multiviewFeatures, KEY_MULTIVIEW);
+                        emitBoolean(store, multiviewFeatures, KEY_MULTIVIEW_GEOMETRY_SHADER);
+                        emitBoolean(store, multiviewFeatures, KEY_MULTIVIEW_TESSELLATION_SHADER);
+                    }
+                    store.endGroup();
+
+                    JSONObject variablePointerFeatures = device.getJSONObject(KEY_VARIABLE_POINTER_FEATURES);
+                    store.startGroup(convertName(KEY_VARIABLE_POINTER_FEATURES));
+                    {
+                        emitBoolean(store, variablePointerFeatures, KEY_VARIABLE_POINTERS_STORAGE_BUFFER);
+                        emitBoolean(store, variablePointerFeatures, KEY_VARIABLE_POINTERS);
+                    }
+                    store.endGroup();
+
+                    JSONObject protectedMemoryFeatures = device.getJSONObject(KEY_PROTECTED_MEMORY_FEATURES);
+                    store.startGroup(convertName(KEY_PROTECTED_MEMORY_FEATURES));
+                    {
+                        emitBoolean(store, protectedMemoryFeatures, KEY_PROTECTED_MEMORY);
+                    }
+                    store.endGroup();
+
+                    JSONObject samplerYcbcrConversionFeatures = device.getJSONObject(KEY_SAMPLER_YCBCR_CONVERSION_FEATURES);
+                    store.startGroup(convertName(KEY_SAMPLER_YCBCR_CONVERSION_FEATURES));
+                    {
+                        emitBoolean(store, samplerYcbcrConversionFeatures, KEY_SAMPLER_YCBCR_CONVERSION);
+                    }
+                    store.endGroup();
+
+                    JSONObject shaderDrawParameterFeatures = device.getJSONObject(KEY_SHADER_DRAW_PARAMETER_FEATURES);
+                    store.startGroup(convertName(KEY_SHADER_DRAW_PARAMETER_FEATURES));
+                    {
+                        emitBoolean(store, shaderDrawParameterFeatures, KEY_SHADER_DRAW_PARAMETERS);
+                    }
+                    store.endGroup();
+
+                    JSONArray externalFences = device.getJSONArray(KEY_EXTERNAL_FENCE_PROPERTIES);
+                    store.startArray(convertName(KEY_EXTERNAL_FENCE_PROPERTIES));
+                    for (int idx = 0; idx < externalFences.length(); ++idx) {
+                        JSONArray externalFencePair = externalFences.getJSONArray(idx);
+                        JSONObject externalFenceProperties = externalFencePair.getJSONObject(1);
+                        store.startGroup();
+                        {
+                            store.addResult("handle_type", externalFencePair.getLong(0));
+                            emitLong(store, externalFenceProperties, KEY_EXPORT_FROM_IMPORTED_HANDLE_TYPES);
+                            emitLong(store, externalFenceProperties, KEY_COMPATIBLE_HANDLE_TYPES);
+                            emitLong(store, externalFenceProperties, KEY_EXTERNAL_FENCE_FEATURES);
+                        }
+                        store.endGroup();
+                    }
+                    store.endArray();
+
+                    JSONArray externalSemaphores = device.getJSONArray(KEY_EXTERNAL_SEMAPHORE_PROPERTIES);
+                    store.startArray(convertName(KEY_EXTERNAL_SEMAPHORE_PROPERTIES));
+                    for (int idx = 0; idx < externalSemaphores.length(); ++idx) {
+                        JSONArray externalSemaphorePair = externalSemaphores.getJSONArray(idx);
+                        JSONObject externalSemaphoreProperties = externalSemaphorePair.getJSONObject(1);
+                        store.startGroup();
+                        {
+                            store.addResult("handle_type", externalSemaphorePair.getLong(0));
+                            emitLong(store, externalSemaphoreProperties, KEY_EXPORT_FROM_IMPORTED_HANDLE_TYPES);
+                            emitLong(store, externalSemaphoreProperties, KEY_COMPATIBLE_HANDLE_TYPES);
+                            emitLong(store, externalSemaphoreProperties, KEY_EXTERNAL_SEMAPHORE_FEATURES);
+                        }
+                        store.endGroup();
+                    }
+                    store.endArray();
+                }
             }
             store.endGroup();
         }
@@ -613,6 +811,40 @@
         store.endArray();
     }
 
+    private static void emitVariablePointerFeaturesKHR(DeviceInfoStore store, JSONObject parent)
+            throws Exception {
+        try {
+            JSONObject extVariablePointerFeatures = parent.getJSONObject(KEY_VK_KHR_VARIABLE_POINTERS);
+            try {
+                store.startGroup(convertName(KEY_VK_KHR_VARIABLE_POINTERS));
+                {
+                    JSONObject variablePointerFeaturesKHR = extVariablePointerFeatures.getJSONObject(KEY_VARIABLE_POINTER_FEATURES_KHR);
+                    store.startGroup(convertName(KEY_VARIABLE_POINTER_FEATURES_KHR));
+                    {
+                        emitBoolean(store, variablePointerFeaturesKHR, KEY_VARIABLE_POINTERS_STORAGE_BUFFER);
+                        emitBoolean(store, variablePointerFeaturesKHR, KEY_VARIABLE_POINTERS);
+                    }
+                    store.endGroup();
+                }
+                store.endGroup();
+            } catch (JSONException e) {
+                e.printStackTrace();
+                throw new RuntimeException(e);
+            }
+        } catch (JSONException ignored) {
+        }
+    }
+
+    private static void emitExtension(String key, DeviceInfoStore store, JSONObject parent)
+            throws Exception {
+        if (!extensionNameToEnum.containsKey(key)) return;
+        switch (extensionNameToEnum.get(key)) {
+            case ENUM_VK_KHR_VARIABLE_POINTERS:
+              emitVariablePointerFeaturesKHR(store, parent);
+              break;
+        }
+    }
+
     private static void emitExtensions(DeviceInfoStore store, JSONObject parent)
             throws Exception {
         JSONArray extensions = parent.getJSONArray(KEY_EXTENSIONS);
@@ -627,6 +859,12 @@
             store.endGroup();
         }
         store.endArray();
+
+        for (int i = 0; i < extensions.length(); i++) {
+            JSONObject extension = extensions.getJSONObject(i);
+            String key = extension.getString(KEY_EXTENSION_NAME);
+            emitExtension(key, store, parent);
+        }
     }
 
     private static void emitBoolean(DeviceInfoStore store, JSONObject parent, String name)
@@ -673,25 +911,38 @@
         // This translation could be done algorithmically, but in this case being able to
         // code-search for both the original and converted names is more important.
         switch (name) {
+            case KEY_16BIT_STORAGE_FEATURES: return "bit16_storage_features";
             case KEY_ALPHA_TO_ONE: return "alpha_to_one";
             case KEY_API_VERSION: return "api_version";
             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_DEPTH: return "depth";
             case KEY_DEPTH_BIAS_CLAMP: return "depth_bias_clamp";
             case KEY_DEPTH_BOUNDS: return "depth_bounds";
             case KEY_DEPTH_CLAMP: return "depth_clamp";
             case KEY_DESCRIPTION: return "description";
+            case KEY_DEVICE_GROUPS: return "device_groups";
             case KEY_DEVICE_ID: return "device_id";
+            case KEY_DEVICE_LUID: return "device_luid";
+            case KEY_DEVICE_LUID_VALID: return "device_luid_valid";
+            case KEY_DEVICE_NODE_MASK: return "device_node_mask";
             case KEY_DEVICE_NAME: return "device_name";
             case KEY_DEVICE_TYPE: return "device_type";
+            case KEY_DEVICE_UUID: return "device_uuid";
             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_UUID: return "driver_uuid";
             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";
             case KEY_EXTENSIONS: return "extensions";
+            case KEY_EXTERNAL_FENCE_FEATURES: return "external_fence_features";
+            case KEY_EXTERNAL_FENCE_PROPERTIES: return "external_fence_properties";
+            case KEY_EXTERNAL_SEMAPHORE_FEATURES: return "external_semaphore_features";
+            case KEY_EXTERNAL_SEMAPHORE_PROPERTIES: return "external_semaphore_properties";
             case KEY_FEATURES: return "features";
             case KEY_FILL_MODE_NON_SOLID: return "fill_mode_non_solid";
             case KEY_FLAGS: return "flags";
@@ -705,6 +956,7 @@
             case KEY_GEOMETRY_SHADER: return "geometry_shader";
             case KEY_HEAP_INDEX: return "heap_index";
             case KEY_HEIGHT: return "height";
+            case KEY_ID_PROPERTIES: return "id_properties";
             case KEY_IMAGE_CUBE_ARRAY: return "image_cube_array";
             case KEY_IMPLEMENTATION_VERSION: return "implementation_version";
             case KEY_INDEPENDENT_BLEND: return "independent_blend";
@@ -717,6 +969,7 @@
             case KEY_LINE_WIDTH_RANGE: return "line_width_range";
             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_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";
@@ -755,6 +1008,10 @@
             case KEY_MAX_IMAGE_DIMENSION_CUBE: return "max_image_dimension_cube";
             case KEY_MAX_INTERPOLATION_OFFSET: return "max_interpolation_offset";
             case KEY_MAX_MEMORY_ALLOCATION_COUNT: return "max_memory_allocation_count";
+            case KEY_MAX_MEMORY_ALLOCATION_SIZE: return "max_memory_allocation_size";
+            case KEY_MAX_MULTIVIEW_VIEW_COUNT: return "max_multiview_view_count";
+            case KEY_MAX_MULTIVIEW_INSTANCE_INDEX: return "max_multiview_instance_index";
+            case KEY_MAX_PER_SET_DESCRIPTORS: return "max_per_set_descriptors";
             case KEY_MAX_PER_STAGE_DESCRIPTOR_INPUT_ATTACHMENTS: return "max_per_stage_descriptor_input_attachments";
             case KEY_MAX_PER_STAGE_DESCRIPTOR_SAMPLED_IMAGES: return "max_per_stage_descriptor_sampled_images";
             case KEY_MAX_PER_STAGE_DESCRIPTOR_SAMPLERS: return "max_per_stage_descriptor_samplers";
@@ -803,6 +1060,11 @@
             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";
+            case KEY_MULTIVIEW: return "multiview";
+            case KEY_MULTIVIEW_FEATURES: return "multiview_features";
+            case KEY_MULTIVIEW_GEOMETRY_SHADER: return "multiview_geometry_shader";
+            case KEY_MULTIVIEW_PROPERTIES: return "multiview_properties";
+            case KEY_MULTIVIEW_TESSELLATION_SHADER: return "multiview_tessellation_shader";
             case KEY_NON_COHERENT_ATOM_SIZE: return "non_coherent_atom_size";
             case KEY_OCCLUSION_QUERY_PRECISE: return "occlusion_query_precise";
             case KEY_OPTIMAL_BUFFER_COPY_OFFSET_ALIGNMENT: return "optimal_buffer_copy_offset_alignment";
@@ -810,10 +1072,15 @@
             case KEY_OPTIMAL_TILING_FEATURES: return "optimal_tiling_features";
             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";
+            case KEY_POINT_CLIPPING_PROPERTIES: return "point_clipping_properties";
             case KEY_POINT_SIZE_GRANULARITY: return "point_size_granularity";
             case KEY_POINT_SIZE_RANGE: return "point_size_range";
             case KEY_PROPERTIES: return "properties";
             case KEY_PROPERTY_FLAGS: return "property_flags";
+            case KEY_PROTECTED_MEMORY: return "protected_memory";
+            case KEY_PROTECTED_MEMORY_FEATURES: return "protected_memory_features";
+            case KEY_QUAD_OPERATIONS_IN_ALL_STAGES: return "quad_operations_in_all_stages";
             case KEY_QUEUE_COUNT: return "queue_count";
             case KEY_QUEUE_FLAGS: return "queue_flags";
             case KEY_QUEUES: return "queues";
@@ -829,8 +1096,12 @@
             case KEY_SAMPLED_IMAGE_INTEGER_SAMPLE_COUNTS: return "sampled_image_integer_sample_counts";
             case KEY_SAMPLED_IMAGE_STENCIL_SAMPLE_COUNTS: return "sampled_image_stencil_sample_counts";
             case KEY_SAMPLER_ANISOTROPY: return "sampler_anisotropy";
+            case KEY_SAMPLER_YCBCR_CONVERSION: return "sampler_ycbcr_conversion";
+            case KEY_SAMPLER_YCBCR_CONVERSION_FEATURES: return "sampler_ycbcr_conversion_features";
             case KEY_SHADER_CLIP_DISTANCE: return "shader_clip_distance";
             case KEY_SHADER_CULL_DISTANCE: return "shader_cull_distance";
+            case KEY_SHADER_DRAW_PARAMETER_FEATURES: return "shader_draw_parameter_features";
+            case KEY_SHADER_DRAW_PARAMETERS: return "shader_draw_parameters";
             case KEY_SHADER_FLOAT64: return "shader_float64";
             case KEY_SHADER_IMAGE_GATHER_EXTENDED: return "shader_image_gather_extended";
             case KEY_SHADER_INT16: return "shader_int16";
@@ -860,11 +1131,19 @@
             case KEY_SPARSE_RESIDENCY_IMAGE_3D: return "sparse_residency_image_3d";
             case KEY_SPEC_VERSION: return "spec_version";
             case KEY_STANDARD_SAMPLE_LOCATIONS: return "standard_sample_locations";
+            case KEY_STORAGE_BUFFER_16BIT_ACCESS: return "storage_buffer_16bit_access";
             case KEY_STORAGE_IMAGE_SAMPLE_COUNTS: return "storage_image_sample_counts";
+            case KEY_STORAGE_INPUT_OUTPUT_16: return "storage_input_output_16";
+            case KEY_STORAGE_PUSH_CONSTANT_16: return "storage_push_constant_16";
             case KEY_STRICT_LINES: return "strict_lines";
             case KEY_SUB_PIXEL_INTERPOLATION_OFFSET_BITS: return "sub_pixel_interpolation_offset_bits";
             case KEY_SUB_PIXEL_PRECISION_BITS: return "sub_pixel_precision_bits";
             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_SUBSET_ALLOCATION: return "subset_allocation";
+            case KEY_SUPPORTED_OPERATIONS: return "supported_operations";
+            case KEY_SUPPORTED_STAGES: return "supported_stages";
             case KEY_TESSELLATION_SHADER: return "tessellation_shader";
             case KEY_TEXTURE_COMPRESSION_ASTC_LDR: return "texture_compression_astc_ldr";
             case KEY_TEXTURE_COMPRESSION_BC: return "texture_compression_bc";
@@ -872,11 +1151,17 @@
             case KEY_TIMESTAMP_COMPUTE_AND_GRAPHICS: return "timestamp_compute_and_graphics";
             case KEY_TIMESTAMP_PERIOD: return "timestamp_period";
             case KEY_TIMESTAMP_VALID_BITS: return "timestamp_valid_bits";
+            case KEY_UNIFORM_AND_STORAGE_BUFFER_16BIT_ACCESS: return "uniform_and_storage_buffer_16bit_access";
             case KEY_VARIABLE_MULTISAMPLE_RATE: return "variable_multisample_rate";
+            case KEY_VARIABLE_POINTER_FEATURES: return "variable_pointer_features";
+            case KEY_VARIABLE_POINTER_FEATURES_KHR: return "variable_pointer_features_khr";
+            case KEY_VARIABLE_POINTERS: return "variable_pointers";
+            case KEY_VARIABLE_POINTERS_STORAGE_BUFFER: return "variable_pointers_storage_buffer";
             case KEY_VENDOR_ID: return "vendor_id";
             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_VARIABLE_POINTERS: return "vk_khr_variable_pointers";
             case KEY_WIDE_LINES: return "wide_lines";
             case KEY_WIDTH: return "width";
             default: throw new RuntimeException("unknown key name: " + name);
diff --git a/tools/cts-holo-generation/Android.mk b/tools/cts-holo-generation/Android.mk
index 43b346d..195f5a5 100644
--- a/tools/cts-holo-generation/Android.mk
+++ b/tools/cts-holo-generation/Android.mk
@@ -23,7 +23,9 @@
 LOCAL_DEX_PREOPT := false
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tools/cts-preconditions/Android.mk b/tools/cts-preconditions/Android.mk
index 76bfaa8..5f00c36 100644
--- a/tools/cts-preconditions/Android.mk
+++ b/tools/cts-preconditions/Android.mk
@@ -27,8 +27,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    compatibility-device-preconditions \
-    legacy-android-test
+    compatibility-device-preconditions
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tools/cts-reference-app-lib/Android.mk b/tools/cts-reference-app-lib/Android.mk
index 8f6039d..f006bb0 100644
--- a/tools/cts-reference-app-lib/Android.mk
+++ b/tools/cts-reference-app-lib/Android.mk
@@ -24,7 +24,9 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
 
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
index 953ef63..e6c4750 100644
--- a/tools/cts-tradefed/Android.mk
+++ b/tools/cts-tradefed/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_JAVA_RESOURCE_DIRS += ../../common/host-side/tradefed/res
 LOCAL_MODULE := cts-tradefed-harness
 LOCAL_JAVA_LIBRARIES += tradefed compatibility-host-util
+LOCAL_STATIC_JAVA_LIBRARIES := google-api-java-client-min-repackaged
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
diff --git a/tools/cts-tradefed/res/config/cts-automated.xml b/tools/cts-tradefed/res/config/cts-automated.xml
index bedfb15..150f8b9 100644
--- a/tools/cts-tradefed/res/config/cts-automated.xml
+++ b/tools/cts-tradefed/res/config/cts-automated.xml
@@ -20,7 +20,7 @@
     <option name="plan" value="cts" />
 
     <option name="skip-preconditions" value="false" />
-    <option name="skip-connectivity-check" value="false" />
+    <option name="skip-system-status-check" value="com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker" />
 
     <!-- Tell all AndroidJUnitTests to exclude certain annotations -->
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:android.platform.test.annotations.RestrictedBuildTest" />
diff --git a/tools/cts-tradefed/res/config/cts-common.xml b/tools/cts-tradefed/res/config/cts-common.xml
index d19cad1..7fd0de0 100644
--- a/tools/cts-tradefed/res/config/cts-common.xml
+++ b/tools/cts-tradefed/res/config/cts-common.xml
@@ -16,6 +16,7 @@
 <configuration description="Common configuration for cts and cts-reference-aosp">
 
     <include name="everything" />
+    <option name="compatibility:run-suite-tag" value="cts" />
     <include name="cts-preconditions" />
     <include name="cts-system-checkers" />
     <include name="cts-known-failures" />
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index 9f0256c..ffbd6b1 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -23,8 +23,8 @@
     <option name="compatibility:exclude-filter" value="CtsLocationTestCases android.location.cts.GnssTtffTests#testTtffWithNetwork" />
 
     <!-- b/35314835 -->
-    <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
-    <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
+    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
+    <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
 
     <!-- b/17595050 -->
     <option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText" />
@@ -126,6 +126,12 @@
     <!-- b/36686383 -->
     <option name="compatibility:exclude-filter" value="CtsIncidentHostTestCases com.android.server.cts.ErrorsTest#testANR" />
 
+    <!-- b/33090965 -->
+    <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf0320x0180" />
+    <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf0640x0360" />
+    <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1280x0720" />
+    <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1920x1080" />
+
     <!-- b/37482372 -->
     <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowHistoryPreservePortraitTest" />
     <option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowInOutPortraitTest" />
@@ -175,6 +181,9 @@
     <option name="compatibility:exclude-filter" value="x86_64 CtsMediaBitstreamsTestCases" />
     <option name="compatibility:exclude-filter" value="mips64 CtsMediaBitstreamsTestCases" />
 
+    <!-- b/38280830 -->
+    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testVp8Goog0Perf1280x0720" />
+
     <!-- b/38420898 -->
     <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyAccelMultiChannel" />
     <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyGyroMultiChannel" />
diff --git a/tools/cts-tradefed/res/config/cts-preconditions.xml b/tools/cts-tradefed/res/config/cts-preconditions.xml
index 21215df..0f0f9b7 100644
--- a/tools/cts-tradefed/res/config/cts-preconditions.xml
+++ b/tools/cts-tradefed/res/config/cts-preconditions.xml
@@ -21,7 +21,11 @@
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
         <option name="target" value="host" />
-        <option name="config-filename" value="cts"/>
+        <!-- the name under which to find the configuration -->
+        <option name="config-filename" value="cts" />
+        <option name="extract-from-resource" value="true" />
+        <!-- the name of the resource inside the jar -->
+        <option name="dynamic-resource-name" value="cts-tradefed" />
     </target_preparer>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.StayAwakePreparer" />
diff --git a/tools/cts-tradefed/res/config/cts-suite.xml b/tools/cts-tradefed/res/config/cts-suite.xml
index f2672ec..f35a94b 100644
--- a/tools/cts-tradefed/res/config/cts-suite.xml
+++ b/tools/cts-tradefed/res/config/cts-suite.xml
@@ -16,7 +16,9 @@
 <configuration description="Runs CTS as a suite">
     <!-- running config -->
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
-    <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite" />
+    <test class="com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite">
+        <option name="run-suite-tag" value="cts" />
+    </test>
 
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
@@ -63,5 +65,9 @@
 
     <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
     <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
-    <result_reporter class="com.android.tradefed.result.suite.SuiteResultReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CertificationSuiteResultReporter">
+        <!-- Avoid double posting from this reporter until ResultReporter is removed -->
+        <option name="disable-result-posting" value="true" />
+    </result_reporter>
+
 </configuration>
diff --git a/tools/cts-tradefed/res/config/retry.xml b/tools/cts-tradefed/res/config/retry.xml
index cbecada..d425d8e 100644
--- a/tools/cts-tradefed/res/config/retry.xml
+++ b/tools/cts-tradefed/res/config/retry.xml
@@ -14,10 +14,18 @@
      limitations under the License.
 -->
 <configuration description="Runs a retry of a previous CTS session.">
+    <option name="dynamic-sharding" value="true" />
+    <option name="disable-strict-sharding" value="true" />
 
     <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
     <test class="com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest" />
+
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
+    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:fallback-to-serial-rerun:false" />
+
     <logger class="com.android.tradefed.log.FileLogger">
         <option name="log-level-display" value="WARN" />
     </logger>
@@ -32,6 +40,16 @@
     <option name="skip-device-info" value="true" />
     <option name="enable-root" value="false" />
 
+    <!-- retain 200MB of host log -->
+    <option name="max-log-size" value="200" />
+    <!--  retain 200MB of logcat -->
+    <option name="max-tmp-logcat-file" value="209715200" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put global package_verifier_enable 0" />
+        <option name="teardown-command" value="settings put global package_verifier_enable 1"/>
+    </target_preparer>
+
     <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
     <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
 
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
index 512d8bd..5d5df59 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
@@ -46,7 +46,9 @@
 
     @Override
     protected void tearDown() throws Exception {
-        System.setProperty(PROPERTY_NAME, mOriginalProperty);
+        if (mOriginalProperty != null) {
+            System.setProperty(PROPERTY_NAME, mOriginalProperty);
+        }
         super.tearDown();
     }
 
diff --git a/tools/device-setup/TestDeviceSetup/Android.mk b/tools/device-setup/TestDeviceSetup/Android.mk
index b7056ad..1820347 100644
--- a/tools/device-setup/TestDeviceSetup/Android.mk
+++ b/tools/device-setup/TestDeviceSetup/Android.mk
@@ -27,7 +27,7 @@
 
 # uncomment when b/13282254 is fixed
 #LOCAL_SDK_VERSION := current
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := system_current
 
 LOCAL_PACKAGE_NAME := TestDeviceSetup
 
diff --git a/tools/utils/java-cert-list-generator.sh b/tools/utils/java-cert-list-generator.sh
index 3b09f86..1f33c4a 100755
--- a/tools/utils/java-cert-list-generator.sh
+++ b/tools/utils/java-cert-list-generator.sh
@@ -60,5 +60,20 @@
 
 cat <<-ENDCLASS
   };
+ENDCLASS
+
+cat <<-STARTCLASS
+
+  static final String[] WFA_CERTIFICATE_DATA = {
+STARTCLASS
+
+CERT_DIRECTORY=$ANDROID_BUILD_TOP/system/ca-certificates/wfa_certs/files/
+for FILE in `ls $CERT_DIRECTORY`; do
+  FINGERPRINT=`cat $CERT_DIRECTORY/$FILE | grep "SHA1 Fingerprint=" | cut -d '=' -f 2`
+  echo "      \"${FINGERPRINT}\","
+done
+
+cat <<-ENDCLASS
+  };
 }
 ENDCLASS
diff --git a/tools/vm-tests-tf/Android.mk b/tools/vm-tests-tf/Android.mk
index 70c74e1..ea4f09b 100644
--- a/tools/vm-tests-tf/Android.mk
+++ b/tools/vm-tests-tf/Android.mk
@@ -25,7 +25,6 @@
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LIBRARIES := junit
--include cts/error_prone_rules_tests.mk
 include $(BUILD_JAVA_LIBRARY)
 
 cts-tf-dalvik-lib.jar := $(full_classes_jar)
diff --git a/tools/vm-tests-tf/AndroidTest.xml b/tools/vm-tests-tf/AndroidTest.xml
index 72b3914..fc0f8af 100644
--- a/tools/vm-tests-tf/AndroidTest.xml
+++ b/tools/vm-tests-tf/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS VM test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
     <option name="not-strict-shardable" value= "true" />
     <target_preparer class="android.core.vm.targetprep.VmTestPreparer" />